Branch data Line data Source code
1 : : /*
2 : : * This file is part of the MicroPython project, http://micropython.org/
3 : : *
4 : : * The MIT License (MIT)
5 : : *
6 : : * Copyright (c) 2022 Damien P. George
7 : : *
8 : : * Permission is hereby granted, free of charge, to any person obtaining a copy
9 : : * of this software and associated documentation files (the "Software"), to deal
10 : : * in the Software without restriction, including without limitation the rights
11 : : * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 : : * copies of the Software, and to permit persons to whom the Software is
13 : : * furnished to do so, subject to the following conditions:
14 : : *
15 : : * The above copyright notice and this permission notice shall be included in
16 : : * all copies or substantial portions of the Software.
17 : : *
18 : : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 : : * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 : : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 : : * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 : : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 : : * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 : : * THE SOFTWARE.
25 : : */
26 : :
27 : : // ROMFS filesystem format
28 : : // =======================
29 : : //
30 : : // ROMFS is a flexible and extensible filesystem format designed to represent a
31 : : // directory hierarchy with files, where those files are read-only and their data
32 : : // can be memory mapped.
33 : : //
34 : : // Concepts:
35 : : // - varuint: An unsigned integer that is encoded in a variable number of bytes. It is
36 : : // stored big-endian with the high bit of the byte set if there are following bytes.
37 : : // - record: A variable sized element with a type. It is stored as two varuint's and then
38 : : // a payload. The first varuint is the record kind and the second varuint is the
39 : : // payload length (which may be zero bytes long).
40 : : //
41 : : // A ROMFS filesystem is a record with record kind 0x14a6b1, chosen so the encoded value
42 : : // is 0xd2-0xcd-0x31 which is "RM1" with the first two bytes having their high bit set.
43 : : // If the ROMFS record's payload is non-empty then it contains records.
44 : : //
45 : : // Record types:
46 : : // - 0 = unused, can be used to detect corruption of the filesystem.
47 : : // - 1 = padding/comments, can contain any data in their payload.
48 : : // - 2 = verbatim data, used to store file data.
49 : : // - 3 = indirect data, pointer to offset within the ROMFS payload.
50 : : // - 4 = a directory: payload contains a varuint which is the length of the directory
51 : : // name in bytes, then the name, then optional nested records for the contents
52 : : // of the directory (including optional metadata).
53 : : // - 5 = a file: payload contains a varuint which is the length of the filename in bytes
54 : : // then the name, then optional nested records.
55 : : //
56 : : // Remarks:
57 : : // - A varuint can be padded if needed by prepending with one or more 0x80 bytes. This
58 : : // padding does not change any semantics.
59 : : // - The size of the ROMFS record (including kind and length and payload) must be a
60 : : // multiple of 2 (because it's not possible to add a padding record of one byte).
61 : : // - File data can be optionally aligned using padding records and/or indirect data
62 : : // records.
63 : : // - There is no limit to the size of directory/file names or file data.
64 : : //
65 : : // Unknown record types must be skipped over. They may in the future add optional
66 : : // features, while still retaining backwards compatibility. Such features may be:
67 : : // - Alignment requirements of the ROMFS record.
68 : : // - Timestamps on directories/files.
69 : : // - A precomputed hash of a file, or other metadata.
70 : : // - An optimised lookup table indexing the directory hierarchy.
71 : :
72 : : #include <string.h>
73 : :
74 : : #include "py/bc.h"
75 : : #include "py/runtime.h"
76 : : #include "py/mperrno.h"
77 : : #include "extmod/vfs.h"
78 : : #include "extmod/vfs_rom.h"
79 : :
80 : : #if MICROPY_VFS_ROM
81 : :
82 : : #define ROMFS_SIZE_MIN (4)
83 : : #define ROMFS_HEADER_BYTE0 (0x80 | 'R')
84 : : #define ROMFS_HEADER_BYTE1 (0x80 | 'M')
85 : : #define ROMFS_HEADER_BYTE2 (0x00 | '1')
86 : :
87 : : // Values for `record_kind_t`.
88 : : #define ROMFS_RECORD_KIND_UNUSED (0)
89 : : #define ROMFS_RECORD_KIND_PADDING (1)
90 : : #define ROMFS_RECORD_KIND_DATA_VERBATIM (2)
91 : : #define ROMFS_RECORD_KIND_DATA_POINTER (3)
92 : : #define ROMFS_RECORD_KIND_DIRECTORY (4)
93 : : #define ROMFS_RECORD_KIND_FILE (5)
94 : :
95 : : typedef mp_uint_t record_kind_t;
96 : :
97 : : struct _mp_obj_vfs_rom_t {
98 : : mp_obj_base_t base;
99 : : mp_obj_t memory;
100 : : const uint8_t *filesystem;
101 : : const uint8_t *filesystem_end;
102 : : };
103 : :
104 : 472 : static record_kind_t extract_record(const uint8_t **fs, const uint8_t **fs_next) {
105 : 472 : record_kind_t record_kind = mp_decode_uint(fs);
106 : 472 : mp_uint_t record_len = mp_decode_uint(fs);
107 : 472 : *fs_next = *fs + record_len;
108 : 472 : return record_kind;
109 : : }
110 : :
111 : 72 : static void extract_data(mp_obj_vfs_rom_t *self, const uint8_t *fs, const uint8_t *fs_top, size_t *size_out, const uint8_t **data_out) {
112 : 72 : *size_out = 0;
113 : 72 : *data_out = NULL;
114 [ + - ]: 76 : while (fs < fs_top) {
115 : 76 : const uint8_t *fs_next;
116 : 76 : record_kind_t record_kind = extract_record(&fs, &fs_next);
117 [ + + ]: 76 : if (record_kind == ROMFS_RECORD_KIND_DATA_VERBATIM) {
118 : : // Verbatim data.
119 : 58 : *size_out = fs_next - fs;
120 : 58 : *data_out = fs;
121 : 72 : break;
122 [ + + ]: 18 : } else if (record_kind == ROMFS_RECORD_KIND_DATA_POINTER) {
123 : : // Pointer to data.
124 : 14 : *size_out = mp_decode_uint(&fs);
125 : 14 : *data_out = self->filesystem + mp_decode_uint(&fs);
126 : 14 : break;
127 : : } else {
128 : : // Skip this record.
129 : 4 : fs = fs_next;
130 : : }
131 : : }
132 : 72 : }
133 : :
134 : : // Searches for `path` in the filesystem.
135 : : // `path` must be null-terminated.
136 : 102 : mp_import_stat_t mp_vfs_rom_search_filesystem(mp_obj_vfs_rom_t *self, const char *path, size_t *size_out, const uint8_t **data_out) {
137 : 102 : const uint8_t *fs = self->filesystem;
138 : 102 : const uint8_t *fs_top = self->filesystem_end;
139 : 102 : size_t path_len = strlen(path);
140 [ + + ]: 102 : if (*path == '/') {
141 : : // An optional slash at the start of the path enters the top-level filesystem.
142 : 84 : ++path;
143 : 84 : --path_len;
144 : : }
145 [ + + ]: 350 : while (path_len > 0 && fs < fs_top) {
146 : 292 : const uint8_t *fs_next;
147 : 292 : record_kind_t record_kind = extract_record(&fs, &fs_next);
148 [ + + ]: 292 : if (record_kind == ROMFS_RECORD_KIND_DIRECTORY || record_kind == ROMFS_RECORD_KIND_FILE) {
149 : : // A directory or file record.
150 : 228 : mp_uint_t name_len = mp_decode_uint(&fs);
151 [ + + ]: 228 : if ((name_len == path_len
152 [ + + + + ]: 126 : || (name_len < path_len && path[name_len] == '/'))
153 [ + + ]: 122 : && memcmp(path, fs, name_len) == 0) {
154 : : // Name matches, so enter this record.
155 : 74 : fs += name_len;
156 : 74 : fs_top = fs_next;
157 : 74 : path += name_len;
158 : 74 : path_len -= name_len;
159 [ + + ]: 74 : if (record_kind == ROMFS_RECORD_KIND_DIRECTORY) {
160 : : // Continue searching in this directory.
161 [ + + ]: 30 : if (*path == '/') {
162 : 20 : ++path;
163 : 20 : --path_len;
164 : : }
165 : : } else {
166 : : // Return this file.
167 [ + - ]: 44 : if (path_len != 0) {
168 : 44 : return MP_IMPORT_STAT_NO_EXIST;
169 : : }
170 [ + + ]: 44 : if (size_out != NULL) {
171 : 36 : extract_data(self, fs, fs_top, size_out, data_out);
172 : : }
173 : 44 : return MP_IMPORT_STAT_FILE;
174 : : }
175 : : } else {
176 : : // Skip this directory/file record.
177 : 154 : fs = fs_next;
178 : : }
179 : : } else {
180 : : // Skip this record.
181 : 64 : fs = fs_next;
182 : : }
183 : : }
184 [ + + ]: 58 : if (path_len == 0) {
185 [ + - ]: 38 : if (size_out != NULL) {
186 : 38 : *size_out = fs_top - fs;
187 : 38 : *data_out = fs;
188 : : }
189 : 38 : return MP_IMPORT_STAT_DIR;
190 : : }
191 : : return MP_IMPORT_STAT_NO_EXIST;
192 : : }
193 : :
194 : 48 : static mp_obj_t vfs_rom_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
195 : 48 : mp_arg_check_num(n_args, n_kw, 1, 1, false);
196 : :
197 : 48 : mp_obj_vfs_rom_t *self = m_new_obj(mp_obj_vfs_rom_t);
198 : 48 : self->base.type = type;
199 : 48 : self->memory = args[0];
200 : :
201 : 48 : mp_buffer_info_t bufinfo;
202 [ + + ]: 48 : if (mp_get_buffer(self->memory, &bufinfo, MP_BUFFER_READ)) {
203 [ + + ]: 46 : if (bufinfo.len < ROMFS_SIZE_MIN) {
204 : 4 : mp_raise_OSError(MP_ENODEV);
205 : : }
206 : 42 : self->filesystem = bufinfo.buf;
207 : : } else {
208 : 2 : self->filesystem = (const uint8_t *)(uintptr_t)mp_obj_get_int_truncated(self->memory);
209 : : }
210 : :
211 : : // Verify it is a ROMFS.
212 [ + + ]: 44 : if (!(self->filesystem[0] == ROMFS_HEADER_BYTE0
213 [ + - ]: 42 : && self->filesystem[1] == ROMFS_HEADER_BYTE1
214 [ - + ]: 42 : && self->filesystem[2] == ROMFS_HEADER_BYTE2)) {
215 : 2 : mp_raise_OSError(MP_ENODEV);
216 : : }
217 : :
218 : : // The ROMFS is a record itself, so enter into it and compute its limit.
219 : 42 : extract_record(&self->filesystem, &self->filesystem_end);
220 : :
221 : 42 : return MP_OBJ_FROM_PTR(self);
222 : : }
223 : :
224 : 22 : static mp_obj_t vfs_rom_mount(mp_obj_t self_in, mp_obj_t readonly, mp_obj_t mkfs) {
225 : 22 : (void)self_in;
226 : 22 : (void)readonly;
227 [ + + ]: 22 : if (mp_obj_is_true(mkfs)) {
228 : 2 : mp_raise_OSError(MP_EPERM);
229 : : }
230 : 20 : return mp_const_none;
231 : : }
232 : : static MP_DEFINE_CONST_FUN_OBJ_3(vfs_rom_mount_obj, vfs_rom_mount);
233 : :
234 : : // mp_vfs_rom_file_open is implemented in vfs_rom_file.c.
235 : : static MP_DEFINE_CONST_FUN_OBJ_3(vfs_rom_open_obj, mp_vfs_rom_file_open);
236 : :
237 : 6 : static mp_obj_t vfs_rom_chdir(mp_obj_t self_in, mp_obj_t path_in) {
238 : 6 : mp_obj_vfs_rom_t *self = MP_OBJ_TO_PTR(self_in);
239 : 6 : const char *path = mp_vfs_rom_get_path_str(self, path_in);
240 [ + - + + ]: 6 : if (path[0] == '/' && path[1] == '\0') {
241 : : // Allow chdir to the root of the filesystem.
242 : 4 : } else {
243 : : // Don't allow chdir to any subdirectory (not currently implemented).
244 : 2 : mp_raise_OSError(MP_EOPNOTSUPP);
245 : : }
246 : 4 : return mp_const_none;
247 : : }
248 : : static MP_DEFINE_CONST_FUN_OBJ_2(vfs_rom_chdir_obj, vfs_rom_chdir);
249 : :
250 : : typedef struct _vfs_rom_ilistdir_it_t {
251 : : mp_obj_base_t base;
252 : : mp_fun_1_t iternext;
253 : : mp_obj_vfs_rom_t *vfs_rom;
254 : : bool is_str;
255 : : const uint8_t *index;
256 : : const uint8_t *index_top;
257 : : } vfs_rom_ilistdir_it_t;
258 : :
259 : 68 : static mp_obj_t vfs_rom_ilistdir_it_iternext(mp_obj_t self_in) {
260 : 68 : vfs_rom_ilistdir_it_t *self = MP_OBJ_TO_PTR(self_in);
261 : :
262 [ + + ]: 82 : while (self->index < self->index_top) {
263 : 62 : const uint8_t *index_next;
264 : 62 : record_kind_t record_kind = extract_record(&self->index, &index_next);
265 : 62 : uint32_t type;
266 : 62 : mp_uint_t name_len;
267 : 62 : size_t data_len;
268 [ + + ]: 62 : if (record_kind == ROMFS_RECORD_KIND_DIRECTORY || record_kind == ROMFS_RECORD_KIND_FILE) {
269 : : // A directory or file record.
270 : 48 : name_len = mp_decode_uint(&self->index);
271 [ + + ]: 48 : if (record_kind == ROMFS_RECORD_KIND_DIRECTORY) {
272 : : // A directory.
273 : 12 : type = MP_S_IFDIR;
274 : 12 : data_len = index_next - self->index - name_len;
275 : : } else {
276 : : // A file.
277 : 36 : type = MP_S_IFREG;
278 : 36 : const uint8_t *data_value;
279 : 36 : extract_data(self->vfs_rom, self->index + name_len, index_next, &data_len, &data_value);
280 : : }
281 : : } else {
282 : : // Skip this record.
283 : 14 : self->index = index_next;
284 : 14 : continue;
285 : : }
286 : :
287 : 48 : const uint8_t *name_str = self->index;
288 : 48 : self->index = index_next;
289 : :
290 : : // Make 4-tuple with info about this entry: (name, attr, inode, size)
291 : 48 : mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(4, NULL));
292 : :
293 [ + + ]: 48 : if (self->is_str) {
294 : 42 : t->items[0] = mp_obj_new_str((const char *)name_str, name_len);
295 : : } else {
296 : 6 : t->items[0] = mp_obj_new_bytes(name_str, name_len);
297 : : }
298 : :
299 : 48 : t->items[1] = MP_OBJ_NEW_SMALL_INT(type);
300 : 48 : t->items[2] = MP_OBJ_NEW_SMALL_INT(0);
301 : 48 : t->items[3] = mp_obj_new_int(data_len);
302 : :
303 : 48 : return MP_OBJ_FROM_PTR(t);
304 : : }
305 : :
306 : : return MP_OBJ_STOP_ITERATION;
307 : : }
308 : :
309 : 22 : static mp_obj_t vfs_rom_ilistdir(mp_obj_t self_in, mp_obj_t path_in) {
310 : 22 : mp_obj_vfs_rom_t *self = MP_OBJ_TO_PTR(self_in);
311 : 22 : vfs_rom_ilistdir_it_t *iter = m_new_obj(vfs_rom_ilistdir_it_t);
312 : 22 : iter->base.type = &mp_type_polymorph_iter;
313 : 22 : iter->iternext = vfs_rom_ilistdir_it_iternext;
314 : 22 : iter->vfs_rom = self;
315 : 22 : iter->is_str = mp_obj_get_type(path_in) == &mp_type_str;
316 : 22 : const char *path = mp_vfs_rom_get_path_str(self, path_in);
317 : 22 : size_t size;
318 [ + + ]: 22 : if (mp_vfs_rom_search_filesystem(self, path, &size, &iter->index) != MP_IMPORT_STAT_DIR) {
319 : 2 : mp_raise_OSError(MP_ENOENT);
320 : : }
321 : 20 : iter->index_top = iter->index + size;
322 : 20 : return MP_OBJ_FROM_PTR(iter);
323 : : }
324 : : static MP_DEFINE_CONST_FUN_OBJ_2(vfs_rom_ilistdir_obj, vfs_rom_ilistdir);
325 : :
326 : 22 : static mp_obj_t vfs_rom_stat(mp_obj_t self_in, mp_obj_t path_in) {
327 : 22 : mp_obj_vfs_rom_t *self = MP_OBJ_TO_PTR(self_in);
328 : 22 : const char *path = mp_vfs_rom_get_path_str(self, path_in);
329 : 22 : size_t file_size;
330 : 22 : const uint8_t *file_data;
331 : 22 : mp_import_stat_t stat = mp_vfs_rom_search_filesystem(self, path, &file_size, &file_data);
332 [ + + ]: 22 : if (stat == MP_IMPORT_STAT_NO_EXIST) {
333 : 4 : mp_raise_OSError(MP_ENOENT);
334 : : }
335 : 18 : mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(10, NULL));
336 [ + + ]: 18 : t->items[0] = MP_OBJ_NEW_SMALL_INT(stat == MP_IMPORT_STAT_FILE ? MP_S_IFREG : MP_S_IFDIR); // st_mode
337 : 18 : t->items[1] = MP_OBJ_NEW_SMALL_INT(0); // st_ino
338 : 18 : t->items[2] = MP_OBJ_NEW_SMALL_INT(0); // st_dev
339 : 18 : t->items[3] = MP_OBJ_NEW_SMALL_INT(0); // st_nlink
340 : 18 : t->items[4] = MP_OBJ_NEW_SMALL_INT(0); // st_uid
341 : 18 : t->items[5] = MP_OBJ_NEW_SMALL_INT(0); // st_gid
342 : 18 : t->items[6] = MP_OBJ_NEW_SMALL_INT(file_size); // st_size
343 : 18 : t->items[7] = MP_OBJ_NEW_SMALL_INT(0); // st_atime
344 : 18 : t->items[8] = MP_OBJ_NEW_SMALL_INT(0); // st_mtime
345 : 18 : t->items[9] = MP_OBJ_NEW_SMALL_INT(0); // st_ctime
346 : 18 : return MP_OBJ_FROM_PTR(t);
347 : : }
348 : : static MP_DEFINE_CONST_FUN_OBJ_2(vfs_rom_stat_obj, vfs_rom_stat);
349 : :
350 : 4 : static mp_obj_t vfs_rom_statvfs(mp_obj_t self_in, mp_obj_t path_in) {
351 : 4 : mp_obj_vfs_rom_t *self = MP_OBJ_TO_PTR(self_in);
352 : 4 : (void)path_in;
353 : 4 : size_t filesystem_len = self->filesystem_end - self->filesystem;
354 : 4 : mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(10, NULL));
355 : 4 : t->items[0] = MP_OBJ_NEW_SMALL_INT(1); // f_bsize
356 : 4 : t->items[1] = MP_OBJ_NEW_SMALL_INT(0); // f_frsize
357 : 4 : t->items[2] = mp_obj_new_int_from_uint(filesystem_len); // f_blocks
358 : 4 : t->items[3] = MP_OBJ_NEW_SMALL_INT(0); // f_bfree
359 : 4 : t->items[4] = MP_OBJ_NEW_SMALL_INT(0); // f_bavail
360 : 4 : t->items[5] = MP_OBJ_NEW_SMALL_INT(0); // f_files
361 : 4 : t->items[6] = MP_OBJ_NEW_SMALL_INT(0); // f_ffree
362 : 4 : t->items[7] = MP_OBJ_NEW_SMALL_INT(0); // f_favail
363 : 4 : t->items[8] = MP_OBJ_NEW_SMALL_INT(0); // f_flags
364 : 4 : t->items[9] = MP_OBJ_NEW_SMALL_INT(32767); // f_namemax
365 : 4 : return MP_OBJ_FROM_PTR(t);
366 : : }
367 : : static MP_DEFINE_CONST_FUN_OBJ_2(vfs_rom_statvfs_obj, vfs_rom_statvfs);
368 : :
369 : : static const mp_rom_map_elem_t vfs_rom_locals_dict_table[] = {
370 : : { MP_ROM_QSTR(MP_QSTR_mount), MP_ROM_PTR(&vfs_rom_mount_obj) },
371 : : { MP_ROM_QSTR(MP_QSTR_umount), MP_ROM_PTR(&mp_identity_obj) },
372 : : { MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&vfs_rom_open_obj) },
373 : :
374 : : { MP_ROM_QSTR(MP_QSTR_chdir), MP_ROM_PTR(&vfs_rom_chdir_obj) },
375 : : { MP_ROM_QSTR(MP_QSTR_ilistdir), MP_ROM_PTR(&vfs_rom_ilistdir_obj) },
376 : : { MP_ROM_QSTR(MP_QSTR_stat), MP_ROM_PTR(&vfs_rom_stat_obj) },
377 : : { MP_ROM_QSTR(MP_QSTR_statvfs), MP_ROM_PTR(&vfs_rom_statvfs_obj) },
378 : : };
379 : : static MP_DEFINE_CONST_DICT(vfs_rom_locals_dict, vfs_rom_locals_dict_table);
380 : :
381 : 18 : static mp_import_stat_t mp_vfs_rom_import_stat(void *self_in, const char *path) {
382 : 18 : mp_obj_vfs_rom_t *self = MP_OBJ_TO_PTR(self_in);
383 : 18 : return mp_vfs_rom_search_filesystem(self, path, NULL, NULL);
384 : : }
385 : :
386 : : static const mp_vfs_proto_t vfs_rom_proto = {
387 : : .import_stat = mp_vfs_rom_import_stat,
388 : : };
389 : :
390 : : MP_DEFINE_CONST_OBJ_TYPE(
391 : : mp_type_vfs_rom,
392 : : MP_QSTR_VfsRom,
393 : : MP_TYPE_FLAG_NONE,
394 : : make_new, vfs_rom_make_new,
395 : : protocol, &vfs_rom_proto,
396 : : locals_dict, &vfs_rom_locals_dict
397 : : );
398 : :
399 : : #endif // MICROPY_VFS_ROM
|