1 /*
2  * Copyright (c) 2023 Antmicro <www.antmicro.com>
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <errno.h>
8 #include <zephyr/init.h>
9 #include <zephyr/fs/fs.h>
10 #include <zephyr/fs/fs_sys.h>
11 #include <zephyr/fs/ext2.h>
12 #include <zephyr/logging/log.h>
13 
14 #include "../fs_impl.h"
15 #include "ext2.h"
16 #include "ext2_impl.h"
17 #include "ext2_struct.h"
18 
19 LOG_MODULE_DECLARE(ext2);
20 
21 K_MEM_SLAB_DEFINE(file_struct_slab, sizeof(struct ext2_file), CONFIG_MAX_FILES, sizeof(void *));
22 
23 /* File operations */
24 
ext2_open(struct fs_file_t * filp,const char * fs_path,fs_mode_t flags)25 static int ext2_open(struct fs_file_t *filp, const char *fs_path, fs_mode_t flags)
26 {
27 	int rc, ret = 0;
28 	struct ext2_file *file;
29 	struct ext2_data *fs = filp->mp->fs_data;
30 
31 	if (fs->open_files >= CONFIG_MAX_FILES) {
32 		LOG_DBG("Too many open files");
33 		return -EMFILE;
34 	}
35 
36 	LOG_DBG("Open mode: Rd:%d Wr:%d App:%d Creat:%d",
37 			(flags & FS_O_READ) != 0,
38 			(flags & FS_O_WRITE) != 0,
39 			(flags & FS_O_APPEND) != 0,
40 			(flags & FS_O_CREATE) != 0);
41 
42 	const char *path = fs_impl_strip_prefix(fs_path, filp->mp);
43 	struct ext2_lookup_args args = {
44 		.path = path,
45 		.inode = NULL,
46 		.flags = LOOKUP_ARG_OPEN,
47 	};
48 
49 	if (flags & FS_O_CREATE) {
50 		args.flags |= LOOKUP_ARG_CREATE;
51 		args.parent = NULL;
52 	}
53 
54 	rc = ext2_lookup_inode(fs, &args);
55 	if (rc < 0) {
56 		return rc;
57 	}
58 
59 	/* Inodes allocated by lookup. Must be freed in manually. */
60 	struct ext2_inode *found_inode = args.inode;
61 
62 	/* Not NULL if FS_O_CREATE and found_inode == NULL */
63 	struct ext2_inode *parent = args.parent;
64 
65 	/* File has to be created */
66 	if (flags & FS_O_CREATE && found_inode == NULL) {
67 		LOG_DBG("Returned from lookup & create: '%s':%d creating file: %d",
68 				path + args.name_pos, args.name_len, found_inode == NULL);
69 
70 		struct ext2_inode *new_inode;
71 
72 		rc = ext2_inode_get(fs, 0, &new_inode);
73 		if (rc < 0) {
74 			ret = rc;
75 			goto out;
76 		}
77 
78 		rc = ext2_create_file(parent, new_inode, &args);
79 		if (rc < 0) {
80 			ext2_inode_drop(new_inode);
81 			ret = rc;
82 			goto out;
83 		}
84 
85 		found_inode = new_inode;
86 	}
87 
88 	if ((found_inode->i_mode & EXT2_S_IFMT) != EXT2_S_IFREG) {
89 		ret = -EINVAL;
90 		goto out;
91 	}
92 
93 	rc = k_mem_slab_alloc(&file_struct_slab, (void **)&file, K_FOREVER);
94 	if (rc < 0) {
95 		ret = -ENOMEM;
96 		goto out;
97 	}
98 
99 	file->f_inode = found_inode;
100 	file->f_off = 0;
101 	file->f_flags = flags & (FS_O_RDWR | FS_O_APPEND);
102 
103 	filp->filep = file;
104 
105 	ext2_inode_drop(parent);
106 	return 0;
107 
108 out:
109 	ext2_inode_drop(found_inode);
110 	ext2_inode_drop(parent);
111 	return ret;
112 }
113 
ext2_close(struct fs_file_t * filp)114 static int ext2_close(struct fs_file_t *filp)
115 {
116 	int rc;
117 	struct ext2_file *f = filp->filep;
118 
119 	rc = ext2_inode_sync(f->f_inode);
120 	if (rc < 0) {
121 		goto out;
122 	}
123 
124 	rc = ext2_inode_drop(f->f_inode);
125 	if (rc < 0) {
126 		goto out;
127 	}
128 
129 	k_mem_slab_free(&file_struct_slab, (void *)f);
130 	filp->filep = NULL;
131 out:
132 	return rc;
133 }
134 
ext2_read(struct fs_file_t * filp,void * dest,size_t nbytes)135 static ssize_t ext2_read(struct fs_file_t *filp, void *dest, size_t nbytes)
136 {
137 	struct ext2_file *f = filp->filep;
138 
139 	if ((f->f_flags & FS_O_READ) == 0) {
140 		return -EACCES;
141 	}
142 
143 	ssize_t r = ext2_inode_read(f->f_inode, dest, f->f_off, nbytes);
144 
145 	if (r < 0) {
146 		return r;
147 	}
148 	f->f_off += r;
149 	return r;
150 }
151 
ext2_write(struct fs_file_t * filp,const void * src,size_t nbytes)152 static ssize_t ext2_write(struct fs_file_t *filp, const void *src, size_t nbytes)
153 {
154 	struct ext2_file *f = filp->filep;
155 
156 	if ((f->f_flags & FS_O_WRITE) == 0) {
157 		return -EACCES;
158 	}
159 
160 	if (f->f_flags & FS_O_APPEND) {
161 		f->f_off = f->f_inode->i_size;
162 	}
163 
164 	ssize_t r = ext2_inode_write(f->f_inode, src, f->f_off, nbytes);
165 
166 	if (r < 0) {
167 		return r;
168 	}
169 
170 	f->f_off += r;
171 	return r;
172 }
173 
ext2_lseek(struct fs_file_t * filp,off_t off,int whence)174 static int ext2_lseek(struct fs_file_t *filp, off_t off, int whence)
175 {
176 	struct ext2_file *f = filp->filep;
177 	off_t new_off = 0;
178 
179 	switch (whence) {
180 	case FS_SEEK_SET:
181 		new_off = off;
182 		break;
183 
184 	case FS_SEEK_CUR:
185 		new_off = f->f_off + off;
186 		break;
187 
188 	case FS_SEEK_END:
189 		new_off = f->f_inode->i_size + off;
190 		break;
191 
192 	default:
193 		return -EINVAL;
194 	}
195 
196 	/* New offset not inside the file. */
197 	if (new_off < 0 || new_off > f->f_inode->i_size) {
198 		return -EINVAL;
199 	}
200 	f->f_off = new_off;
201 	return 0;
202 }
203 
ext2_tell(struct fs_file_t * filp)204 static off_t ext2_tell(struct fs_file_t *filp)
205 {
206 	struct ext2_file *f = filp->filep;
207 
208 	return f->f_off;
209 }
210 
ext2_truncate(struct fs_file_t * filp,off_t length)211 static int ext2_truncate(struct fs_file_t *filp, off_t length)
212 {
213 	struct ext2_file *f = filp->filep;
214 
215 	if ((f->f_flags & FS_O_WRITE) == 0) {
216 		return -EACCES;
217 	}
218 
219 	int rc = ext2_inode_trunc(f->f_inode, length);
220 
221 	if (rc < 0) {
222 		return rc;
223 	}
224 	return 0;
225 }
226 
ext2_sync(struct fs_file_t * filp)227 static int ext2_sync(struct fs_file_t *filp)
228 {
229 	struct ext2_file *f = filp->filep;
230 
231 	int rc = ext2_inode_sync(f->f_inode);
232 
233 	if (rc >= 0) {
234 		return 0;
235 	}
236 	return rc;
237 }
238 
239 /* Directory operations */
240 
ext2_mkdir(struct fs_mount_t * mountp,const char * name)241 static int ext2_mkdir(struct fs_mount_t *mountp, const char *name)
242 {
243 	int rc, ret = 0;
244 	struct ext2_data *fs = mountp->fs_data;
245 
246 	const char *path = fs_impl_strip_prefix(name, mountp);
247 	struct ext2_lookup_args args = {
248 		.path = path,
249 		.inode = NULL,
250 		.parent = NULL,
251 	};
252 
253 	args.flags = LOOKUP_ARG_CREATE;
254 
255 	rc = ext2_lookup_inode(fs, &args);
256 	if (rc < 0) {
257 		return rc;
258 	}
259 
260 	struct ext2_inode *found_inode = args.inode;
261 	struct ext2_inode *parent = args.parent;
262 
263 	LOG_DBG("Returned from lookup & create: '%s':%d res: %d",
264 			path + args.name_pos, args.name_len, found_inode == NULL);
265 
266 	if (found_inode != NULL) {
267 		ret = -EEXIST;
268 		goto out;
269 	}
270 
271 	rc = ext2_inode_get(fs, 0, &found_inode);
272 	if (rc < 0) {
273 		ret = rc;
274 		goto out;
275 	}
276 
277 	rc = ext2_create_dir(parent, found_inode, &args);
278 	if (rc < 0) {
279 		ret = rc;
280 	}
281 
282 out:
283 	ext2_inode_drop(parent);
284 	ext2_inode_drop(found_inode);
285 	return ret;
286 }
287 
ext2_opendir(struct fs_dir_t * dirp,const char * fs_path)288 static int ext2_opendir(struct fs_dir_t *dirp, const char *fs_path)
289 {
290 	int rc, ret = 0;
291 	struct ext2_file *dir;
292 	const char *path = fs_impl_strip_prefix(fs_path, dirp->mp);
293 	struct ext2_data *fs = dirp->mp->fs_data;
294 	struct ext2_lookup_args args = {
295 		.path = path,
296 		.inode = NULL,
297 		.flags = LOOKUP_ARG_OPEN,
298 	};
299 
300 	rc = ext2_lookup_inode(fs, &args);
301 	if (rc < 0) {
302 		return rc;
303 	}
304 
305 	struct ext2_inode *found_inode = args.inode;
306 
307 	if (!(found_inode->i_mode & EXT2_S_IFDIR)) {
308 		ret = -ENOTDIR;
309 		goto out;
310 	}
311 
312 	rc = k_mem_slab_alloc(&file_struct_slab, (void **)&dir, K_FOREVER);
313 	if (rc < 0) {
314 		ret = -ENOMEM;
315 		goto out;
316 	}
317 
318 
319 	if (!dir) {
320 		ret = -ENOMEM;
321 		goto out;
322 	}
323 
324 	dir->f_inode = found_inode;
325 	dir->f_off = 0;
326 
327 	dirp->dirp = dir;
328 	return 0;
329 
330 out:
331 	ext2_inode_drop(found_inode);
332 	return ret;
333 }
334 
ext2_readdir(struct fs_dir_t * dirp,struct fs_dirent * entry)335 static int ext2_readdir(struct fs_dir_t *dirp, struct fs_dirent *entry)
336 {
337 	struct ext2_file *dir = dirp->dirp;
338 	int rc = ext2_get_direntry(dir, entry);
339 
340 	if (rc < 0) {
341 		return rc;
342 	}
343 
344 	return 0;
345 }
346 
ext2_closedir(struct fs_dir_t * dirp)347 static int ext2_closedir(struct fs_dir_t *dirp)
348 {
349 	struct ext2_file *dir = dirp->dirp;
350 
351 	ext2_inode_drop(dir->f_inode);
352 	k_mem_slab_free(&file_struct_slab, (void *)dir);
353 	return 0;
354 }
355 
356 /* File system level operations */
357 
358 #ifdef CONFIG_FILE_SYSTEM_MKFS
359 FS_EXT2_DECLARE_DEFAULT_CONFIG(ext2_default_cfg);
360 #endif
361 
362 /* Superblock is used only once. Because ext2 may have only one instance at the time we could
363  * statically allocate this strusture.
364  * Alignment needed for reads to work with DMAs that force it (e.g. stm32 SDMMC + DMA).
365  */
366 static struct ext2_disk_superblock __aligned(CONFIG_EXT2_SUPERBLOCK_ALIGNMENT) superblock;
367 
ext2_mount(struct fs_mount_t * mountp)368 static int ext2_mount(struct fs_mount_t *mountp)
369 {
370 	int ret = 0;
371 	struct ext2_data *fs = NULL;
372 #ifdef CONFIG_FILE_SYSTEM_MKFS
373 	bool do_format = false;
374 	bool possible_format = (mountp->flags & FS_MOUNT_FLAG_NO_FORMAT) == 0 &&
375 				(mountp->flags & FS_MOUNT_FLAG_READ_ONLY) == 0;
376 #endif
377 
378 	ret = ext2_init_storage(&fs, mountp->storage_dev, mountp->flags);
379 	if (ret < 0) {
380 		goto err;
381 	}
382 
383 	fs->flags = 0;
384 	if (mountp->flags & FS_MOUNT_FLAG_READ_ONLY) {
385 		fs->flags |= EXT2_DATA_FLAGS_RO;
386 	}
387 
388 	ret = fs->backend_ops->read_superblock(fs, &superblock);
389 	if (ret < 0) {
390 		goto err;
391 	}
392 
393 	ret = ext2_verify_disk_superblock(&superblock);
394 	if (ret == 0) {
395 		fs->block_size = 1024 << superblock.s_log_block_size;
396 
397 	} else if (ret == -EROFS) {
398 		fs->block_size = 1024 << superblock.s_log_block_size;
399 		fs->flags |= EXT2_DATA_FLAGS_RO;
400 
401 #ifdef CONFIG_FILE_SYSTEM_MKFS
402 	} else if (ret == -EINVAL && possible_format) {
403 		do_format = true;
404 		fs->block_size = ext2_default_cfg.block_size;
405 #endif
406 
407 	} else {
408 		goto err;
409 	}
410 
411 	if (fs->block_size % fs->write_size != 0) {
412 		LOG_ERR("Blocks size isn't multiple of sector size. (bsz: %d, ssz: %d)",
413 				fs->block_size, fs->write_size);
414 		ret = -ENOTSUP;
415 		goto err;
416 	}
417 
418 	ext2_init_blocks_slab(fs);
419 
420 #ifdef CONFIG_FILE_SYSTEM_MKFS
421 	if (do_format) {
422 		LOG_INF("Formatting the storage device");
423 
424 		ret = ext2_format(fs, &ext2_default_cfg);
425 		if (ret < 0) {
426 			goto err;
427 		}
428 		/* We don't need to verify superblock here again. Format has succeeded hence
429 		 * superblock must be valid.
430 		 */
431 	}
432 #endif
433 
434 	ret = ext2_init_fs(fs);
435 	if (ret < 0) {
436 		goto err;
437 	}
438 
439 	mountp->fs_data = fs;
440 	return 0;
441 
442 err:
443 	ext2_close_struct(fs);
444 	return ret;
445 }
446 
447 #if defined(CONFIG_FILE_SYSTEM_MKFS)
448 
ext2_mkfs(uintptr_t dev_id,void * vcfg,int flags)449 static int ext2_mkfs(uintptr_t dev_id, void *vcfg, int flags)
450 {
451 	int ret = 0;
452 	struct ext2_data *fs;
453 	struct ext2_cfg *cfg = vcfg;
454 
455 	if (cfg == NULL) {
456 		cfg = &ext2_default_cfg;
457 	}
458 
459 	ret = ext2_init_storage(&fs, (const void *)dev_id, flags);
460 	if (ret < 0) {
461 		LOG_ERR("Initialization of %ld device failed (%d)", dev_id, ret);
462 		goto out;
463 	}
464 
465 	fs->block_size = cfg->block_size;
466 
467 	ext2_init_blocks_slab(fs);
468 
469 	LOG_INF("Formatting the storage device");
470 	ret = ext2_format(fs, cfg);
471 	if (ret < 0) {
472 		LOG_ERR("Format of %ld device failed (%d)", dev_id, ret);
473 	}
474 
475 out:
476 	ext2_close_struct(fs);
477 	return ret;
478 }
479 
480 #endif /* CONFIG_FILE_SYSTEM_MKFS */
481 
ext2_unmount(struct fs_mount_t * mountp)482 static int ext2_unmount(struct fs_mount_t *mountp)
483 {
484 	int ret;
485 	struct ext2_data *fs = mountp->fs_data;
486 
487 	ret = ext2_close_fs(fs);
488 	if (ret < 0) {
489 		return ret;
490 	}
491 
492 	ret = ext2_close_struct(fs);
493 	if (ret < 0) {
494 		return ret;
495 	}
496 	mountp->fs_data = NULL;
497 	return 0;
498 }
499 
ext2_unlink(struct fs_mount_t * mountp,const char * name)500 static int ext2_unlink(struct fs_mount_t *mountp, const char *name)
501 {
502 	int rc, ret = 0;
503 	struct ext2_data *fs = mountp->fs_data;
504 
505 	const char *path = fs_impl_strip_prefix(name, mountp);
506 	struct ext2_lookup_args args = {
507 		.path = path,
508 		.inode = NULL,
509 		.parent = NULL,
510 	};
511 
512 	args.flags = LOOKUP_ARG_UNLINK;
513 
514 	rc = ext2_lookup_inode(fs, &args);
515 	if (rc < 0) {
516 		return rc;
517 	}
518 
519 	ret = ext2_inode_unlink(args.parent, args.inode, args.offset);
520 
521 	rc = ext2_inode_drop(args.parent);
522 	if (rc < 0) {
523 		LOG_WRN("Parent inode not dropped correctly in unlink (%d)", rc);
524 	}
525 	rc = ext2_inode_drop(args.inode);
526 	if (rc < 0) {
527 		LOG_WRN("Unlinked inode not dropped correctly in unlink (%d)", rc);
528 	}
529 	return ret;
530 }
531 
ext2_rename(struct fs_mount_t * mountp,const char * from,const char * to)532 static int ext2_rename(struct fs_mount_t *mountp, const char *from, const char *to)
533 {
534 	int rc, ret = 0;
535 	struct ext2_data *fs = mountp->fs_data;
536 
537 	LOG_DBG("Rename: %s -> %s", from, to);
538 
539 	const char *path_from = fs_impl_strip_prefix(from, mountp);
540 	const char *path_to = fs_impl_strip_prefix(to, mountp);
541 
542 	struct ext2_lookup_args args_from = {
543 		.path = path_from,
544 		.inode = NULL,
545 		.parent = NULL,
546 		.flags = LOOKUP_ARG_UNLINK,
547 	};
548 
549 	struct ext2_lookup_args args_to = {
550 		.path = path_to,
551 		.inode = NULL,
552 		.parent = NULL,
553 		.flags = LOOKUP_ARG_CREATE,
554 	};
555 
556 	rc = ext2_lookup_inode(fs, &args_from);
557 	if (rc < 0) {
558 		return rc;
559 	}
560 
561 	rc = ext2_lookup_inode(fs, &args_to);
562 	if (rc < 0) {
563 		return rc;
564 	}
565 
566 	if (args_to.inode != NULL) {
567 		/* Replace existing directory entry with new one. */
568 		ret = ext2_replace_file(&args_from, &args_to);
569 	} else {
570 		/* Moving to new location */
571 		ret = ext2_move_file(&args_from, &args_to);
572 	}
573 
574 	ext2_inode_drop(args_from.inode);
575 	ext2_inode_drop(args_from.parent);
576 	ext2_inode_drop(args_to.inode);
577 	ext2_inode_drop(args_to.parent);
578 	return ret;
579 }
580 
ext2_stat(struct fs_mount_t * mountp,const char * path,struct fs_dirent * entry)581 static int ext2_stat(struct fs_mount_t *mountp, const char *path, struct fs_dirent *entry)
582 {
583 	int rc;
584 	struct ext2_data *fs = mountp->fs_data;
585 
586 	path = fs_impl_strip_prefix(path, mountp);
587 
588 	struct ext2_lookup_args args = {
589 		.path = path,
590 		.parent = NULL,
591 		.flags = LOOKUP_ARG_STAT,
592 	};
593 
594 	rc = ext2_lookup_inode(fs, &args);
595 	if (rc < 0) {
596 		return rc;
597 	}
598 
599 	uint32_t offset = args.offset;
600 	struct ext2_inode *parent = args.parent;
601 	struct ext2_file dir = {.f_inode = parent, .f_off = offset};
602 
603 	rc = ext2_get_direntry(&dir, entry);
604 
605 	ext2_inode_drop(parent);
606 	ext2_inode_drop(args.inode);
607 	return rc;
608 }
609 
ext2_statvfs(struct fs_mount_t * mountp,const char * path,struct fs_statvfs * stat)610 static int ext2_statvfs(struct fs_mount_t *mountp, const char *path, struct fs_statvfs *stat)
611 {
612 	ARG_UNUSED(path);
613 	struct ext2_data *fs = mountp->fs_data;
614 
615 	stat->f_bsize = fs->block_size;
616 	stat->f_frsize = fs->block_size;
617 	stat->f_blocks = fs->sblock.s_blocks_count;
618 	stat->f_bfree = fs->sblock.s_free_blocks_count;
619 
620 	return 0;
621 }
622 
623 /* File system interface */
624 
625 static const struct fs_file_system_t ext2_fs = {
626 	.open = ext2_open,
627 	.close = ext2_close,
628 	.read = ext2_read,
629 	.write = ext2_write,
630 	.lseek = ext2_lseek,
631 	.tell = ext2_tell,
632 	.truncate = ext2_truncate,
633 	.sync = ext2_sync,
634 	.mkdir = ext2_mkdir,
635 	.opendir = ext2_opendir,
636 	.readdir = ext2_readdir,
637 	.closedir = ext2_closedir,
638 	.mount = ext2_mount,
639 	.unmount = ext2_unmount,
640 	.unlink = ext2_unlink,
641 	.rename = ext2_rename,
642 	.stat = ext2_stat,
643 	.statvfs = ext2_statvfs,
644 #if defined(CONFIG_FILE_SYSTEM_MKFS)
645 	.mkfs = ext2_mkfs,
646 #endif
647 };
648 
ext2_init(void)649 static int ext2_init(void)
650 {
651 	int rc = fs_register(FS_EXT2, &ext2_fs);
652 
653 	if (rc < 0) {
654 		LOG_WRN("Ext2 register error (%d)\n", rc);
655 	} else {
656 		LOG_DBG("Ext2 fs registered\n");
657 	}
658 
659 	return rc;
660 }
661 
662 SYS_INIT(ext2_init, POST_KERNEL, 99);
663