xref: /nrf52832-nimble/rt-thread/tools/mkromfs.py (revision 104654410c56c573564690304ae786df310c91fc)
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