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