1 /*
2  * Copyright (c) 2023 Antmicro <www.antmicro.com>
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/kernel.h>
8 #include <zephyr/init.h>
9 #include <zephyr/fs/fs.h>
10 #include <zephyr/logging/log.h>
11 #include <zephyr/sys/util.h>
12 #include <zephyr/sys/byteorder.h>
13 
14 #include "ext2.h"
15 #include "ext2_impl.h"
16 #include "ext2_struct.h"
17 #include "ext2_diskops.h"
18 #include "ext2_bitmap.h"
19 
20 LOG_MODULE_REGISTER(ext2, CONFIG_EXT2_LOG_LEVEL);
21 
22 static struct ext2_data __fs;
23 static bool initialized;
24 
25 #define BLOCK_MEMORY_BUFFER_SIZE (CONFIG_EXT2_MAX_BLOCK_COUNT * CONFIG_EXT2_MAX_BLOCK_SIZE)
26 #define BLOCK_STRUCT_BUFFER_SIZE (CONFIG_EXT2_MAX_BLOCK_COUNT * sizeof(struct ext2_block))
27 
28 /* Structures for blocks slab alocator */
29 struct k_mem_slab ext2_block_memory_slab, ext2_block_struct_slab;
30 char __aligned(sizeof(void *)) __ext2_block_memory_buffer[BLOCK_MEMORY_BUFFER_SIZE];
31 char __aligned(sizeof(void *)) __ext2_block_struct_buffer[BLOCK_STRUCT_BUFFER_SIZE];
32 
33 /* Initialize heap memory allocator */
34 K_HEAP_DEFINE(direntry_heap, MAX_DIRENTRY_SIZE);
35 K_MEM_SLAB_DEFINE(inode_struct_slab, sizeof(struct ext2_inode), MAX_INODES, sizeof(void *));
36 
37 /* Helper functions --------------------------------------------------------- */
38 
error_behavior(struct ext2_data * fs,const char * msg)39 void error_behavior(struct ext2_data *fs, const char *msg)
40 {
41 	LOG_ERR("File system corrupted: %s", msg);
42 
43 	/* If file system is not initialized panic */
44 	if (!initialized) {
45 		LOG_ERR("File system data not found. Panic...");
46 		k_panic();
47 	}
48 
49 	switch (fs->sblock.s_errors) {
50 	case EXT2_ERRORS_CONTINUE:
51 		/* Do nothing */
52 		break;
53 	case EXT2_ERRORS_RO:
54 		LOG_WRN("Marking file system as read only");
55 		fs->flags |= EXT2_DATA_FLAGS_RO;
56 		break;
57 	case EXT2_ERRORS_PANIC:
58 		LOG_ERR("Panic...");
59 		k_panic();
60 		break;
61 	default:
62 		LOG_ERR("Unrecognized errors behavior in superblock s_errors field. Panic...");
63 		k_panic();
64 	}
65 }
66 
67 /* Block operations --------------------------------------------------------- */
68 
get_block_struct(void)69 static struct ext2_block *get_block_struct(void)
70 {
71 	int ret;
72 	struct ext2_block *b;
73 
74 	ret = k_mem_slab_alloc(&ext2_block_struct_slab, (void **)&b, K_NO_WAIT);
75 	if (ret < 0) {
76 		LOG_ERR("get block: alloc block struct error %d", ret);
77 		return NULL;
78 	}
79 
80 	ret = k_mem_slab_alloc(&ext2_block_memory_slab, (void **)&b->data, K_NO_WAIT);
81 	if (ret < 0) {
82 		LOG_ERR("get block: alloc block memory error %d", ret);
83 		k_mem_slab_free(&ext2_block_struct_slab, (void *)b);
84 		return NULL;
85 	}
86 	return b;
87 }
88 
ext2_get_block(struct ext2_data * fs,uint32_t block)89 struct ext2_block *ext2_get_block(struct ext2_data *fs, uint32_t block)
90 {
91 	int ret;
92 	struct ext2_block *b = get_block_struct();
93 
94 	if (!b) {
95 		return NULL;
96 	}
97 	b->num = block;
98 	b->flags = EXT2_BLOCK_ASSIGNED;
99 	ret = fs->backend_ops->read_block(fs, b->data, block);
100 	if (ret < 0) {
101 		LOG_ERR("get block: read block error %d", ret);
102 		ext2_drop_block(b);
103 		return NULL;
104 	}
105 	return b;
106 }
107 
ext2_get_empty_block(struct ext2_data * fs)108 struct ext2_block *ext2_get_empty_block(struct ext2_data *fs)
109 {
110 	struct ext2_block *b = get_block_struct();
111 
112 	if (!b) {
113 		return NULL;
114 	}
115 	b->num = 0;
116 	b->flags = 0;
117 	memset(b->data, 0, fs->block_size);
118 	return b;
119 }
120 
ext2_write_block(struct ext2_data * fs,struct ext2_block * b)121 int ext2_write_block(struct ext2_data *fs, struct ext2_block *b)
122 {
123 	int ret;
124 
125 	if (!(b->flags & EXT2_BLOCK_ASSIGNED)) {
126 		return -EINVAL;
127 	}
128 
129 	ret = fs->backend_ops->write_block(fs, b->data, b->num);
130 	if (ret < 0) {
131 		return ret;
132 	}
133 	return 0;
134 }
135 
ext2_drop_block(struct ext2_block * b)136 void ext2_drop_block(struct ext2_block *b)
137 {
138 	if (b == NULL) {
139 		return;
140 	}
141 
142 	if (b != NULL && b->data != NULL) {
143 		k_mem_slab_free(&ext2_block_memory_slab, (void *)b->data);
144 		k_mem_slab_free(&ext2_block_struct_slab, (void *)b);
145 	}
146 }
147 
ext2_init_blocks_slab(struct ext2_data * fs)148 void ext2_init_blocks_slab(struct ext2_data *fs)
149 {
150 	memset(__ext2_block_memory_buffer, 0, BLOCK_MEMORY_BUFFER_SIZE);
151 	memset(__ext2_block_struct_buffer, 0, BLOCK_STRUCT_BUFFER_SIZE);
152 
153 	/* These calls will always succeed because sizes and memory buffers are properly aligned. */
154 
155 	k_mem_slab_init(&ext2_block_struct_slab, __ext2_block_struct_buffer,
156 			sizeof(struct ext2_block), CONFIG_EXT2_MAX_BLOCK_COUNT);
157 
158 	k_mem_slab_init(&ext2_block_memory_slab, __ext2_block_memory_buffer, fs->block_size,
159 			CONFIG_EXT2_MAX_BLOCK_COUNT);
160 }
161 
ext2_assign_block_num(struct ext2_data * fs,struct ext2_block * b)162 int ext2_assign_block_num(struct ext2_data *fs, struct ext2_block *b)
163 {
164 	int64_t new_block;
165 
166 	if (b->flags & EXT2_BLOCK_ASSIGNED) {
167 		return -EINVAL;
168 	}
169 
170 	/* Allocate block in the file system. */
171 	new_block = ext2_alloc_block(fs);
172 	if (new_block < 0) {
173 		return new_block;
174 	}
175 
176 	b->num = new_block;
177 	b->flags |= EXT2_BLOCK_ASSIGNED;
178 	return 0;
179 }
180 
181 
182 /* FS operations ------------------------------------------------------------ */
183 
ext2_init_storage(struct ext2_data ** fsp,const void * storage_dev,int flags)184 int ext2_init_storage(struct ext2_data **fsp, const void *storage_dev, int flags)
185 {
186 	if (initialized) {
187 		return -EBUSY;
188 	}
189 
190 	int ret = 0;
191 	struct ext2_data *fs = &__fs;
192 	int64_t dev_size, write_size;
193 
194 	*fsp = fs;
195 	fs->open_inodes = 0;
196 	fs->flags = 0;
197 	fs->bgroup.num = -1;
198 
199 	ret = ext2_init_disk_access_backend(fs, storage_dev, flags);
200 	if (ret < 0) {
201 		return ret;
202 	}
203 
204 	dev_size = fs->backend_ops->get_device_size(fs);
205 	if (dev_size < 0) {
206 		ret = dev_size;
207 		goto err;
208 	}
209 
210 	write_size = fs->backend_ops->get_write_size(fs);
211 	if (write_size < 0) {
212 		ret = write_size;
213 		goto err;
214 	}
215 
216 	if (write_size < 1024 && 1024 % write_size != 0) {
217 		ret = -EINVAL;
218 		LOG_ERR("expecting sector size that divides 1024 (got: %lld)", write_size);
219 		goto err;
220 	}
221 
222 	LOG_DBG("Device size: %lld", dev_size);
223 	LOG_DBG("Write size: %lld", write_size);
224 
225 	fs->device_size = dev_size;
226 	fs->write_size = write_size;
227 
228 	initialized = true;
229 err:
230 	return ret;
231 }
232 
ext2_verify_disk_superblock(struct ext2_disk_superblock * sb)233 int ext2_verify_disk_superblock(struct ext2_disk_superblock *sb)
234 {
235 	/* Check if it is a valid Ext2 file system. */
236 	if (sys_le16_to_cpu(sb->s_magic) != EXT2_MAGIC_NUMBER) {
237 		LOG_ERR("Wrong file system magic number (%x)", sb->s_magic);
238 		return -EINVAL;
239 	}
240 
241 	/* For now we don't support file systems with frag size different from block size */
242 	if (sys_le32_to_cpu(sb->s_log_block_size) != sb->s_log_frag_size) {
243 		LOG_ERR("Filesystem with frag_size != block_size is not supported");
244 		return -ENOTSUP;
245 	}
246 
247 	/* Support only second revision */
248 	if (sys_le32_to_cpu(sb->s_rev_level) != EXT2_DYNAMIC_REV) {
249 		LOG_ERR("Filesystem with revision %d is not supported", sb->s_rev_level);
250 		return -ENOTSUP;
251 	}
252 
253 	if (sys_le16_to_cpu(sb->s_inode_size) != EXT2_GOOD_OLD_INODE_SIZE) {
254 		LOG_ERR("Filesystem with inode size %d is not supported", sb->s_inode_size);
255 		return -ENOTSUP;
256 	}
257 
258 	/* Check if file system may contain errors. */
259 	if (sys_le16_to_cpu(sb->s_state) == EXT2_ERROR_FS) {
260 		LOG_WRN("File system may contain errors.");
261 		switch (sys_le16_to_cpu(sb->s_errors)) {
262 		case EXT2_ERRORS_CONTINUE:
263 			break;
264 
265 		case EXT2_ERRORS_RO:
266 			LOG_WRN("File system can be mounted read only");
267 			return -EROFS;
268 
269 		case EXT2_ERRORS_PANIC:
270 			LOG_ERR("File system can't be mounted. Panic...");
271 			k_panic();
272 		default:
273 			LOG_WRN("Unknown option for superblock s_errors field.");
274 		}
275 	}
276 
277 	if ((sys_le32_to_cpu(sb->s_feature_incompat) & EXT2_FEATURE_INCOMPAT_FILETYPE) == 0) {
278 		LOG_ERR("File system without file type stored in de is not supported");
279 		return -ENOTSUP;
280 	}
281 
282 	if ((sys_le32_to_cpu(sb->s_feature_incompat) & ~EXT2_FEATURE_INCOMPAT_SUPPORTED) > 0) {
283 		LOG_ERR("File system can't be mounted. Incompat features %d not supported",
284 				(sb->s_feature_incompat & ~EXT2_FEATURE_INCOMPAT_SUPPORTED));
285 		return -ENOTSUP;
286 	}
287 
288 	if ((sys_le32_to_cpu(sb->s_feature_ro_compat) & ~EXT2_FEATURE_RO_COMPAT_SUPPORTED) > 0) {
289 		LOG_WRN("File system can be mounted read only. RO features %d detected.",
290 				(sb->s_feature_ro_compat & ~EXT2_FEATURE_RO_COMPAT_SUPPORTED));
291 		return -EROFS;
292 	}
293 
294 	LOG_DBG("ino_cnt:%d blk_cnt:%d blk_per_grp:%d ino_per_grp:%d free_ino:%d free_blk:%d "
295 			"blk_size:%d ino_size:%d mntc:%d",
296 			sys_le32_to_cpu(sb->s_inodes_count),
297 			sys_le32_to_cpu(sb->s_blocks_count),
298 			sys_le32_to_cpu(sb->s_blocks_per_group),
299 			sys_le32_to_cpu(sb->s_inodes_per_group),
300 			sys_le32_to_cpu(sb->s_free_inodes_count),
301 			sys_le32_to_cpu(sb->s_free_blocks_count),
302 			sys_le32_to_cpu(1024 << sb->s_log_block_size),
303 			sys_le16_to_cpu(sb->s_inode_size),
304 			sys_le16_to_cpu(sb->s_mnt_count));
305 	return 0;
306 }
307 
ext2_init_fs(struct ext2_data * fs)308 int ext2_init_fs(struct ext2_data *fs)
309 {
310 	int ret = 0;
311 
312 	/* Fetch superblock */
313 	ret = ext2_fetch_superblock(fs);
314 	if (ret < 0) {
315 		return ret;
316 	}
317 
318 	if (!(fs->flags & EXT2_DATA_FLAGS_RO)) {
319 		/* Update sblock fields set during the successful mount. */
320 		fs->sblock.s_state = EXT2_ERROR_FS;
321 		fs->sblock.s_mnt_count += 1;
322 		ret = ext2_commit_superblock(fs);
323 		if (ret < 0) {
324 			return ret;
325 		}
326 	}
327 
328 	ret = ext2_fetch_block_group(fs, 0);
329 	if (ret < 0) {
330 		return ret;
331 	}
332 	ret = ext2_fetch_bg_ibitmap(&fs->bgroup);
333 	if (ret < 0) {
334 		return ret;
335 	}
336 	ret = ext2_fetch_bg_bbitmap(&fs->bgroup);
337 	if (ret < 0) {
338 		return ret;
339 	}
340 
341 	/* Validate superblock */
342 	uint32_t set;
343 	struct ext2_superblock *sb = &fs->sblock;
344 	uint32_t fs_blocks = sb->s_blocks_count - sb->s_first_data_block;
345 
346 	set = ext2_bitmap_count_set(BGROUP_BLOCK_BITMAP(&fs->bgroup), fs_blocks);
347 
348 	if (set != sb->s_blocks_count - sb->s_free_blocks_count - sb->s_first_data_block) {
349 		error_behavior(fs, "Wrong number of used blocks in superblock and bitmap");
350 		return -EINVAL;
351 	}
352 
353 	set = ext2_bitmap_count_set(BGROUP_INODE_BITMAP(&fs->bgroup), sb->s_inodes_count);
354 
355 	if (set != sb->s_inodes_count - sb->s_free_inodes_count) {
356 		error_behavior(fs, "Wrong number of used inodes in superblock and bitmap");
357 		return -EINVAL;
358 	}
359 	return 0;
360 }
361 
ext2_close_fs(struct ext2_data * fs)362 int ext2_close_fs(struct ext2_data *fs)
363 {
364 	int ret = 0;
365 
366 	/* Close all open inodes */
367 	for (int32_t i = 0; i < fs->open_inodes; ++i) {
368 		if (fs->inode_pool[i] != NULL) {
369 			ext2_inode_drop(fs->inode_pool[i]);
370 		}
371 	}
372 
373 	/* To save file system as correct it must be writable and without errors */
374 	if (!(fs->flags & (EXT2_DATA_FLAGS_RO | EXT2_DATA_FLAGS_ERR))) {
375 		fs->sblock.s_state = EXT2_VALID_FS;
376 		ret = ext2_commit_superblock(fs);
377 		if (ret < 0) {
378 			return ret;
379 		}
380 	}
381 
382 	/* free block group if it is fetched */
383 	ext2_drop_block(fs->bgroup.inode_table);
384 	ext2_drop_block(fs->bgroup.inode_bitmap);
385 	ext2_drop_block(fs->bgroup.block_bitmap);
386 
387 	if (fs->backend_ops->sync(fs) < 0) {
388 		return -EIO;
389 	}
390 	return 0;
391 }
392 
ext2_close_struct(struct ext2_data * fs)393 int ext2_close_struct(struct ext2_data *fs)
394 {
395 	memset(fs, 0, sizeof(struct ext2_data));
396 	initialized = false;
397 	return 0;
398 }
399 
400 /* Lookup ------------------------------------------------------------------- */
401 
402 /* Functions needed by lookup inode */
403 static const char *skip_slash(const char *str);
404 static char *strchrnul(const char *str, const char c);
405 static int64_t find_dir_entry(struct ext2_inode *inode, const char *name, size_t len,
406 		uint32_t *r_offset);
407 
ext2_lookup_inode(struct ext2_data * fs,struct ext2_lookup_args * args)408 int ext2_lookup_inode(struct ext2_data *fs, struct ext2_lookup_args *args)
409 {
410 	LOG_DBG("Looking for file %s", args->path);
411 
412 	int rc, ret = 0;
413 	struct ext2_inode *cur_dir = NULL, *next = NULL;
414 	static char name_buf[EXT2_MAX_FILE_NAME + 1];
415 
416 	/* Start looking from root directory of file system */
417 	rc = ext2_inode_get(fs, EXT2_ROOT_INODE, &cur_dir);
418 	if (rc < 0) {
419 		ret = rc;
420 		goto out;
421 	}
422 
423 	/* There may be slash at the beginning of path */
424 	const char *path = args->path;
425 
426 	path = skip_slash(path);
427 
428 	/* If path is empty then return root directory */
429 	if (path[0] == '\0') {
430 		args->inode = cur_dir;
431 		cur_dir = NULL;
432 		goto out;
433 	}
434 
435 	for (;;) {
436 		/* Get path component */
437 		char *end = strchrnul(path, '/');
438 		size_t len = end - path;
439 
440 		if (len > EXT2_MAX_FILE_NAME) {
441 			ret = -ENAMETOOLONG;
442 			goto out;
443 		}
444 
445 		strncpy(name_buf, path, len);
446 		name_buf[len] = '\0';
447 
448 		/* Search in current directory */
449 		uint32_t dir_off = 0;
450 		/* using 64 bit value to don't lose any information on error */
451 		int64_t ino = find_dir_entry(cur_dir, name_buf, len, &dir_off);
452 
453 		const char *next_path = skip_slash(end);
454 		bool last_entry = next_path[0] == '\0';
455 
456 		if (!last_entry) {
457 			/*  prepare the next loop iteration */
458 
459 			if (ino < 0) {
460 				/* next entry not found */
461 				ret = -ENOENT;
462 				goto out;
463 			}
464 
465 			rc = ext2_inode_get(fs, ino, &next);
466 			if (rc < 0) {
467 				/* error while fetching next entry */
468 				ret = rc;
469 				goto out;
470 			}
471 
472 			if (!(next->i_mode & EXT2_S_IFDIR)) {
473 				/* path component should be directory */
474 				ret = -ENOTDIR;
475 				goto out;
476 			}
477 
478 			/* Go to the next path component */
479 			path = next_path;
480 
481 			/* Move to next directory */
482 			ext2_inode_drop(cur_dir);
483 			cur_dir = next;
484 
485 			next = NULL;
486 			continue;
487 		}
488 
489 		/* Last entry */
490 
491 		if (ino < 0 && !(args->flags & LOOKUP_ARG_CREATE)) {
492 			/* entry not found but we need it */
493 			ret = -ENOENT;
494 			goto out;
495 		}
496 
497 		if (ino > 0) {
498 			rc = ext2_inode_get(fs, ino, &next);
499 			if (rc < 0) {
500 				ret = rc;
501 				goto out;
502 			}
503 		}
504 
505 		/* Store parent directory and offset in parent directory */
506 		if (args->flags & (LOOKUP_ARG_CREATE | LOOKUP_ARG_STAT | LOOKUP_ARG_UNLINK)) {
507 			/* In create it will be valid only if we have found existing file */
508 			args->offset = dir_off;
509 			args->parent = cur_dir;
510 			cur_dir = NULL;
511 		}
512 
513 		/* Store name info */
514 		if (args->flags & LOOKUP_ARG_CREATE) {
515 			args->name_pos = path - args->path;
516 			args->name_len = len;
517 		}
518 
519 		/* Store found inode */
520 		if (ino > 0) {
521 			args->inode = next;
522 			next = NULL;
523 		}
524 		goto out;
525 	}
526 
527 out:
528 	/* Always free that inodes.
529 	 * If some of them is returned from function then proper pointer is set to NULL.
530 	 */
531 	ext2_inode_drop(cur_dir);
532 	ext2_inode_drop(next);
533 	return ret;
534 }
535 
536 /* Return position of given char or end of string. */
strchrnul(const char * s,char c)537 static char *strchrnul(const char *s, char c)
538 {
539 	while ((*s != c) && (*s != '\0')) {
540 		s++;
541 	}
542 	return (char *) s;
543 }
544 
skip_slash(const char * s)545 static const char *skip_slash(const char *s)
546 {
547 	while ((*s == '/') && (*s != '\0')) {
548 		s++;
549 	}
550 	return s;
551 }
552 
553 /**
554  * @brief Find inode
555  *
556  * @note Inodes are 32 bit. When we return signed 64 bit number then we don't
557  *       lose any information.
558  *
559  * @param r_offset If not NULL then offset in directory of that entry is written here.
560  * @return Inode number or negative error code
561  */
find_dir_entry(struct ext2_inode * inode,const char * name,size_t len,uint32_t * r_offset)562 static int64_t find_dir_entry(struct ext2_inode *inode, const char *name, size_t len,
563 		uint32_t *r_offset)
564 {
565 	int rc;
566 	uint32_t block, block_off, offset = 0;
567 	int64_t ino = -1;
568 	struct ext2_data *fs = inode->i_fs;
569 	struct ext2_direntry *de;
570 
571 	while (offset < inode->i_size) {
572 		block = offset / fs->block_size;
573 		block_off = offset % fs->block_size;
574 
575 		rc = ext2_fetch_inode_block(inode, block);
576 		if (rc < 0) {
577 			return rc;
578 		}
579 
580 		struct ext2_disk_direntry *disk_de =
581 			EXT2_DISK_DIRENTRY_BY_OFFSET(inode_current_block_mem(inode), block_off);
582 
583 		de = ext2_fetch_direntry(disk_de);
584 		if (de == NULL) {
585 			return -EINVAL;
586 		}
587 
588 		if (len == de->de_name_len && strncmp(de->de_name, name, len) == 0) {
589 			ino = de->de_inode;
590 			if (r_offset) {
591 				/* Return offset*/
592 				*r_offset = offset;
593 			}
594 			goto success;
595 		}
596 		/* move to the next directory entry */
597 		offset += de->de_rec_len;
598 		k_heap_free(&direntry_heap, de);
599 	}
600 
601 	return -EINVAL;
602 success:
603 	k_heap_free(&direntry_heap, de);
604 	return (int64_t)ino;
605 }
606 
607 /* Inode operations --------------------------------------------------------- */
608 
ext2_inode_read(struct ext2_inode * inode,void * buf,uint32_t offset,size_t nbytes)609 ssize_t ext2_inode_read(struct ext2_inode *inode, void *buf, uint32_t offset, size_t nbytes)
610 {
611 	int rc = 0;
612 	ssize_t read = 0;
613 	uint32_t block_size = inode->i_fs->block_size;
614 	size_t nbytes_to_read = nbytes;
615 
616 	while (read < nbytes && offset < inode->i_size) {
617 
618 		uint32_t block = offset / block_size;
619 		uint32_t block_off = offset % block_size;
620 
621 		rc = ext2_fetch_inode_block(inode, block);
622 		if (rc < 0) {
623 			break;
624 		}
625 
626 		uint32_t left_on_blk = block_size - block_off;
627 		uint32_t left_in_file = inode->i_size - offset;
628 		size_t to_read = MIN(nbytes_to_read, MIN(left_on_blk, left_in_file));
629 
630 		memcpy((uint8_t *)buf + read, inode_current_block_mem(inode) + block_off, to_read);
631 
632 		read += to_read;
633 		nbytes_to_read -= read;
634 		offset += to_read;
635 	}
636 
637 	if (rc < 0) {
638 		return rc;
639 	}
640 	return read;
641 }
642 
ext2_inode_write(struct ext2_inode * inode,const void * buf,uint32_t offset,size_t nbytes)643 ssize_t ext2_inode_write(struct ext2_inode *inode, const void *buf, uint32_t offset, size_t nbytes)
644 {
645 	int rc = 0;
646 	ssize_t written = 0;
647 	uint32_t block_size = inode->i_fs->block_size;
648 
649 	while (written < nbytes) {
650 		uint32_t block = offset / block_size;
651 		uint32_t block_off = offset % block_size;
652 
653 		LOG_DBG("inode:%d Write to block %d (offset: %d-%zd/%d)",
654 				inode->i_id, block, offset, offset + nbytes, inode->i_size);
655 
656 		rc = ext2_fetch_inode_block(inode, block);
657 		if (rc < 0) {
658 			break;
659 		}
660 
661 		size_t to_write = MIN(nbytes, block_size - block_off);
662 
663 		memcpy(inode_current_block_mem(inode) + block_off, (uint8_t *)buf + written,
664 				to_write);
665 		LOG_DBG("Written %zd bytes at offset %d in block i%d", to_write, block_off, block);
666 
667 		rc = ext2_commit_inode_block(inode);
668 		if (rc < 0) {
669 			break;
670 		}
671 
672 		written += to_write;
673 	}
674 
675 	if (rc < 0) {
676 		return rc;
677 	}
678 
679 	if (offset + written > inode->i_size) {
680 		LOG_DBG("New inode size: %d -> %zd", inode->i_size, offset + written);
681 		inode->i_size = offset + written;
682 		rc = ext2_commit_inode(inode);
683 		if (rc < 0) {
684 			return rc;
685 		}
686 	}
687 
688 	return written;
689 }
690 
ext2_inode_trunc(struct ext2_inode * inode,off_t length)691 int ext2_inode_trunc(struct ext2_inode *inode, off_t length)
692 {
693 	if (length > UINT32_MAX) {
694 		return -ENOTSUP;
695 	}
696 
697 	int rc = 0;
698 	uint32_t new_size = (uint32_t)length;
699 	uint32_t old_size = inode->i_size;
700 	const uint32_t block_size = inode->i_fs->block_size;
701 
702 	LOG_DBG("Resizing inode from %d to %d", old_size, new_size);
703 
704 	if (old_size == new_size) {
705 		return 0;
706 	}
707 
708 	uint32_t used_blocks = new_size / block_size + (new_size % block_size != 0);
709 
710 	if (new_size > old_size) {
711 		if (old_size % block_size != 0) {
712 			/* file ends inside some block */
713 
714 			LOG_DBG("Has to insert zeros to the end of block");
715 
716 			/* insert zeros to the end of last block */
717 			uint32_t old_block = old_size / block_size;
718 			uint32_t start_off = old_size % block_size;
719 			uint32_t to_write = MIN(new_size - old_size, block_size - start_off);
720 
721 			rc = ext2_fetch_inode_block(inode, old_block);
722 			if (rc < 0) {
723 				return rc;
724 			}
725 
726 			memset(inode_current_block_mem(inode) + start_off, 0, to_write);
727 			rc = ext2_commit_inode_block(inode);
728 			if (rc < 0) {
729 				return rc;
730 			}
731 		}
732 
733 		/* There is no need to zero rest of blocks because they will be automatically
734 		 * treated as zero filled.
735 		 */
736 
737 	} else {
738 		/* First removed block is just the number of used blocks.
739 		 * (We count blocks from zero hence its number is just number of used blocks.)
740 		 */
741 		uint32_t start_blk = used_blocks;
742 		int64_t removed_blocks;
743 
744 		LOG_DBG("Inode trunc from blk: %d", start_blk);
745 
746 		/* Remove blocks starting with start_blk. */
747 		removed_blocks = ext2_inode_remove_blocks(inode, start_blk);
748 		if (removed_blocks < 0) {
749 			return removed_blocks;
750 		}
751 
752 		LOG_DBG("Removed blocks: %lld (%lld)",
753 				removed_blocks, removed_blocks * (block_size / 512));
754 		inode->i_blocks -= removed_blocks * (block_size / 512);
755 	}
756 
757 	inode->i_size = new_size;
758 
759 	LOG_DBG("New inode size: %d (blocks: %d)", inode->i_size, inode->i_blocks);
760 
761 	rc = ext2_commit_inode(inode);
762 	return rc;
763 }
764 
write_one_block(struct ext2_data * fs,struct ext2_block * b)765 static int write_one_block(struct ext2_data *fs, struct ext2_block *b)
766 {
767 	int ret = 0;
768 
769 	if (!(b->flags & EXT2_BLOCK_ASSIGNED)) {
770 		ret = ext2_assign_block_num(fs, b);
771 		if (ret < 0) {
772 			return ret;
773 		}
774 	}
775 
776 	ret = ext2_write_block(fs, b);
777 	return ret;
778 }
779 
ext2_inode_sync(struct ext2_inode * inode)780 int ext2_inode_sync(struct ext2_inode *inode)
781 {
782 	int ret;
783 	struct ext2_data *fs = inode->i_fs;
784 
785 	for (int i = 0; i < 4; ++i) {
786 		if (inode->blocks[i] == NULL) {
787 			break;
788 		}
789 		ret = write_one_block(fs, inode->blocks[i]);
790 		if (ret < 0) {
791 			return ret;
792 		}
793 		ret = fs->backend_ops->sync(fs);
794 		if (ret < 0) {
795 			return ret;
796 		}
797 	}
798 	return 0;
799 }
800 
ext2_get_direntry(struct ext2_file * dir,struct fs_dirent * ent)801 int ext2_get_direntry(struct ext2_file *dir, struct fs_dirent *ent)
802 {
803 	if (dir->f_off >= dir->f_inode->i_size) {
804 		/* end of directory */
805 		ent->name[0] = 0;
806 		return 0;
807 	}
808 
809 	struct ext2_data *fs = dir->f_inode->i_fs;
810 
811 	int rc, ret = 0;
812 	uint32_t block = dir->f_off / fs->block_size;
813 	uint32_t block_off = dir->f_off % fs->block_size;
814 	uint32_t len;
815 
816 	LOG_DBG("Reading dir entry from block %d at offset %d", block, block_off);
817 
818 	rc = ext2_fetch_inode_block(dir->f_inode, block);
819 	if (rc < 0) {
820 		return rc;
821 	}
822 
823 	struct ext2_inode *inode = NULL;
824 	struct ext2_disk_direntry *disk_de =
825 		EXT2_DISK_DIRENTRY_BY_OFFSET(inode_current_block_mem(dir->f_inode), block_off);
826 	struct ext2_direntry *de = ext2_fetch_direntry(disk_de);
827 
828 	if (de == NULL) {
829 		LOG_ERR("Read directory entry name too long");
830 		return -EINVAL;
831 	}
832 
833 	LOG_DBG("inode=%d name_len=%d rec_len=%d", de->de_inode, de->de_name_len, de->de_rec_len);
834 
835 	len = de->de_name_len;
836 	if (de->de_name_len > MAX_FILE_NAME) {
837 		LOG_WRN("Directory name won't fit in direntry");
838 		len = MAX_FILE_NAME;
839 	}
840 	memcpy(ent->name, de->de_name, len);
841 	ent->name[len] = '\0';
842 
843 	LOG_DBG("name_len=%d name=%s %d", de->de_name_len, ent->name, EXT2_MAX_FILE_NAME);
844 
845 	/* Get type of directory entry */
846 	ent->type = de->de_file_type & EXT2_FT_DIR ? FS_DIR_ENTRY_DIR : FS_DIR_ENTRY_FILE;
847 
848 	/* Get size only for files. Directories have size 0. */
849 	size_t size = 0;
850 
851 	if (ent->type == FS_DIR_ENTRY_FILE) {
852 		rc = ext2_inode_get(fs, de->de_inode, &inode);
853 		if (rc < 0) {
854 			ret = rc;
855 			goto out;
856 		}
857 		size = inode->i_size;
858 	}
859 
860 	ent->size = size;
861 
862 	/* Update offset to point to next directory entry */
863 	dir->f_off += de->de_rec_len;
864 
865 out:
866 	k_heap_free(&direntry_heap, de);
867 	ext2_inode_drop(inode);
868 	return ret;
869 }
870 
871 /* Create files and directories */
872 
873 /* Allocate inode number and fill inode table with default values. */
ext2_create_inode(struct ext2_data * fs,struct ext2_inode * parent,struct ext2_inode * inode,int type)874 static int ext2_create_inode(struct ext2_data *fs, struct ext2_inode *parent,
875 		struct ext2_inode *inode, int type)
876 {
877 	int rc;
878 	int32_t ino = ext2_alloc_inode(fs);
879 
880 	if (ino < 0) {
881 		return ino;
882 	}
883 
884 	/* fill inode with correct data */
885 	inode->i_fs = fs;
886 	inode->flags = 0;
887 	inode->i_id = ino;
888 	inode->i_size = 0;
889 	inode->i_mode = type == FS_DIR_ENTRY_FILE ? EXT2_DEF_FILE_MODE : EXT2_DEF_DIR_MODE;
890 	inode->i_links_count = 0;
891 	memset(inode->i_block, 0, 15 * 4);
892 
893 	if (type == FS_DIR_ENTRY_DIR) {
894 		/* Block group current block is already fetched. We don't have to do it again.
895 		 * (It was done above in ext2_alloc_inode function.)
896 		 */
897 		fs->bgroup.bg_used_dirs_count += 1;
898 		rc = ext2_commit_bg(fs);
899 		if (rc < 0) {
900 			return rc;
901 		}
902 	}
903 
904 	rc = ext2_commit_inode(inode);
905 	return rc;
906 }
907 
ext2_create_direntry(const char * name,uint8_t namelen,uint32_t ino,uint8_t filetype)908 struct ext2_direntry *ext2_create_direntry(const char *name, uint8_t namelen, uint32_t ino,
909 		uint8_t filetype)
910 {
911 	__ASSERT(namelen <= EXT2_MAX_FILE_NAME, "Name length to long");
912 
913 	uint32_t prog_rec_len = sizeof(struct ext2_direntry) + namelen;
914 	struct ext2_direntry *de = k_heap_alloc(&direntry_heap, prog_rec_len, K_FOREVER);
915 
916 	/* Size of future disk structure. */
917 	uint32_t reclen = sizeof(struct ext2_disk_direntry) + namelen;
918 
919 	/* Align reclen to 4 bytes. */
920 	reclen = ROUND_UP(reclen, 4);
921 
922 	de->de_inode = ino;
923 	de->de_rec_len = reclen;
924 	de->de_name_len = (uint8_t)namelen;
925 	de->de_file_type = filetype;
926 	memcpy(de->de_name, name, namelen);
927 
928 	LOG_DBG("Initialized directory entry %p{%s(%d) %d %d %c}",
929 			de, de->de_name, de->de_name_len, de->de_inode, de->de_rec_len,
930 			de->de_file_type == EXT2_FT_DIR ? 'd' : 'f');
931 	return de;
932 }
933 
ext2_add_direntry(struct ext2_inode * dir,struct ext2_direntry * entry)934 static int ext2_add_direntry(struct ext2_inode *dir, struct ext2_direntry *entry)
935 {
936 	LOG_DBG("Adding entry: {in=%d type=%d name_len=%d} to directory (in=%d)",
937 			entry->de_inode, entry->de_file_type, entry->de_name_len, dir->i_id);
938 
939 	int rc = 0;
940 	uint32_t block_size = dir->i_fs->block_size;
941 	uint32_t entry_size = sizeof(struct ext2_disk_direntry) + entry->de_name_len;
942 
943 	if (entry_size > block_size) {
944 		return -EINVAL;
945 	}
946 
947 	/* Find last entry */
948 	/* get last block and start from first entry on that block */
949 	int last_blk = (dir->i_size / block_size) - 1;
950 
951 	rc = ext2_fetch_inode_block(dir, last_blk);
952 	if (rc < 0) {
953 		return rc;
954 	}
955 
956 	uint32_t offset = 0;
957 	uint16_t reclen;
958 
959 	struct ext2_disk_direntry *de = 0;
960 
961 	/* loop must be executed at least once, because block_size > 0 */
962 	while (offset < block_size) {
963 		de = EXT2_DISK_DIRENTRY_BY_OFFSET(inode_current_block_mem(dir), offset);
964 		reclen = ext2_get_disk_direntry_reclen(de);
965 		if (offset + reclen == block_size) {
966 			break;
967 		}
968 		offset += reclen;
969 	}
970 
971 
972 	uint32_t occupied = sizeof(struct ext2_disk_direntry) + ext2_get_disk_direntry_namelen(de);
973 
974 	/* Align to 4 bytes */
975 	occupied = ROUND_UP(occupied, 4);
976 
977 	LOG_DBG("Occupied: %d total: %d needed: %d", occupied, reclen, entry_size);
978 
979 	if (reclen - occupied >= entry_size) {
980 		/* Entry fits into current block */
981 		offset += occupied;
982 		entry->de_rec_len = block_size - offset;
983 		ext2_set_disk_direntry_reclen(de, occupied);
984 	} else {
985 		LOG_DBG("Allocating new block for directory");
986 
987 		/* Have to allocate new block */
988 		rc = ext2_fetch_inode_block(dir, last_blk + 1);
989 		if (rc < 0) {
990 			return rc;
991 		}
992 
993 		/* Increase size of directory */
994 		dir->i_size += block_size;
995 		rc = ext2_commit_inode(dir);
996 		if (rc < 0) {
997 			return rc;
998 		}
999 		rc = ext2_commit_inode_block(dir);
1000 		if (rc < 0) {
1001 			return rc;
1002 		}
1003 
1004 		/* New entry will start at offset 0 */
1005 		offset = 0;
1006 		entry->de_rec_len = block_size;
1007 	}
1008 
1009 	LOG_DBG("Writing entry {in=%d type=%d rec_len=%d name_len=%d} to block %d of inode %d",
1010 			entry->de_inode, entry->de_file_type, entry->de_rec_len, entry->de_name_len,
1011 			inode_current_block(dir)->num, dir->i_id);
1012 
1013 
1014 	de = EXT2_DISK_DIRENTRY_BY_OFFSET(inode_current_block_mem(dir), offset);
1015 	ext2_write_direntry(de, entry);
1016 
1017 	rc = ext2_commit_inode_block(dir);
1018 	return rc;
1019 }
1020 
ext2_create_file(struct ext2_inode * parent,struct ext2_inode * new_inode,struct ext2_lookup_args * args)1021 int ext2_create_file(struct ext2_inode *parent, struct ext2_inode *new_inode,
1022 		struct ext2_lookup_args *args)
1023 {
1024 	int rc, ret = 0;
1025 	struct ext2_direntry *entry;
1026 	struct ext2_data *fs = parent->i_fs;
1027 
1028 	rc = ext2_create_inode(fs, args->inode, new_inode, FS_DIR_ENTRY_FILE);
1029 	if (rc < 0) {
1030 		return rc;
1031 	}
1032 
1033 	entry = ext2_create_direntry(args->path + args->name_pos, args->name_len, new_inode->i_id,
1034 			EXT2_FT_REG_FILE);
1035 
1036 	rc = ext2_add_direntry(parent, entry);
1037 	if (rc < 0) {
1038 		ret = rc;
1039 		goto out;
1040 	}
1041 
1042 	/* Successfully added to directory */
1043 	new_inode->i_links_count += 1;
1044 
1045 	rc = ext2_commit_inode(new_inode);
1046 	if (rc < 0) {
1047 		ret = rc;
1048 	}
1049 out:
1050 	k_heap_free(&direntry_heap, entry);
1051 	return ret;
1052 }
1053 
ext2_create_dir(struct ext2_inode * parent,struct ext2_inode * new_inode,struct ext2_lookup_args * args)1054 int ext2_create_dir(struct ext2_inode *parent, struct ext2_inode *new_inode,
1055 		struct ext2_lookup_args *args)
1056 {
1057 	int rc, ret = 0;
1058 	struct ext2_direntry *entry;
1059 	struct ext2_disk_direntry *disk_de;
1060 	struct ext2_data *fs = parent->i_fs;
1061 	uint32_t block_size = parent->i_fs->block_size;
1062 
1063 	rc = ext2_create_inode(fs, args->inode, new_inode, FS_DIR_ENTRY_DIR);
1064 	if (rc < 0) {
1065 		return rc;
1066 	}
1067 
1068 	/* Directory must have at least one block */
1069 	new_inode->i_size = block_size;
1070 
1071 	entry = ext2_create_direntry(args->path + args->name_pos, args->name_len, new_inode->i_id,
1072 			EXT2_FT_DIR);
1073 
1074 	rc = ext2_add_direntry(parent, entry);
1075 	if (rc < 0) {
1076 		ret = rc;
1077 		goto out;
1078 	}
1079 
1080 	/* Successfully added to directory */
1081 	new_inode->i_links_count += 1;
1082 
1083 	k_heap_free(&direntry_heap, entry);
1084 
1085 	/* Create "." directory entry */
1086 	entry = ext2_create_direntry(".", 1, new_inode->i_id, EXT2_FT_DIR);
1087 	entry->de_rec_len = block_size;
1088 
1089 	/* It has to be inserted manually */
1090 	rc = ext2_fetch_inode_block(new_inode, 0);
1091 	if (rc < 0) {
1092 		ret = rc;
1093 		goto out;
1094 	}
1095 
1096 	disk_de = EXT2_DISK_DIRENTRY_BY_OFFSET(inode_current_block_mem(new_inode), 0);
1097 	ext2_write_direntry(disk_de, entry);
1098 
1099 	new_inode->i_links_count += 1;
1100 
1101 	k_heap_free(&direntry_heap, entry);
1102 
1103 	/* Add ".." directory entry */
1104 	entry = ext2_create_direntry("..", 2, parent->i_id, EXT2_FT_DIR);
1105 
1106 	rc = ext2_add_direntry(new_inode, entry);
1107 	if (rc < 0) {
1108 		ret = rc;
1109 		goto out;
1110 	}
1111 
1112 	/* Successfully added to directory */
1113 	parent->i_links_count += 1;
1114 
1115 	rc = ext2_commit_inode_block(new_inode);
1116 	if (rc < 0) {
1117 		ret = rc;
1118 	}
1119 
1120 	rc = ext2_commit_inode_block(parent);
1121 	if (rc < 0) {
1122 		ret = rc;
1123 	}
1124 
1125 	/* Commit inodes after increasing link counts */
1126 	rc = ext2_commit_inode(new_inode);
1127 	if (rc < 0) {
1128 		ret = rc;
1129 	}
1130 
1131 	rc = ext2_commit_inode(parent);
1132 	if (rc < 0) {
1133 		ret = rc;
1134 	}
1135 out:
1136 	k_heap_free(&direntry_heap, entry);
1137 	return ret;
1138 }
1139 
ext2_del_direntry(struct ext2_inode * parent,uint32_t offset)1140 static int ext2_del_direntry(struct ext2_inode *parent, uint32_t offset)
1141 {
1142 	int rc = 0;
1143 	uint32_t block_size = parent->i_fs->block_size;
1144 
1145 	uint32_t blk = offset / block_size;
1146 	uint32_t blk_off = offset % block_size;
1147 
1148 	rc = ext2_fetch_inode_block(parent, blk);
1149 	if (rc < 0) {
1150 		return rc;
1151 	}
1152 
1153 	if (blk_off == 0) {
1154 		struct ext2_disk_direntry *de =
1155 			EXT2_DISK_DIRENTRY_BY_OFFSET(inode_current_block_mem(parent), 0);
1156 		uint16_t reclen = ext2_get_disk_direntry_reclen(de);
1157 
1158 		if (reclen == block_size) {
1159 			/* Remove whole block */
1160 
1161 			uint32_t last_blk = parent->i_size / block_size - 1;
1162 			uint32_t old_blk = parent->i_block[blk];
1163 
1164 			/* move last block in place of removed one. Entries start only at beginning
1165 			 * of the block, hence we don't have to care to move any entry.
1166 			 */
1167 			parent->i_block[blk] = parent->i_block[last_blk];
1168 			parent->i_block[last_blk] = 0;
1169 
1170 			/* Free removed block */
1171 			rc = ext2_free_block(parent->i_fs, old_blk);
1172 			if (rc < 0) {
1173 				return rc;
1174 			}
1175 
1176 			rc = ext2_commit_inode(parent);
1177 			if (rc < 0) {
1178 				return rc;
1179 			}
1180 		} else {
1181 			/* Move next entry to beginning of block */
1182 			struct ext2_disk_direntry *next =
1183 			      EXT2_DISK_DIRENTRY_BY_OFFSET(inode_current_block_mem(parent), reclen);
1184 			uint16_t next_reclen = ext2_get_disk_direntry_reclen(next);
1185 
1186 			memmove(de, next, next_reclen);
1187 			ext2_set_disk_direntry_reclen(de, reclen + next_reclen);
1188 
1189 			rc = ext2_commit_inode_block(parent);
1190 			if (rc < 0) {
1191 				return rc;
1192 			}
1193 		}
1194 
1195 	} else {
1196 		/* Entry inside the block */
1197 		uint32_t cur = 0;
1198 		uint16_t reclen;
1199 
1200 		struct ext2_disk_direntry *de =
1201 			EXT2_DISK_DIRENTRY_BY_OFFSET(inode_current_block_mem(parent), 0);
1202 
1203 		reclen = ext2_get_disk_direntry_reclen(de);
1204 		/* find previous entry */
1205 		while (cur + reclen < blk_off) {
1206 			cur += reclen;
1207 			de = EXT2_DISK_DIRENTRY_BY_OFFSET(inode_current_block_mem(parent), cur);
1208 			reclen = ext2_get_disk_direntry_reclen(de);
1209 		}
1210 
1211 		struct ext2_disk_direntry *del_entry =
1212 			EXT2_DISK_DIRENTRY_BY_OFFSET(inode_current_block_mem(parent), blk_off);
1213 		uint16_t del_reclen = ext2_get_disk_direntry_reclen(del_entry);
1214 
1215 		ext2_set_disk_direntry_reclen(de, reclen + del_reclen);
1216 		rc = ext2_commit_inode_block(parent);
1217 		if (rc < 0) {
1218 			return rc;
1219 		}
1220 	}
1221 
1222 	return 0;
1223 }
1224 
remove_inode(struct ext2_inode * inode)1225 static int remove_inode(struct ext2_inode *inode)
1226 {
1227 	int ret = 0;
1228 
1229 	LOG_DBG("inode: %d", inode->i_id);
1230 
1231 	/* Free blocks of inode */
1232 	ret = ext2_inode_remove_blocks(inode, 0);
1233 	if (ret < 0) {
1234 		return ret;
1235 	}
1236 
1237 	/* Free inode */
1238 	ret = ext2_free_inode(inode->i_fs, inode->i_id, IS_DIR(inode->i_mode));
1239 	return ret;
1240 }
1241 
can_unlink(struct ext2_inode * inode)1242 static int can_unlink(struct ext2_inode *inode)
1243 {
1244 	if (!IS_DIR(inode->i_mode)) {
1245 		return 0;
1246 	}
1247 
1248 	int rc = 0;
1249 
1250 	rc = ext2_fetch_inode_block(inode, 0);
1251 	if (rc < 0) {
1252 		return rc;
1253 	}
1254 
1255 	/* If directory check if it is empty */
1256 
1257 	uint32_t offset = 0;
1258 	struct ext2_disk_direntry *de;
1259 
1260 	/* Get first entry */
1261 	de = EXT2_DISK_DIRENTRY_BY_OFFSET(inode_current_block_mem(inode), 0);
1262 	offset += ext2_get_disk_direntry_reclen(de);
1263 
1264 	/* Get second entry */
1265 	de = EXT2_DISK_DIRENTRY_BY_OFFSET(inode_current_block_mem(inode), offset);
1266 	offset += ext2_get_disk_direntry_reclen(de);
1267 
1268 	uint32_t block_size = inode->i_fs->block_size;
1269 
1270 	/* If directory has size of one block and second entry ends with block end
1271 	 * then directory is empty.
1272 	 */
1273 	if (offset == block_size && inode->i_size == block_size) {
1274 		return 0;
1275 	}
1276 
1277 	return -ENOTEMPTY;
1278 }
1279 
ext2_inode_unlink(struct ext2_inode * parent,struct ext2_inode * inode,uint32_t offset)1280 int ext2_inode_unlink(struct ext2_inode *parent, struct ext2_inode *inode, uint32_t offset)
1281 {
1282 	int rc;
1283 
1284 	rc = can_unlink(inode);
1285 	if (rc < 0) {
1286 		return rc;
1287 	}
1288 
1289 	rc = ext2_del_direntry(parent, offset);
1290 	if (rc < 0) {
1291 		return rc;
1292 	}
1293 
1294 	if ((IS_REG_FILE(inode->i_mode) && inode->i_links_count == 1) ||
1295 			(IS_DIR(inode->i_mode) && inode->i_links_count == 2)) {
1296 
1297 		/* Only set the flag. Inode may still be open. Inode will be
1298 		 * removed after dropping all references to it.
1299 		 */
1300 		inode->flags |= INODE_REMOVE;
1301 	}
1302 
1303 	inode->i_links_count -= 1;
1304 	rc = ext2_commit_inode(inode);
1305 	if (rc < 0) {
1306 		return rc;
1307 	}
1308 	return 0;
1309 }
1310 
ext2_replace_file(struct ext2_lookup_args * args_from,struct ext2_lookup_args * args_to)1311 int ext2_replace_file(struct ext2_lookup_args *args_from, struct ext2_lookup_args *args_to)
1312 {
1313 	LOG_DBG("Replace existing directory entry in rename");
1314 	LOG_DBG("Inode: %d Inode to replace: %d", args_from->inode->i_id, args_to->inode->i_id);
1315 
1316 	int rc = 0;
1317 	struct ext2_disk_direntry *de;
1318 
1319 	uint32_t block_size = args_from->parent->i_fs->block_size;
1320 	uint32_t from_offset = args_from->offset;
1321 	uint32_t from_blk = from_offset / block_size;
1322 	uint32_t from_blk_off = from_offset % block_size;
1323 
1324 	rc = ext2_fetch_inode_block(args_from->parent, from_blk);
1325 	if (rc < 0) {
1326 		return rc;
1327 	}
1328 
1329 	de = EXT2_DISK_DIRENTRY_BY_OFFSET(inode_current_block_mem(args_from->parent), from_blk_off);
1330 
1331 	/* record file type */
1332 	uint8_t file_type = ext2_get_disk_direntry_type(de);
1333 
1334 	/* NOTE: Replace the inode number in removed entry with inode of file that will be replaced
1335 	 * with new one. Thanks to that we can use the function that unlinks directory entry to get
1336 	 * rid of old directory entry and link to inode that will no longer be referenced by the
1337 	 * directory entry after it is replaced with moved file.
1338 	 */
1339 	ext2_set_disk_direntry_inode(de, args_to->inode->i_id);
1340 	rc = ext2_inode_unlink(args_from->parent, args_to->inode, args_from->offset);
1341 	if (rc < 0) {
1342 		/* restore the old inode number */
1343 		ext2_set_disk_direntry_inode(de, args_from->inode->i_id);
1344 		return rc;
1345 	}
1346 
1347 	uint32_t to_offset = args_to->offset;
1348 	uint32_t to_blk = to_offset / block_size;
1349 	uint32_t to_blk_off = to_offset % block_size;
1350 
1351 	rc = ext2_fetch_inode_block(args_to->parent, to_blk);
1352 	if (rc < 0) {
1353 		return rc;
1354 	}
1355 
1356 	de = EXT2_DISK_DIRENTRY_BY_OFFSET(inode_current_block_mem(args_to->parent), to_blk_off);
1357 
1358 	/* change inode of new entry */
1359 	ext2_set_disk_direntry_inode(de, args_from->inode->i_id);
1360 	ext2_set_disk_direntry_type(de, file_type);
1361 
1362 	rc = ext2_commit_inode_block(args_to->parent);
1363 	if (rc < 0) {
1364 		return rc;
1365 	}
1366 	return 0;
1367 }
1368 
ext2_move_file(struct ext2_lookup_args * args_from,struct ext2_lookup_args * args_to)1369 int ext2_move_file(struct ext2_lookup_args *args_from, struct ext2_lookup_args *args_to)
1370 {
1371 	int rc = 0;
1372 	uint32_t block_size = args_from->parent->i_fs->block_size;
1373 
1374 	struct ext2_inode *fparent = args_from->parent;
1375 	struct ext2_inode *tparent = args_to->parent;
1376 	uint32_t offset = args_from->offset;
1377 	uint32_t blk = offset / block_size;
1378 	uint32_t blk_off = offset % block_size;
1379 
1380 	/* Check if we could just modify existing entry */
1381 	if (fparent->i_id == tparent->i_id) {
1382 		rc = ext2_fetch_inode_block(fparent, blk);
1383 		if (rc < 0) {
1384 			return rc;
1385 		}
1386 
1387 		struct ext2_disk_direntry *de;
1388 
1389 		de = EXT2_DISK_DIRENTRY_BY_OFFSET(inode_current_block_mem(fparent), blk_off);
1390 
1391 		uint16_t reclen = ext2_get_disk_direntry_reclen(de);
1392 
1393 		/* If new name fits in old entry, then just copy it there */
1394 		if (reclen - sizeof(struct ext2_disk_direntry) >= args_to->name_len) {
1395 			LOG_DBG("Old entry is modified to hold new name");
1396 			ext2_set_disk_direntry_namelen(de, args_to->name_len);
1397 			ext2_set_disk_direntry_name(de, args_to->path + args_to->name_pos,
1398 					args_to->name_len);
1399 
1400 			rc = ext2_commit_inode_block(fparent);
1401 			return rc;
1402 		}
1403 	}
1404 
1405 	LOG_DBG("Create new directory entry in rename");
1406 
1407 	int ret = 0;
1408 
1409 	rc = ext2_fetch_inode_block(fparent, blk);
1410 	if (rc < 0) {
1411 		return rc;
1412 	}
1413 
1414 	struct ext2_disk_direntry *old_de;
1415 	struct ext2_direntry *new_de;
1416 
1417 	old_de = EXT2_DISK_DIRENTRY_BY_OFFSET(inode_current_block_mem(fparent), blk_off);
1418 
1419 	uint32_t inode = ext2_get_disk_direntry_inode(old_de);
1420 	uint8_t file_type = ext2_get_disk_direntry_type(old_de);
1421 
1422 	new_de = ext2_create_direntry(args_to->path + args_to->name_pos, args_to->name_len, inode,
1423 			file_type);
1424 
1425 	rc = ext2_add_direntry(tparent, new_de);
1426 	if (rc < 0) {
1427 		ret = rc;
1428 		goto out;
1429 	}
1430 
1431 	rc = ext2_del_direntry(fparent, args_from->offset);
1432 	if (rc < 0) {
1433 		return rc;
1434 	}
1435 
1436 out:
1437 	k_heap_free(&direntry_heap, new_de);
1438 	return ret;
1439 }
1440 
ext2_inode_get(struct ext2_data * fs,uint32_t ino,struct ext2_inode ** ret)1441 int ext2_inode_get(struct ext2_data *fs, uint32_t ino, struct ext2_inode **ret)
1442 {
1443 	int rc;
1444 	struct ext2_inode *inode;
1445 
1446 	for (int i = 0; i < fs->open_inodes; ++i) {
1447 		inode = fs->inode_pool[i];
1448 
1449 		if (inode->i_id == ino) {
1450 			*ret = inode;
1451 			inode->i_ref++;
1452 			return 0;
1453 		}
1454 	}
1455 
1456 	if (fs->open_inodes >= MAX_INODES) {
1457 		return -ENOMEM;
1458 	}
1459 
1460 
1461 	rc = k_mem_slab_alloc(&inode_struct_slab, (void **)&inode, K_FOREVER);
1462 	if (rc < 0) {
1463 		return -ENOMEM;
1464 	}
1465 	memset(inode, 0, sizeof(struct ext2_inode));
1466 
1467 	if (ino != 0) {
1468 		int rc2 = ext2_fetch_inode(fs, ino, inode);
1469 
1470 		if (rc2 < 0) {
1471 			k_mem_slab_free(&inode_struct_slab, (void *)inode);
1472 			return rc2;
1473 		}
1474 	}
1475 
1476 	fs->inode_pool[fs->open_inodes] = inode;
1477 	fs->open_inodes++;
1478 
1479 	inode->i_fs = fs;
1480 	inode->i_ref = 1;
1481 	*ret = inode;
1482 	return 0;
1483 }
1484 
ext2_inode_drop(struct ext2_inode * inode)1485 int ext2_inode_drop(struct ext2_inode *inode)
1486 {
1487 	if (inode == NULL) {
1488 		return 0;
1489 	}
1490 
1491 	struct ext2_data *fs = inode->i_fs;
1492 
1493 	if (fs->open_inodes <= 0) {
1494 		LOG_WRN("All inodes should be already closed");
1495 		return 0;
1496 	}
1497 
1498 	inode->i_ref--;
1499 
1500 	/* Clean inode if that was last reference */
1501 	if (inode->i_ref == 0) {
1502 
1503 		/* find entry */
1504 		uint32_t offset = 0;
1505 
1506 		while (offset < MAX_INODES && fs->inode_pool[offset] != inode) {
1507 			offset++;
1508 		}
1509 
1510 		if (offset >= MAX_INODES) {
1511 			LOG_ERR("Inode structure at %p not in inode_pool", inode);
1512 			return -EINVAL;
1513 		}
1514 
1515 		ext2_inode_drop_blocks(inode);
1516 
1517 		if (inode->flags & INODE_REMOVE) {
1518 			/* This is the inode that should be removed because
1519 			 * there was called unlink function on it.
1520 			 */
1521 			int rc = remove_inode(inode);
1522 
1523 			if (rc < 0) {
1524 				return rc;
1525 			}
1526 		}
1527 
1528 		k_mem_slab_free(&inode_struct_slab, (void *)inode);
1529 
1530 		/* copy last open in place of freed inode */
1531 		uint32_t last = fs->open_inodes - 1;
1532 
1533 		fs->inode_pool[offset] = fs->inode_pool[last];
1534 		fs->open_inodes--;
1535 
1536 	}
1537 
1538 	return 0;
1539 }
1540 
ext2_inode_drop_blocks(struct ext2_inode * inode)1541 void ext2_inode_drop_blocks(struct ext2_inode *inode)
1542 {
1543 	for (int i = 0; i < 4; ++i) {
1544 		ext2_drop_block(inode->blocks[i]);
1545 	}
1546 	inode->flags &= ~INODE_FETCHED_BLOCK;
1547 }
1548