1#!/usr/bin/env python 2 3import sys 4import os 5 6import struct 7from collections import namedtuple 8import StringIO 9 10import argparse 11parser = argparse.ArgumentParser() 12parser.add_argument('rootdir', type=str, help='the path to rootfs') 13parser.add_argument('output', type=argparse.FileType('wb'), nargs='?', help='output file name') 14parser.add_argument('--dump', action='store_true', help='dump the fs hierarchy') 15parser.add_argument('--binary', action='store_true', help='output binary file') 16parser.add_argument('--addr', default='0', help='set the base address of the binary file, default to 0.') 17 18class File(object): 19 def __init__(self, name): 20 self._name = name 21 self._data = open(name, 'rb').read() 22 23 @property 24 def name(self): 25 return self._name 26 27 @property 28 def c_name(self): 29 return '_' + self._name.replace('.', '_') 30 31 @property 32 def bin_name(self): 33 # Pad to 4 bytes boundary with \0 34 pad_len = 4 35 bn = self._name + '\0' * (pad_len - len(self._name) % pad_len) 36 return bn 37 38 def c_data(self, prefix=''): 39 '''Get the C code represent of the file content.''' 40 head = 'static const rt_uint8_t %s[] = {\n' % \ 41 (prefix + self.c_name) 42 tail = '\n};' 43 44 if self.entry_size == 0: 45 return '' 46 47 return head + ','.join(('0x%02x' % ord(i) for i in self._data)) + tail 48 49 @property 50 def entry_size(self): 51 return len(self._data) 52 53 def bin_data(self, base_addr=0x0): 54 return bytes(self._data) 55 56 def dump(self, indent=0): 57 print('%s%s' % (' ' * indent, self._name)) 58 59class Folder(object): 60 bin_fmt = struct.Struct('IIII') 61 bin_item = namedtuple('dirent', 'type, name, data, size') 62 63 def __init__(self, name): 64 self._name = name 65 self._children = [] 66 67 @property 68 def name(self): 69 return self._name 70 71 @property 72 def c_name(self): 73 # add _ to avoid conflict with C key words. 74 return '_' + self._name 75 76 @property 77 def bin_name(self): 78 # Pad to 4 bytes boundary with \0 79 pad_len = 4 80 bn = self._name + '\0' * (pad_len - len(self._name) % pad_len) 81 return bn 82 83 def walk(self): 84 # os.listdir will return unicode list if the argument is unicode. 85 # TODO: take care of the unicode names 86 for ent in os.listdir(u'.'): 87 if os.path.isdir(ent): 88 cwd = os.getcwdu() 89 d = Folder(ent) 90 # depth-first 91 os.chdir(os.path.join(cwd, ent)) 92 d.walk() 93 # restore the cwd 94 os.chdir(cwd) 95 self._children.append(d) 96 else: 97 self._children.append(File(ent)) 98 99 def sort(self): 100 def _sort(x, y): 101 if x.name == y.name: 102 return 0 103 elif x.name > y.name: 104 return 1 105 else: 106 return -1 107 self._children.sort(cmp=_sort) 108 109 # sort recursively 110 for c in self._children: 111 if isinstance(c, Folder): 112 c.sort() 113 114 def dump(self, indent=0): 115 print('%s%s' % (' ' * indent, self._name)) 116 for c in self._children: 117 c.dump(indent + 1) 118 119 def c_data(self, prefix=''): 120 '''get the C code represent of the folder. 121 122 It is recursive.''' 123 # make the current dirent 124 # static is good. Only root dirent is global visible. 125 if self.entry_size == 0: 126 return '' 127 128 dhead = 'static const struct romfs_dirent %s[] = {\n' % (prefix + self.c_name) 129 dtail = '\n};' 130 body_fmt = ' {{{type}, "{name}", (rt_uint8_t *){data}, sizeof({data})/sizeof({data}[0])}}' 131 body_fmt0= ' {{{type}, "{name}", RT_NULL, 0}}' 132 # prefix of children 133 cpf = prefix+self.c_name 134 body_li = [] 135 payload_li = [] 136 for c in self._children: 137 entry_size = c.entry_size 138 if isinstance(c, File): 139 tp = 'ROMFS_DIRENT_FILE' 140 elif isinstance(c, Folder): 141 tp = 'ROMFS_DIRENT_DIR' 142 else: 143 assert False, 'Unkown instance:%s' % str(c) 144 if entry_size == 0: 145 body_li.append(body_fmt0.format(type=tp, name = c.name)) 146 else: 147 body_li.append(body_fmt.format(type=tp, 148 name=c.name, 149 data=cpf+c.c_name)) 150 payload_li.append(c.c_data(prefix=cpf)) 151 152 # All the data we need is defined in payload so we should append the 153 # dirent to it. It also meet the depth-first policy in this code. 154 payload_li.append(dhead + ',\n'.join(body_li) + dtail) 155 156 return '\n\n'.join(payload_li) 157 158 @property 159 def entry_size(self): 160 return len(self._children) 161 162 def bin_data(self, base_addr=0x0): 163 '''Return StringIO object''' 164 # The binary layout is different from the C code layout. We put the 165 # dirent before the payload in this mode. But the idea is still simple: 166 # Depth-First. 167 168 #{ 169 # rt_uint32_t type; 170 # const char *name; 171 # const rt_uint8_t *data; 172 # rt_size_t size; 173 #} 174 d_li = [] 175 # payload base 176 p_base = base_addr + self.bin_fmt.size * self.entry_size 177 # the length to record how many data is in 178 v_len = p_base 179 # payload 180 p_li = [] 181 for c in self._children: 182 if isinstance(c, File): 183 # ROMFS_DIRENT_FILE 184 tp = 0 185 elif isinstance(c, Folder): 186 # ROMFS_DIRENT_DIR 187 tp = 1 188 else: 189 assert False, 'Unkown instance:%s' % str(c) 190 191 name = bytes(c.bin_name) 192 name_addr = v_len 193 v_len += len(name) 194 195 data = c.bin_data(base_addr=v_len) 196 data_addr = v_len 197 # pad the data to 4 bytes boundary 198 pad_len = 4 199 if len(data) % pad_len != 0: 200 data += '\0' * (pad_len - len(data) % pad_len) 201 v_len += len(data) 202 203 d_li.append(self.bin_fmt.pack(*self.bin_item( 204 type=tp, 205 name=name_addr, 206 data=data_addr, 207 size=c.entry_size))) 208 209 p_li.extend((name, data)) 210 211 return bytes().join(d_li) + bytes().join(p_li) 212 213def get_c_data(tree): 214 # Handle the root dirent specially. 215 root_dirent_fmt = '''/* Generated by mkromfs. Edit with caution. */ 216#include <rtthread.h> 217#include <dfs_romfs.h> 218 219{data} 220 221const struct romfs_dirent {name} = {{ 222 ROMFS_DIRENT_DIR, "/", (rt_uint8_t *){rootdirent}, sizeof({rootdirent})/sizeof({rootdirent}[0]) 223}}; 224''' 225 226 return root_dirent_fmt.format(name='romfs_root', 227 rootdirent=tree.c_name, 228 data=tree.c_data()) 229 230def get_bin_data(tree, base_addr): 231 v_len = base_addr + Folder.bin_fmt.size 232 name = bytes('/\0\0\0') 233 name_addr = v_len 234 v_len += len(name) 235 data_addr = v_len 236 # root entry 237 data = Folder.bin_fmt.pack(*Folder.bin_item(type=1, 238 name=name_addr, 239 data=data_addr, 240 size=tree.entry_size)) 241 return data + name + tree.bin_data(v_len) 242 243if __name__ == '__main__': 244 args = parser.parse_args() 245 246 os.chdir(args.rootdir) 247 248 tree = Folder('romfs_root') 249 tree.walk() 250 tree.sort() 251 252 if args.dump: 253 tree.dump() 254 255 if args.binary: 256 data = get_bin_data(tree, int(args.addr, 16)) 257 else: 258 data = get_c_data(tree) 259 260 output = args.output 261 if not output: 262 output = sys.stdout 263 264 output.write(data) 265