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 	/* Assume whole disk may be used minus CONFIG_EXT2_DISK_STARTING_SECTOR sectors. */
121 	fs_memory -= CONFIG_EXT2_DISK_STARTING_SECTOR * fs->write_size;
122 
123 	uint32_t blocks_count = fs_memory / cfg->block_size;
124 	uint32_t blocks_per_group = cfg->block_size * 8;
125 	uint32_t inodes_per_block = cfg->block_size / sizeof(struct ext2_disk_inode);
126 	uint32_t mem_per_inode = cfg->bytes_per_inode + sizeof(struct ext2_disk_inode);
127 
128 	/* 24 block should be enough to fit minimal file system. */
129 	if (blocks_count < 24) {
130 		LOG_ERR("Storage device too small to fit ext2 file system");
131 		return -ENOSPC;
132 	}
133 
134 	uint32_t sb_offset;
135 	uint32_t first_data_block;
136 	uint32_t occupied_blocks;
137 
138 	if (cfg->block_size == 1024) {
139 		/* Superblock is stored in 1st block */
140 		sb_offset = 0;
141 		first_data_block = 1;
142 		occupied_blocks = 2;
143 	} else {
144 		/* Superblock is stored in 0th block */
145 		sb_offset = 1024;
146 		first_data_block = 0;
147 		occupied_blocks = 1;
148 	}
149 
150 	/* Reserve blocks for block group and bitmaps. */
151 	uint32_t bg_block_num = occupied_blocks++;
152 	uint32_t bbitmap_block_num = occupied_blocks++;
153 	uint32_t ibitmap_block_num = occupied_blocks++;
154 
155 	/* We want to have only 1 block group (that starts with first data block) */
156 	if (blocks_count > blocks_per_group + first_data_block) {
157 		LOG_ERR("File systems with more than 1 block group are not supported.");
158 		return -ENOTSUP;
159 	}
160 
161 	uint32_t mem_for_inodes = fs_memory - occupied_blocks * cfg->block_size;
162 	uint32_t inodes_count = mem_for_inodes / mem_per_inode;
163 
164 	/* Align indes_count to use last block of inode table entirely. */
165 	if (inodes_count % inodes_per_block) {
166 		inodes_count += inodes_per_block - (inodes_count % inodes_per_block);
167 	}
168 
169 	uint32_t itable_blocks = inodes_count / inodes_per_block;
170 	uint32_t used_inodes = EXT2_RESERVED_INODES;
171 	uint32_t lost_found_inode = 1 + used_inodes++; /* We count inodes from 1. */
172 
173 	/* First unoccupied block will be the start of inode table. */
174 	uint32_t itable_block_num = occupied_blocks;
175 
176 	occupied_blocks += itable_blocks;
177 
178 	/* Two next block after inode table will be the blocks for '/' and 'lost+found' dirs */
179 	uint32_t root_dir_blk_num = occupied_blocks++;
180 	uint32_t lost_found_dir_blk_num = occupied_blocks++;
181 
182 	LOG_INF("root: %d l+f: %d", root_dir_blk_num, lost_found_dir_blk_num);
183 
184 	/* All blocks available for writes after creating file system. */
185 	uint32_t free_blocks = blocks_count - occupied_blocks;
186 
187 	/* Blocks that will be described in bitmaps. */
188 	uint32_t used_blocks = occupied_blocks - first_data_block;
189 
190 	LOG_INF("[Blocks] total:%d per_grp:%d occupied:%d used:%d",
191 			blocks_count, blocks_per_group, occupied_blocks, used_blocks);
192 	LOG_INF("[Inodes] total:%d used:%d itable_blocks:%d",
193 			inodes_count, used_inodes, itable_blocks);
194 
195 	struct ext2_block *sb_block = ext2_get_block(fs, first_data_block);
196 	struct ext2_block *bg_block = ext2_get_block(fs, bg_block_num);
197 	struct ext2_block *bbitmap_block = ext2_get_block(fs, bbitmap_block_num);
198 	struct ext2_block *ibitmap_block = ext2_get_block(fs, ibitmap_block_num);
199 	struct ext2_block *itable_block1, *itable_block2, *root_dir_blk, *lost_found_dir_blk;
200 
201 	itable_block1 = itable_block2 = root_dir_blk = lost_found_dir_blk =  NULL;
202 
203 	if (ibitmap_block == NULL || bbitmap_block == NULL ||
204 	    bg_block == NULL || sb_block == NULL) {
205 		ret =  -ENOMEM;
206 		goto out;
207 	}
208 
209 	struct ext2_disk_superblock *sb =
210 		(struct ext2_disk_superblock *)((uint8_t *)sb_block->data + sb_offset);
211 
212 	memset(sb, 0, 1024);
213 	sb->s_inodes_count        = sys_cpu_to_le32(inodes_count);
214 	sb->s_blocks_count        = sys_cpu_to_le32(blocks_count);
215 	sb->s_r_blocks_count      = sys_cpu_to_le32(0);
216 	sb->s_free_blocks_count   = sys_cpu_to_le32(free_blocks);
217 	sb->s_free_inodes_count   = sys_cpu_to_le32(inodes_count - used_inodes);
218 	sb->s_first_data_block    = sys_cpu_to_le32(first_data_block);
219 	sb->s_log_block_size      = sys_cpu_to_le32(block_log_size);
220 	sb->s_log_frag_size       = sys_cpu_to_le32(block_log_size);
221 	sb->s_blocks_per_group    = sys_cpu_to_le32(cfg->block_size * 8);
222 	sb->s_frags_per_group     = sys_cpu_to_le32(cfg->block_size * 8);
223 	sb->s_inodes_per_group    = sys_cpu_to_le32(inodes_count);
224 	sb->s_mtime               = sys_cpu_to_le32(0);
225 	sb->s_wtime               = sys_cpu_to_le32(0);
226 	sb->s_mnt_count           = sys_cpu_to_le32(0);
227 	sb->s_max_mnt_count       = sys_cpu_to_le32(-1);
228 	sb->s_magic               = sys_cpu_to_le32(0xEF53);
229 	sb->s_state               = sys_cpu_to_le32(EXT2_VALID_FS);
230 	sb->s_errors              = sys_cpu_to_le32(EXT2_ERRORS_RO);
231 	sb->s_minor_rev_level     = sys_cpu_to_le32(0);
232 	sb->s_lastcheck           = sys_cpu_to_le32(0);
233 	sb->s_checkinterval       = sys_cpu_to_le32(0);
234 	sb->s_creator_os          = sys_cpu_to_le32(5); /* Unknown OS */
235 	sb->s_rev_level           = sys_cpu_to_le32(EXT2_DYNAMIC_REV);
236 	sb->s_def_resuid          = sys_cpu_to_le32(0);
237 	sb->s_def_resgid          = sys_cpu_to_le32(0);
238 	sb->s_first_ino           = sys_cpu_to_le32(11);
239 	sb->s_inode_size          = sys_cpu_to_le32(sizeof(struct ext2_disk_inode));
240 	sb->s_block_group_nr      = sys_cpu_to_le32(0);
241 	sb->s_feature_compat      = sys_cpu_to_le32(0);
242 	sb->s_feature_incompat    = sys_cpu_to_le32(EXT2_FEATURE_INCOMPAT_FILETYPE);
243 	sb->s_feature_ro_compat   = sys_cpu_to_le32(0);
244 	sb->s_algo_bitmap         = sys_cpu_to_le32(0);
245 	sb->s_prealloc_blocks     = sys_cpu_to_le32(0);
246 	sb->s_prealloc_dir_blocks = sys_cpu_to_le32(0);
247 	sb->s_journal_inum        = sys_cpu_to_le32(0);
248 	sb->s_journal_dev         = sys_cpu_to_le32(0);
249 	sb->s_last_orphan         = sys_cpu_to_le32(0);
250 
251 	memcpy(sb->s_uuid, cfg->uuid, 16);
252 	strcpy(sb->s_volume_name, cfg->volume_name);
253 
254 	if (ext2_write_block(fs, sb_block) < 0) {
255 		ret = -EIO;
256 		goto out;
257 	}
258 
259 	/* Block descriptor table */
260 
261 	struct ext2_disk_bgroup *bg = (struct ext2_disk_bgroup *)bg_block->data;
262 
263 	memset(bg, 0, cfg->block_size);
264 	bg->bg_block_bitmap      = sys_cpu_to_le32(bbitmap_block_num);
265 	bg->bg_inode_bitmap      = sys_cpu_to_le32(ibitmap_block_num);
266 	bg->bg_inode_table       = sys_cpu_to_le32(itable_block_num);
267 	bg->bg_free_blocks_count = sys_cpu_to_le16(free_blocks);
268 	bg->bg_free_inodes_count = sys_cpu_to_le16(inodes_count - used_inodes);
269 	bg->bg_used_dirs_count   = sys_cpu_to_le16(2); /* '/' and 'lost+found' */
270 
271 	if (ext2_write_block(fs, bg_block) < 0) {
272 		ret = -EIO;
273 		goto out;
274 	}
275 
276 	/* Block bitmap */
277 	uint8_t *bbitmap = bbitmap_block->data;
278 
279 	/* In bitmap we describe blocks starting from s_first_data_block. */
280 	set_bitmap_padding(bbitmap, blocks_count - sb->s_first_data_block, cfg);
281 	set_bitmap_bits(bbitmap, used_blocks);
282 	if (ext2_write_block(fs, bbitmap_block) < 0) {
283 		ret = -EIO;
284 		goto out;
285 	}
286 
287 	/* Inode bitmap */
288 	uint8_t *ibitmap = ibitmap_block->data;
289 
290 	set_bitmap_padding(ibitmap, inodes_count, cfg);
291 	set_bitmap_bits(ibitmap, used_inodes);
292 	if (ext2_write_block(fs, ibitmap_block) < 0) {
293 		ret = -EIO;
294 		goto out;
295 	}
296 
297 	/* Inode table */
298 	/* Zero inode table */
299 	for (int i = 0; i < itable_blocks; i++) {
300 		struct ext2_block *blk = ext2_get_block(fs, itable_block_num + i);
301 
302 		memset(blk->data, 0, cfg->block_size);
303 		rc = ext2_write_block(fs, blk);
304 		ext2_drop_block(blk);
305 		if (rc < 0) {
306 			ret = -EIO;
307 			goto out;
308 		}
309 	}
310 
311 	struct ext2_disk_inode *in;
312 	int inode_offset;
313 
314 	/* Set inode 2 ('/' directory) */
315 	itable_block1 = ext2_get_block(fs, itable_block_num);
316 	in = (struct ext2_disk_inode *)itable_block1->data;
317 	inode_offset = EXT2_ROOT_INODE - 1;
318 	default_directory_inode(&in[inode_offset], 1, cfg);
319 
320 	in[inode_offset].i_links_count = sys_cpu_to_le16(3); /* 2 from itself, 1 from child */
321 	in[inode_offset].i_block[0]    = sys_cpu_to_le32(root_dir_blk_num);
322 	if (ext2_write_block(fs, itable_block1) < 0) {
323 		ret = -EIO;
324 		goto out;
325 	}
326 
327 	/* Set inode for 'lost+found' directory */
328 	inode_offset = (lost_found_inode - 1) % inodes_per_block; /* We count inodes from 1 */
329 
330 	LOG_DBG("Inode offset: %d", inode_offset);
331 
332 	if (inodes_per_block < lost_found_inode) {
333 		/* We need to fetch new inode table block */
334 		uint32_t block_num = itable_block_num + lost_found_inode / inodes_per_block;
335 
336 		itable_block2 = ext2_get_block(fs, block_num);
337 		in = (struct ext2_disk_inode *)itable_block2->data;
338 	}
339 
340 	default_directory_inode(&in[inode_offset], 1, cfg);
341 	in[inode_offset].i_links_count = sys_cpu_to_le16(2); /* 1 from itself, 1 from parent */
342 	in[inode_offset].i_block[0]    = sys_cpu_to_le32(lost_found_dir_blk_num);
343 	if (itable_block2) {
344 		if (ext2_write_block(fs, itable_block2) < 0) {
345 			ret = -EIO;
346 			goto out;
347 		}
348 	}
349 
350 	struct ext2_disk_direntry *disk_de;
351 	struct ext2_direntry *de;
352 	uint32_t de_offset;
353 
354 	/* Contents of '/' directory */
355 	LOG_DBG("Root dir blk: %d", root_dir_blk_num);
356 	root_dir_blk = ext2_get_block(fs, root_dir_blk_num);
357 	if (root_dir_blk == NULL) {
358 		ret = ENOMEM;
359 		goto out;
360 	}
361 	memset(root_dir_blk->data, 0, cfg->block_size);
362 
363 	de_offset = 0;
364 
365 	disk_de = EXT2_DISK_DIRENTRY_BY_OFFSET(root_dir_blk->data, de_offset);
366 	de = ext2_create_direntry(".", 1, EXT2_ROOT_INODE, EXT2_FT_DIR);
367 	ext2_write_direntry(disk_de, de);
368 
369 	de_offset += de->de_rec_len;
370 	k_heap_free(&direntry_heap, de);
371 
372 	disk_de = EXT2_DISK_DIRENTRY_BY_OFFSET(root_dir_blk->data, de_offset);
373 	de = ext2_create_direntry("..", 2, EXT2_ROOT_INODE, EXT2_FT_DIR);
374 	ext2_write_direntry(disk_de, de);
375 
376 	de_offset += de->de_rec_len;
377 	k_heap_free(&direntry_heap, de);
378 
379 	char *name = "lost+found";
380 
381 	disk_de = EXT2_DISK_DIRENTRY_BY_OFFSET(root_dir_blk->data, de_offset);
382 	de = ext2_create_direntry(name, strlen(name), lost_found_inode, EXT2_FT_DIR);
383 	de_offset += de->de_rec_len;
384 
385 	/* This was the last entry so add padding until end of block */
386 	de->de_rec_len += cfg->block_size - de_offset;
387 
388 	ext2_write_direntry(disk_de, de);
389 	k_heap_free(&direntry_heap, de);
390 
391 	if (ext2_write_block(fs, root_dir_blk) < 0) {
392 		ret = -EIO;
393 		goto out;
394 	}
395 
396 	/* Contents of 'lost+found' directory */
397 	LOG_DBG("Lost found dir blk: %d", lost_found_dir_blk_num);
398 	lost_found_dir_blk = ext2_get_block(fs, lost_found_dir_blk_num);
399 	if (lost_found_dir_blk == NULL) {
400 		ret = ENOMEM;
401 		goto out;
402 	}
403 	memset(lost_found_dir_blk->data, 0, cfg->block_size);
404 
405 	de_offset = 0;
406 
407 	disk_de = EXT2_DISK_DIRENTRY_BY_OFFSET(lost_found_dir_blk->data, de_offset);
408 	de = ext2_create_direntry(".", 1, lost_found_inode, EXT2_FT_DIR);
409 	ext2_write_direntry(disk_de, de);
410 
411 	de_offset += de->de_rec_len;
412 	k_heap_free(&direntry_heap, de);
413 
414 	disk_de = EXT2_DISK_DIRENTRY_BY_OFFSET(lost_found_dir_blk->data, de_offset);
415 	de = ext2_create_direntry("..", 2, EXT2_ROOT_INODE, EXT2_FT_DIR);
416 	de_offset += de->de_rec_len;
417 
418 	/* This was the last entry so add padding until end of block */
419 	de->de_rec_len += cfg->block_size - de_offset;
420 
421 	ext2_write_direntry(disk_de, de);
422 	k_heap_free(&direntry_heap, de);
423 
424 	if (ext2_write_block(fs, lost_found_dir_blk) < 0) {
425 		ret = -EIO;
426 		goto out;
427 	}
428 out:
429 	ext2_drop_block(sb_block);
430 	ext2_drop_block(bg_block);
431 	ext2_drop_block(bbitmap_block);
432 	ext2_drop_block(ibitmap_block);
433 	ext2_drop_block(itable_block1);
434 	ext2_drop_block(itable_block2);
435 	ext2_drop_block(root_dir_blk);
436 	ext2_drop_block(lost_found_dir_blk);
437 	if ((ret >= 0) && (fs->backend_ops->sync(fs)) < 0) {
438 		ret = -EIO;
439 	}
440 	return ret;
441 }
442