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 */
365 static struct ext2_disk_superblock superblock;
366
ext2_mount(struct fs_mount_t * mountp)367 static int ext2_mount(struct fs_mount_t *mountp)
368 {
369 int ret = 0;
370 struct ext2_data *fs = NULL;
371 #ifdef CONFIG_FILE_SYSTEM_MKFS
372 bool do_format = false;
373 bool possible_format = (mountp->flags & FS_MOUNT_FLAG_NO_FORMAT) == 0 &&
374 (mountp->flags & FS_MOUNT_FLAG_READ_ONLY) == 0;
375 #endif
376
377 ret = ext2_init_storage(&fs, mountp->storage_dev, mountp->flags);
378 if (ret < 0) {
379 goto err;
380 }
381
382 fs->flags = 0;
383 if (mountp->flags & FS_MOUNT_FLAG_READ_ONLY) {
384 fs->flags |= EXT2_DATA_FLAGS_RO;
385 }
386
387 ret = fs->backend_ops->read_superblock(fs, &superblock);
388 if (ret < 0) {
389 goto err;
390 }
391
392 ret = ext2_verify_disk_superblock(&superblock);
393 if (ret == 0) {
394 fs->block_size = 1024 << superblock.s_log_block_size;
395
396 } else if (ret == -EROFS) {
397 fs->block_size = 1024 << superblock.s_log_block_size;
398 fs->flags |= EXT2_DATA_FLAGS_RO;
399
400 #ifdef CONFIG_FILE_SYSTEM_MKFS
401 } else if (ret == -EINVAL && possible_format) {
402 do_format = true;
403 fs->block_size = ext2_default_cfg.block_size;
404 #endif
405
406 } else {
407 goto err;
408 }
409
410 if (fs->block_size % fs->write_size != 0) {
411 LOG_ERR("Blocks size isn't multiple of sector size. (bsz: %d, ssz: %d)",
412 fs->block_size, fs->write_size);
413 ret = -ENOTSUP;
414 goto err;
415 }
416
417 ext2_init_blocks_slab(fs);
418
419 #ifdef CONFIG_FILE_SYSTEM_MKFS
420 if (do_format) {
421 LOG_INF("Formatting the storage device");
422
423 ret = ext2_format(fs, &ext2_default_cfg);
424 if (ret < 0) {
425 goto err;
426 }
427 /* We don't need to verify superblock here again. Format has succeeded hence
428 * superblock must be valid.
429 */
430 }
431 #endif
432
433 ret = ext2_init_fs(fs);
434 if (ret < 0) {
435 goto err;
436 }
437
438 mountp->fs_data = fs;
439 return 0;
440
441 err:
442 ext2_close_struct(fs);
443 return ret;
444 }
445
446 #if defined(CONFIG_FILE_SYSTEM_MKFS)
447
ext2_mkfs(uintptr_t dev_id,void * vcfg,int flags)448 static int ext2_mkfs(uintptr_t dev_id, void *vcfg, int flags)
449 {
450 int ret = 0;
451 struct ext2_data *fs;
452 struct ext2_cfg *cfg = vcfg;
453
454 if (cfg == NULL) {
455 cfg = &ext2_default_cfg;
456 }
457
458 ret = ext2_init_storage(&fs, (const void *)dev_id, flags);
459 if (ret < 0) {
460 LOG_ERR("Initialization of %ld device failed (%d)", dev_id, ret);
461 goto out;
462 }
463
464 fs->block_size = cfg->block_size;
465
466 ext2_init_blocks_slab(fs);
467
468 LOG_INF("Formatting the storage device");
469 ret = ext2_format(fs, cfg);
470 if (ret < 0) {
471 LOG_ERR("Format of %ld device failed (%d)", dev_id, ret);
472 }
473
474 out:
475 ext2_close_struct(fs);
476 return ret;
477 }
478
479 #endif /* CONFIG_FILE_SYSTEM_MKFS */
480
ext2_unmount(struct fs_mount_t * mountp)481 static int ext2_unmount(struct fs_mount_t *mountp)
482 {
483 int ret;
484 struct ext2_data *fs = mountp->fs_data;
485
486 ret = ext2_close_fs(fs);
487 if (ret < 0) {
488 return ret;
489 }
490
491 ret = ext2_close_struct(fs);
492 if (ret < 0) {
493 return ret;
494 }
495 mountp->fs_data = NULL;
496 return 0;
497 }
498
ext2_unlink(struct fs_mount_t * mountp,const char * name)499 static int ext2_unlink(struct fs_mount_t *mountp, const char *name)
500 {
501 int rc, ret = 0;
502 struct ext2_data *fs = mountp->fs_data;
503
504 const char *path = fs_impl_strip_prefix(name, mountp);
505 struct ext2_lookup_args args = {
506 .path = path,
507 .inode = NULL,
508 .parent = NULL,
509 };
510
511 args.flags = LOOKUP_ARG_UNLINK;
512
513 rc = ext2_lookup_inode(fs, &args);
514 if (rc < 0) {
515 return rc;
516 }
517
518 ret = ext2_inode_unlink(args.parent, args.inode, args.offset);
519
520 rc = ext2_inode_drop(args.parent);
521 if (rc < 0) {
522 LOG_WRN("Parent inode not dropped correctly in unlink (%d)", rc);
523 }
524 rc = ext2_inode_drop(args.inode);
525 if (rc < 0) {
526 LOG_WRN("Unlinked inode not dropped correctly in unlink (%d)", rc);
527 }
528 return ret;
529 }
530
ext2_rename(struct fs_mount_t * mountp,const char * from,const char * to)531 static int ext2_rename(struct fs_mount_t *mountp, const char *from, const char *to)
532 {
533 int rc, ret = 0;
534 struct ext2_data *fs = mountp->fs_data;
535
536 LOG_DBG("Rename: %s -> %s", from, to);
537
538 const char *path_from = fs_impl_strip_prefix(from, mountp);
539 const char *path_to = fs_impl_strip_prefix(to, mountp);
540
541 struct ext2_lookup_args args_from = {
542 .path = path_from,
543 .inode = NULL,
544 .parent = NULL,
545 .flags = LOOKUP_ARG_UNLINK,
546 };
547
548 struct ext2_lookup_args args_to = {
549 .path = path_to,
550 .inode = NULL,
551 .parent = NULL,
552 .flags = LOOKUP_ARG_CREATE,
553 };
554
555 rc = ext2_lookup_inode(fs, &args_from);
556 if (rc < 0) {
557 return rc;
558 }
559
560 rc = ext2_lookup_inode(fs, &args_to);
561 if (rc < 0) {
562 return rc;
563 }
564
565 if (args_to.inode != NULL) {
566 /* Replace existing directory entry with new one. */
567 ret = ext2_replace_file(&args_from, &args_to);
568 } else {
569 /* Moving to new location */
570 ret = ext2_move_file(&args_from, &args_to);
571 }
572
573 ext2_inode_drop(args_from.inode);
574 ext2_inode_drop(args_from.parent);
575 ext2_inode_drop(args_to.inode);
576 ext2_inode_drop(args_to.parent);
577 return ret;
578 }
579
ext2_stat(struct fs_mount_t * mountp,const char * path,struct fs_dirent * entry)580 static int ext2_stat(struct fs_mount_t *mountp, const char *path, struct fs_dirent *entry)
581 {
582 int rc;
583 struct ext2_data *fs = mountp->fs_data;
584
585 path = fs_impl_strip_prefix(path, mountp);
586
587 struct ext2_lookup_args args = {
588 .path = path,
589 .parent = NULL,
590 .flags = LOOKUP_ARG_STAT,
591 };
592
593 rc = ext2_lookup_inode(fs, &args);
594 if (rc < 0) {
595 return rc;
596 }
597
598 uint32_t offset = args.offset;
599 struct ext2_inode *parent = args.parent;
600 struct ext2_file dir = {.f_inode = parent, .f_off = offset};
601
602 rc = ext2_get_direntry(&dir, entry);
603
604 ext2_inode_drop(parent);
605 ext2_inode_drop(args.inode);
606 return rc;
607 }
608
ext2_statvfs(struct fs_mount_t * mountp,const char * path,struct fs_statvfs * stat)609 static int ext2_statvfs(struct fs_mount_t *mountp, const char *path, struct fs_statvfs *stat)
610 {
611 ARG_UNUSED(path);
612 struct ext2_data *fs = mountp->fs_data;
613
614 stat->f_bsize = fs->block_size;
615 stat->f_frsize = fs->block_size;
616 stat->f_blocks = fs->sblock.s_blocks_count;
617 stat->f_bfree = fs->sblock.s_free_blocks_count;
618
619 return 0;
620 }
621
622 /* File system interface */
623
624 static const struct fs_file_system_t ext2_fs = {
625 .open = ext2_open,
626 .close = ext2_close,
627 .read = ext2_read,
628 .write = ext2_write,
629 .lseek = ext2_lseek,
630 .tell = ext2_tell,
631 .truncate = ext2_truncate,
632 .sync = ext2_sync,
633 .mkdir = ext2_mkdir,
634 .opendir = ext2_opendir,
635 .readdir = ext2_readdir,
636 .closedir = ext2_closedir,
637 .mount = ext2_mount,
638 .unmount = ext2_unmount,
639 .unlink = ext2_unlink,
640 .rename = ext2_rename,
641 .stat = ext2_stat,
642 .statvfs = ext2_statvfs,
643 #if defined(CONFIG_FILE_SYSTEM_MKFS)
644 .mkfs = ext2_mkfs,
645 #endif
646 };
647
ext2_init(void)648 static int ext2_init(void)
649 {
650 int rc = fs_register(FS_EXT2, &ext2_fs);
651
652 if (rc < 0) {
653 LOG_WRN("Ext2 register error (%d)\n", rc);
654 } else {
655 LOG_DBG("Ext2 fs registered\n");
656 }
657
658 return rc;
659 }
660
661 SYS_INIT(ext2_init, POST_KERNEL, 99);
662