1 /*
2 * Copyright (c) 2023 Antmicro <www.antmicro.com>
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <zephyr/logging/log.h>
8 #include <zephyr/random/random.h>
9 #include <zephyr/fs/ext2.h>
10 #include <zephyr/sys/byteorder.h>
11
12 #include "ext2.h"
13 #include "ext2_impl.h"
14 #include "ext2_struct.h"
15 #include "ext2_diskops.h"
16
17 LOG_MODULE_DECLARE(ext2, LOG_LEVEL_DBG);
18
19 FS_EXT2_DECLARE_DEFAULT_CONFIG(ext2_default);
20
validate_config(struct ext2_cfg * cfg)21 static void validate_config(struct ext2_cfg *cfg)
22 {
23 if (cfg->block_size == 0) {
24 cfg->block_size = ext2_default.block_size;
25 }
26
27 if (cfg->bytes_per_inode == 0) {
28 cfg->bytes_per_inode = ext2_default.bytes_per_inode;
29 }
30
31 if (cfg->volume_name[0] == '\0') {
32 strcpy(cfg->volume_name, "ext2");
33 }
34
35 if (!cfg->set_uuid) {
36 /* Generate random UUID */
37 sys_rand_get(cfg->uuid, 16);
38
39 /* Set version of UUID (ver. 4 variant 1) */
40 cfg->uuid[6] = (cfg->uuid[6] & 0x0f) | 0x40;
41 cfg->uuid[8] = (cfg->uuid[8] & 0x3f) | 0x80;
42 }
43 }
44
set_bitmap_padding(uint8_t * bitmap,uint32_t nelems,struct ext2_cfg * cfg)45 static void set_bitmap_padding(uint8_t *bitmap, uint32_t nelems, struct ext2_cfg *cfg)
46 {
47 uint32_t used_bytes = nelems / 8 + (nelems % 8 != 0);
48
49 LOG_DBG("Set bitmap padding: %d bytes", nelems);
50 memset(bitmap, 0x00, used_bytes);
51
52 /* Set padding in block-bitmap block */
53 if (nelems % 8) {
54 bitmap[used_bytes - 1] = (0xff << (nelems % 8)) & 0xff;
55 LOG_DBG("last byte: %02x", (0xff << (nelems % 8)) & 0xff);
56 }
57 memset(bitmap + used_bytes, 0xff, cfg->block_size - used_bytes);
58 }
59
set_bitmap_bits(uint8_t * bitmap,uint32_t to_set)60 static void set_bitmap_bits(uint8_t *bitmap, uint32_t to_set)
61 {
62 int i = 0, bits;
63 uint16_t set_value;
64
65 while (to_set > 0) {
66 bits = MIN(8, to_set);
67 set_value = (1 << bits) - 1;
68 bitmap[i] = (uint8_t)set_value;
69 to_set -= bits;
70 i++;
71 }
72 }
73
default_directory_inode(struct ext2_disk_inode * in,uint32_t nblocks,struct ext2_cfg * cfg)74 static void default_directory_inode(struct ext2_disk_inode *in, uint32_t nblocks,
75 struct ext2_cfg *cfg)
76 {
77 LOG_DBG("Set directory inode: %p", in);
78 in->i_mode = sys_cpu_to_le16(EXT2_DEF_DIR_MODE);
79 in->i_uid = 0;
80 in->i_size = sys_cpu_to_le32(nblocks * cfg->block_size);
81 in->i_atime = 0;
82 in->i_ctime = 0;
83 in->i_mtime = 0;
84 in->i_dtime = 0;
85 in->i_gid = 0;
86 in->i_blocks = sys_cpu_to_le32(nblocks * cfg->block_size / 512);
87 in->i_flags = 0;
88 in->i_osd1 = 0;
89 in->i_generation = 0;
90 in->i_file_acl = 0;
91 in->i_dir_acl = 0;
92 in->i_faddr = 0;
93 memset(in->i_block, 0, EXT2_INODE_BLOCKS * sizeof(uint32_t));
94 }
95
ext2_format(struct ext2_data * fs,struct ext2_cfg * cfg)96 int ext2_format(struct ext2_data *fs, struct ext2_cfg *cfg)
97 {
98 int rc, ret = 0;
99
100 validate_config(cfg);
101 LOG_INF("[Config] blk_sz:%d fs_sz:%d ino_bytes:%d uuid:'%s' vol:'%s'",
102 cfg->block_size, cfg->fs_size, cfg->bytes_per_inode, cfg->uuid,
103 cfg->volume_name);
104
105 uint32_t fs_memory = cfg->fs_size ? MIN(cfg->fs_size, fs->device_size) : fs->device_size;
106
107 /* Calculate value that will be stored in superblock field 's_log_block_size'. That value
108 * tells how much we have to shift 1024 to obtain block size.
109 * To obtain it we calculate: log(block_size) - 11
110 */
111 uint8_t block_log_size = find_msb_set(cfg->block_size) - 11;
112
113 LOG_INF("[Memory] available:%lld requested:%d", fs->device_size, fs_memory);
114
115 if (fs_memory > fs->device_size) {
116 LOG_ERR("No enough space on storage device");
117 return -ENOSPC;
118 }
119
120
121 uint32_t blocks_count = fs_memory / cfg->block_size;
122 uint32_t blocks_per_group = cfg->block_size * 8;
123 uint32_t inodes_per_block = cfg->block_size / sizeof(struct ext2_disk_inode);
124 uint32_t mem_per_inode = cfg->bytes_per_inode + sizeof(struct ext2_disk_inode);
125
126 /* 24 block should be enough to fit minimal file system. */
127 if (blocks_count < 24) {
128 LOG_ERR("Storage device too small to fit ext2 file system");
129 return -ENOSPC;
130 }
131
132 uint32_t sb_offset;
133 uint32_t first_data_block;
134 uint32_t occupied_blocks;
135
136 if (cfg->block_size == 1024) {
137 /* Superblock is stored in 1st block */
138 sb_offset = 0;
139 first_data_block = 1;
140 occupied_blocks = 2;
141 } else {
142 /* Superblock is stored in 0th block */
143 sb_offset = 1024;
144 first_data_block = 0;
145 occupied_blocks = 1;
146 }
147
148 /* Reserve blocks for block group and bitmaps. */
149 uint32_t bg_block_num = occupied_blocks++;
150 uint32_t bbitmap_block_num = occupied_blocks++;
151 uint32_t ibitmap_block_num = occupied_blocks++;
152
153 /* We want to have only 1 block group (that starts with first data block) */
154 if (blocks_count > blocks_per_group + first_data_block) {
155 LOG_ERR("File systems with more than 1 block group are not supported.");
156 return -ENOTSUP;
157 }
158
159 uint32_t mem_for_inodes = fs_memory - occupied_blocks * cfg->block_size;
160 uint32_t inodes_count = mem_for_inodes / mem_per_inode;
161
162 /* Align indes_count to use last block of inode table entirely. */
163 if (inodes_count % inodes_per_block) {
164 inodes_count += inodes_per_block - (inodes_count % inodes_per_block);
165 }
166
167 uint32_t itable_blocks = inodes_count / inodes_per_block;
168 uint32_t used_inodes = EXT2_RESERVED_INODES;
169 uint32_t lost_found_inode = 1 + used_inodes++; /* We count inodes from 1. */
170
171 /* First unoccupied block will be the start of inode table. */
172 uint32_t itable_block_num = occupied_blocks;
173
174 occupied_blocks += itable_blocks;
175
176 /* Two next block after inode table will be the blocks for '/' and 'lost+found' dirs */
177 uint32_t root_dir_blk_num = occupied_blocks++;
178 uint32_t lost_found_dir_blk_num = occupied_blocks++;
179
180 LOG_INF("root: %d l+f: %d", root_dir_blk_num, lost_found_dir_blk_num);
181
182 /* All blocks available for writes after creating file system. */
183 uint32_t free_blocks = blocks_count - occupied_blocks;
184
185 /* Blocks that will be described in bitmaps. */
186 uint32_t used_blocks = occupied_blocks - first_data_block;
187
188 LOG_INF("[Blocks] total:%d per_grp:%d occupied:%d used:%d",
189 blocks_count, blocks_per_group, occupied_blocks, used_blocks);
190 LOG_INF("[Inodes] total:%d used:%d itable_blocks:%d",
191 inodes_count, used_inodes, itable_blocks);
192
193 struct ext2_block *sb_block = ext2_get_block(fs, first_data_block);
194 struct ext2_block *bg_block = ext2_get_block(fs, bg_block_num);
195 struct ext2_block *bbitmap_block = ext2_get_block(fs, bbitmap_block_num);
196 struct ext2_block *ibitmap_block = ext2_get_block(fs, ibitmap_block_num);
197 struct ext2_block *itable_block1, *itable_block2, *root_dir_blk, *lost_found_dir_blk;
198
199 itable_block1 = itable_block2 = root_dir_blk = lost_found_dir_blk = NULL;
200
201 if (ibitmap_block == NULL || bbitmap_block == NULL ||
202 bg_block == NULL || sb_block == NULL) {
203 ret = -ENOMEM;
204 goto out;
205 }
206
207 struct ext2_disk_superblock *sb =
208 (struct ext2_disk_superblock *)((uint8_t *)sb_block->data + sb_offset);
209
210 memset(sb, 0, 1024);
211 sb->s_inodes_count = sys_cpu_to_le32(inodes_count);
212 sb->s_blocks_count = sys_cpu_to_le32(blocks_count);
213 sb->s_r_blocks_count = sys_cpu_to_le32(0);
214 sb->s_free_blocks_count = sys_cpu_to_le32(free_blocks);
215 sb->s_free_inodes_count = sys_cpu_to_le32(inodes_count - used_inodes);
216 sb->s_first_data_block = sys_cpu_to_le32(first_data_block);
217 sb->s_log_block_size = sys_cpu_to_le32(block_log_size);
218 sb->s_log_frag_size = sys_cpu_to_le32(block_log_size);
219 sb->s_blocks_per_group = sys_cpu_to_le32(cfg->block_size * 8);
220 sb->s_frags_per_group = sys_cpu_to_le32(cfg->block_size * 8);
221 sb->s_inodes_per_group = sys_cpu_to_le32(inodes_count);
222 sb->s_mtime = sys_cpu_to_le32(0);
223 sb->s_wtime = sys_cpu_to_le32(0);
224 sb->s_mnt_count = sys_cpu_to_le32(0);
225 sb->s_max_mnt_count = sys_cpu_to_le32(-1);
226 sb->s_magic = sys_cpu_to_le32(0xEF53);
227 sb->s_state = sys_cpu_to_le32(EXT2_VALID_FS);
228 sb->s_errors = sys_cpu_to_le32(EXT2_ERRORS_RO);
229 sb->s_minor_rev_level = sys_cpu_to_le32(0);
230 sb->s_lastcheck = sys_cpu_to_le32(0);
231 sb->s_checkinterval = sys_cpu_to_le32(0);
232 sb->s_creator_os = sys_cpu_to_le32(5); /* Unknown OS */
233 sb->s_rev_level = sys_cpu_to_le32(EXT2_DYNAMIC_REV);
234 sb->s_def_resuid = sys_cpu_to_le32(0);
235 sb->s_def_resgid = sys_cpu_to_le32(0);
236 sb->s_first_ino = sys_cpu_to_le32(11);
237 sb->s_inode_size = sys_cpu_to_le32(sizeof(struct ext2_disk_inode));
238 sb->s_block_group_nr = sys_cpu_to_le32(0);
239 sb->s_feature_compat = sys_cpu_to_le32(0);
240 sb->s_feature_incompat = sys_cpu_to_le32(EXT2_FEATURE_INCOMPAT_FILETYPE);
241 sb->s_feature_ro_compat = sys_cpu_to_le32(0);
242 sb->s_algo_bitmap = sys_cpu_to_le32(0);
243 sb->s_prealloc_blocks = sys_cpu_to_le32(0);
244 sb->s_prealloc_dir_blocks = sys_cpu_to_le32(0);
245 sb->s_journal_inum = sys_cpu_to_le32(0);
246 sb->s_journal_dev = sys_cpu_to_le32(0);
247 sb->s_last_orphan = sys_cpu_to_le32(0);
248
249 memcpy(sb->s_uuid, cfg->uuid, 16);
250 strcpy(sb->s_volume_name, cfg->volume_name);
251
252 if (ext2_write_block(fs, sb_block) < 0) {
253 ret = -EIO;
254 goto out;
255 }
256
257 /* Block descriptor table */
258
259 struct ext2_disk_bgroup *bg = (struct ext2_disk_bgroup *)bg_block->data;
260
261 memset(bg, 0, cfg->block_size);
262 bg->bg_block_bitmap = sys_cpu_to_le32(bbitmap_block_num);
263 bg->bg_inode_bitmap = sys_cpu_to_le32(ibitmap_block_num);
264 bg->bg_inode_table = sys_cpu_to_le32(itable_block_num);
265 bg->bg_free_blocks_count = sys_cpu_to_le16(free_blocks);
266 bg->bg_free_inodes_count = sys_cpu_to_le16(inodes_count - used_inodes);
267 bg->bg_used_dirs_count = sys_cpu_to_le16(2); /* '/' and 'lost+found' */
268
269 if (ext2_write_block(fs, bg_block) < 0) {
270 ret = -EIO;
271 goto out;
272 }
273
274 /* Block bitmap */
275 uint8_t *bbitmap = bbitmap_block->data;
276
277 /* In bitmap we describe blocks starting from s_first_data_block. */
278 set_bitmap_padding(bbitmap, blocks_count - sb->s_first_data_block, cfg);
279 set_bitmap_bits(bbitmap, used_blocks);
280 if (ext2_write_block(fs, bbitmap_block) < 0) {
281 ret = -EIO;
282 goto out;
283 }
284
285 /* Inode bitmap */
286 uint8_t *ibitmap = ibitmap_block->data;
287
288 set_bitmap_padding(ibitmap, inodes_count, cfg);
289 set_bitmap_bits(ibitmap, used_inodes);
290 if (ext2_write_block(fs, ibitmap_block) < 0) {
291 ret = -EIO;
292 goto out;
293 }
294
295 /* Inode table */
296 /* Zero inode table */
297 for (int i = 0; i < itable_blocks; i++) {
298 struct ext2_block *blk = ext2_get_block(fs, itable_block_num + i);
299
300 memset(blk->data, 0, cfg->block_size);
301 rc = ext2_write_block(fs, blk);
302 ext2_drop_block(blk);
303 if (rc < 0) {
304 ret = -EIO;
305 goto out;
306 }
307 }
308
309 struct ext2_disk_inode *in;
310 int inode_offset;
311
312 /* Set inode 2 ('/' directory) */
313 itable_block1 = ext2_get_block(fs, itable_block_num);
314 in = (struct ext2_disk_inode *)itable_block1->data;
315 inode_offset = EXT2_ROOT_INODE - 1;
316 default_directory_inode(&in[inode_offset], 1, cfg);
317
318 in[inode_offset].i_links_count = sys_cpu_to_le16(3); /* 2 from itself, 1 from child */
319 in[inode_offset].i_block[0] = sys_cpu_to_le32(root_dir_blk_num);
320 if (ext2_write_block(fs, itable_block1) < 0) {
321 ret = -EIO;
322 goto out;
323 }
324
325 /* Set inode for 'lost+found' directory */
326 inode_offset = (lost_found_inode - 1) % inodes_per_block; /* We count inodes from 1 */
327
328 LOG_DBG("Inode offset: %d", inode_offset);
329
330 if (inodes_per_block < lost_found_inode) {
331 /* We need to fetch new inode table block */
332 uint32_t block_num = itable_block_num + lost_found_inode / inodes_per_block;
333
334 itable_block2 = ext2_get_block(fs, block_num);
335 in = (struct ext2_disk_inode *)itable_block2->data;
336 }
337
338 default_directory_inode(&in[inode_offset], 1, cfg);
339 in[inode_offset].i_links_count = sys_cpu_to_le16(2); /* 1 from itself, 1 from parent */
340 in[inode_offset].i_block[0] = sys_cpu_to_le32(lost_found_dir_blk_num);
341 if (itable_block2) {
342 if (ext2_write_block(fs, itable_block2) < 0) {
343 ret = -EIO;
344 goto out;
345 }
346 }
347
348 struct ext2_disk_direntry *disk_de;
349 struct ext2_direntry *de;
350 uint32_t de_offset;
351
352 /* Contents of '/' directory */
353 LOG_DBG("Root dir blk: %d", root_dir_blk_num);
354 root_dir_blk = ext2_get_block(fs, root_dir_blk_num);
355 if (root_dir_blk == NULL) {
356 ret = ENOMEM;
357 goto out;
358 }
359 memset(root_dir_blk->data, 0, cfg->block_size);
360
361 de_offset = 0;
362
363 disk_de = EXT2_DISK_DIRENTRY_BY_OFFSET(root_dir_blk->data, de_offset);
364 de = ext2_create_direntry(".", 1, EXT2_ROOT_INODE, EXT2_FT_DIR);
365 ext2_write_direntry(disk_de, de);
366
367 de_offset += de->de_rec_len;
368 k_heap_free(&direntry_heap, de);
369
370 disk_de = EXT2_DISK_DIRENTRY_BY_OFFSET(root_dir_blk->data, de_offset);
371 de = ext2_create_direntry("..", 2, EXT2_ROOT_INODE, EXT2_FT_DIR);
372 ext2_write_direntry(disk_de, de);
373
374 de_offset += de->de_rec_len;
375 k_heap_free(&direntry_heap, de);
376
377 char *name = "lost+found";
378
379 disk_de = EXT2_DISK_DIRENTRY_BY_OFFSET(root_dir_blk->data, de_offset);
380 de = ext2_create_direntry(name, strlen(name), lost_found_inode, EXT2_FT_DIR);
381 de_offset += de->de_rec_len;
382
383 /* This was the last entry so add padding until end of block */
384 de->de_rec_len += cfg->block_size - de_offset;
385
386 ext2_write_direntry(disk_de, de);
387 k_heap_free(&direntry_heap, de);
388
389 if (ext2_write_block(fs, root_dir_blk) < 0) {
390 ret = -EIO;
391 goto out;
392 }
393
394 /* Contents of 'lost+found' directory */
395 LOG_DBG("Lost found dir blk: %d", lost_found_dir_blk_num);
396 lost_found_dir_blk = ext2_get_block(fs, lost_found_dir_blk_num);
397 if (lost_found_dir_blk == NULL) {
398 ret = ENOMEM;
399 goto out;
400 }
401 memset(lost_found_dir_blk->data, 0, cfg->block_size);
402
403 de_offset = 0;
404
405 disk_de = EXT2_DISK_DIRENTRY_BY_OFFSET(lost_found_dir_blk->data, de_offset);
406 de = ext2_create_direntry(".", 1, lost_found_inode, EXT2_FT_DIR);
407 ext2_write_direntry(disk_de, de);
408
409 de_offset += de->de_rec_len;
410 k_heap_free(&direntry_heap, de);
411
412 disk_de = EXT2_DISK_DIRENTRY_BY_OFFSET(lost_found_dir_blk->data, de_offset);
413 de = ext2_create_direntry("..", 2, EXT2_ROOT_INODE, EXT2_FT_DIR);
414 de_offset += de->de_rec_len;
415
416 /* This was the last entry so add padding until end of block */
417 de->de_rec_len += cfg->block_size - de_offset;
418
419 ext2_write_direntry(disk_de, de);
420 k_heap_free(&direntry_heap, de);
421
422 if (ext2_write_block(fs, lost_found_dir_blk) < 0) {
423 ret = -EIO;
424 goto out;
425 }
426 out:
427 ext2_drop_block(sb_block);
428 ext2_drop_block(bg_block);
429 ext2_drop_block(bbitmap_block);
430 ext2_drop_block(ibitmap_block);
431 ext2_drop_block(itable_block1);
432 ext2_drop_block(itable_block2);
433 ext2_drop_block(root_dir_blk);
434 ext2_drop_block(lost_found_dir_blk);
435 if ((ret >= 0) && (fs->backend_ops->sync(fs)) < 0) {
436 ret = -EIO;
437 }
438 return ret;
439 }
440