ext4: return the error code to the caller in case something fails.
[helenos.git] / uspace / srv / fs / ext4fs / ext4fs_ops.c
blob965fd939a3f82957b77592a6c662c2fdc508804c
1 /*
2 * Copyright (c) 2011 Martin Sucha
3 * Copyright (c) 2012 Frantisek Princ
4 * All rights reserved.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
10 * - Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * - Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * - The name of the author may not be used to endorse or promote products
16 * derived from this software without specific prior written permission.
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 /** @addtogroup fs
31 * @{
33 /**
34 * @file ext4fs_ops.c
35 * @brief VFS operations for ext4 filesystem.
38 #include <errno.h>
39 #include <fibril_synch.h>
40 #include <libext4.h>
41 #include <libfs.h>
42 #include <macros.h>
43 #include <malloc.h>
44 #include <adt/hash_table.h>
45 #include <adt/hash.h>
46 #include <ipc/loc.h>
47 #include "ext4fs.h"
48 #include "../../vfs/vfs.h"
50 #define EXT4FS_NODE(node) \
51 ((node) ? (ext4fs_node_t *) (node)->data : NULL)
53 /**
54 * Type for holding an instance of mounted partition.
56 typedef struct ext4fs_instance {
57 link_t link;
58 service_id_t service_id;
59 ext4_filesystem_t *filesystem;
60 unsigned int open_nodes_count;
61 } ext4fs_instance_t;
63 /**
64 * Type for wrapping common fs_node and add some useful pointers.
66 typedef struct ext4fs_node {
67 ext4fs_instance_t *instance;
68 ext4_inode_ref_t *inode_ref;
69 fs_node_t *fs_node;
70 ht_link_t link;
71 unsigned int references;
72 } ext4fs_node_t;
74 /* Forward declarations of auxiliary functions */
76 static int ext4fs_read_directory(ipc_callid_t, aoff64_t, size_t,
77 ext4fs_instance_t *, ext4_inode_ref_t *, size_t *);
78 static int ext4fs_read_file(ipc_callid_t, aoff64_t, size_t, ext4fs_instance_t *,
79 ext4_inode_ref_t *, size_t *);
80 static bool ext4fs_is_dots(const uint8_t *, size_t);
81 static int ext4fs_instance_get(service_id_t, ext4fs_instance_t **);
82 static int ext4fs_node_get_core(fs_node_t **, ext4fs_instance_t *, fs_index_t);
83 static int ext4fs_node_put_core(ext4fs_node_t *);
85 /* Forward declarations of ext4 libfs operations. */
87 static int ext4fs_root_get(fs_node_t **, service_id_t);
88 static int ext4fs_match(fs_node_t **, fs_node_t *, const char *);
89 static int ext4fs_node_get(fs_node_t **, service_id_t, fs_index_t);
90 static int ext4fs_node_open(fs_node_t *);
91 static int ext4fs_node_put(fs_node_t *);
92 static int ext4fs_create_node(fs_node_t **, service_id_t, int);
93 static int ext4fs_destroy_node(fs_node_t *);
94 static int ext4fs_link(fs_node_t *, fs_node_t *, const char *);
95 static int ext4fs_unlink(fs_node_t *, fs_node_t *, const char *);
96 static int ext4fs_has_children(bool *, fs_node_t *);
97 static fs_index_t ext4fs_index_get(fs_node_t *);
98 static aoff64_t ext4fs_size_get(fs_node_t *);
99 static unsigned ext4fs_lnkcnt_get(fs_node_t *);
100 static bool ext4fs_is_directory(fs_node_t *);
101 static bool ext4fs_is_file(fs_node_t *node);
102 static service_id_t ext4fs_service_get(fs_node_t *node);
103 static int ext4fs_size_block(service_id_t, uint32_t *);
104 static int ext4fs_total_block_count(service_id_t, uint64_t *);
105 static int ext4fs_free_block_count(service_id_t, uint64_t *);
107 /* Static variables */
109 static LIST_INITIALIZE(instance_list);
110 static FIBRIL_MUTEX_INITIALIZE(instance_list_mutex);
111 static hash_table_t open_nodes;
112 static FIBRIL_MUTEX_INITIALIZE(open_nodes_lock);
114 /* Hash table interface for open nodes hash table */
116 typedef struct {
117 service_id_t service_id;
118 fs_index_t index;
119 } node_key_t;
121 static size_t open_nodes_key_hash(void *key_arg)
123 node_key_t *key = (node_key_t *)key_arg;
124 return hash_combine(key->service_id, key->index);
127 static size_t open_nodes_hash(const ht_link_t *item)
129 ext4fs_node_t *enode = hash_table_get_inst(item, ext4fs_node_t, link);
130 return hash_combine(enode->instance->service_id, enode->inode_ref->index);
133 static bool open_nodes_key_equal(void *key_arg, const ht_link_t *item)
135 node_key_t *key = (node_key_t *)key_arg;
136 ext4fs_node_t *enode = hash_table_get_inst(item, ext4fs_node_t, link);
138 return key->service_id == enode->instance->service_id
139 && key->index == enode->inode_ref->index;
142 static hash_table_ops_t open_nodes_ops = {
143 .hash = open_nodes_hash,
144 .key_hash = open_nodes_key_hash,
145 .key_equal = open_nodes_key_equal,
146 .equal = NULL,
147 .remove_callback = NULL,
150 /** Basic initialization of the driver.
152 * This is only needed to create the hash table
153 * for storing open nodes.
155 * @return Error code
158 int ext4fs_global_init(void)
160 if (!hash_table_create(&open_nodes, 0, 0, &open_nodes_ops))
161 return ENOMEM;
163 return EOK;
166 /* Finalization of the driver.
168 * This is only needed to destroy the hash table.
170 * @return Error code
172 int ext4fs_global_fini(void)
174 hash_table_destroy(&open_nodes);
175 return EOK;
179 * Ext4 libfs operations.
182 /** Get instance from internal table by service_id.
184 * @param service_id Device identifier
185 * @param inst Output instance if successful operation
187 * @return Error code
190 int ext4fs_instance_get(service_id_t service_id, ext4fs_instance_t **inst)
192 fibril_mutex_lock(&instance_list_mutex);
194 if (list_empty(&instance_list)) {
195 fibril_mutex_unlock(&instance_list_mutex);
196 return EINVAL;
199 list_foreach(instance_list, link, ext4fs_instance_t, tmp) {
200 if (tmp->service_id == service_id) {
201 *inst = tmp;
202 fibril_mutex_unlock(&instance_list_mutex);
203 return EOK;
207 fibril_mutex_unlock(&instance_list_mutex);
208 return EINVAL;
211 /** Get root node of filesystem specified by service_id.
213 * @param rfn Output pointer to loaded node
214 * @param service_id Device to load root node from
216 * @return Error code
219 int ext4fs_root_get(fs_node_t **rfn, service_id_t service_id)
221 return ext4fs_node_get(rfn, service_id, EXT4_INODE_ROOT_INDEX);
224 /** Check if specified name (component) matches with any directory entry.
226 * If match is found, load and return matching node.
228 * @param rfn Output pointer to node if operation successful
229 * @param pfn Parent directory node
230 * @param component Name to check directory for
232 * @return Error code
235 int ext4fs_match(fs_node_t **rfn, fs_node_t *pfn, const char *component)
237 ext4fs_node_t *eparent = EXT4FS_NODE(pfn);
238 ext4_filesystem_t *fs = eparent->instance->filesystem;
240 if (!ext4_inode_is_type(fs->superblock, eparent->inode_ref->inode,
241 EXT4_INODE_MODE_DIRECTORY))
242 return ENOTDIR;
244 /* Try to find entry */
245 ext4_directory_search_result_t result;
246 int rc = ext4_directory_find_entry(&result, eparent->inode_ref,
247 component);
248 if (rc != EOK) {
249 if (rc == ENOENT) {
250 *rfn = NULL;
251 return EOK;
254 return rc;
257 /* Load node from search result */
258 uint32_t inode = ext4_directory_entry_ll_get_inode(result.dentry);
259 rc = ext4fs_node_get_core(rfn, eparent->instance, inode);
260 if (rc != EOK)
261 goto exit;
263 exit:
266 /* Destroy search result structure */
267 int const rc2 = ext4_directory_destroy_result(&result);
268 return rc == EOK ? rc2 : rc;
271 /** Get node specified by index
273 * It's wrapper for node_put_core operation
275 * @param rfn Output pointer to loaded node if operation successful
276 * @param service_id Device identifier
277 * @param index Node index (here i-node number)
279 * @return Error code
282 int ext4fs_node_get(fs_node_t **rfn, service_id_t service_id, fs_index_t index)
284 ext4fs_instance_t *inst;
285 int rc = ext4fs_instance_get(service_id, &inst);
286 if (rc != EOK)
287 return rc;
289 return ext4fs_node_get_core(rfn, inst, index);
292 /** Main function for getting node from the filesystem.
294 * @param rfn Output point to loaded node if operation successful
295 * @param inst Instance of filesystem
296 * @param index Index of node (i-node number)
298 * @return Error code
301 int ext4fs_node_get_core(fs_node_t **rfn, ext4fs_instance_t *inst,
302 fs_index_t index)
304 fibril_mutex_lock(&open_nodes_lock);
306 /* Check if the node is not already open */
307 node_key_t key = {
308 .service_id = inst->service_id,
309 .index = index
312 ht_link_t *already_open = hash_table_find(&open_nodes, &key);
313 ext4fs_node_t *enode = NULL;
314 if (already_open) {
315 enode = hash_table_get_inst(already_open, ext4fs_node_t, link);
316 *rfn = enode->fs_node;
317 enode->references++;
319 fibril_mutex_unlock(&open_nodes_lock);
320 return EOK;
323 /* Prepare new enode */
324 enode = malloc(sizeof(ext4fs_node_t));
325 if (enode == NULL) {
326 fibril_mutex_unlock(&open_nodes_lock);
327 return ENOMEM;
330 /* Prepare new fs_node and initialize */
331 fs_node_t *fs_node = malloc(sizeof(fs_node_t));
332 if (fs_node == NULL) {
333 free(enode);
334 fibril_mutex_unlock(&open_nodes_lock);
335 return ENOMEM;
338 fs_node_initialize(fs_node);
340 /* Load i-node from filesystem */
341 ext4_inode_ref_t *inode_ref;
342 int rc = ext4_filesystem_get_inode_ref(inst->filesystem, index,
343 &inode_ref);
344 if (rc != EOK) {
345 free(enode);
346 free(fs_node);
347 fibril_mutex_unlock(&open_nodes_lock);
348 return rc;
351 /* Initialize enode */
352 enode->inode_ref = inode_ref;
353 enode->instance = inst;
354 enode->references = 1;
355 enode->fs_node = fs_node;
357 fs_node->data = enode;
358 *rfn = fs_node;
360 hash_table_insert(&open_nodes, &enode->link);
361 inst->open_nodes_count++;
363 fibril_mutex_unlock(&open_nodes_lock);
365 return EOK;
368 /** Put previously loaded node.
370 * @param enode Node to put back
372 * @return Error code
375 int ext4fs_node_put_core(ext4fs_node_t *enode)
377 hash_table_remove_item(&open_nodes, &enode->link);
378 assert(enode->instance->open_nodes_count > 0);
379 enode->instance->open_nodes_count--;
381 /* Put inode back in filesystem */
382 int rc = ext4_filesystem_put_inode_ref(enode->inode_ref);
383 if (rc != EOK)
384 return rc;
386 /* Destroy data structure */
387 free(enode->fs_node);
388 free(enode);
390 return EOK;
393 /** Open node.
395 * This operation is stateless in this driver.
397 * @param fn Node to open
399 * @return EOK
402 int ext4fs_node_open(fs_node_t *fn)
404 /* Stateless operation */
405 return EOK;
408 /** Put previously loaded node.
410 * A wrapper for node_put_core operation
412 * @param fn Node to put back
413 * @return Error code
416 int ext4fs_node_put(fs_node_t *fn)
418 fibril_mutex_lock(&open_nodes_lock);
420 ext4fs_node_t *enode = EXT4FS_NODE(fn);
421 assert(enode->references > 0);
422 enode->references--;
423 if (enode->references == 0) {
424 int rc = ext4fs_node_put_core(enode);
425 if (rc != EOK) {
426 fibril_mutex_unlock(&open_nodes_lock);
427 return rc;
431 fibril_mutex_unlock(&open_nodes_lock);
433 return EOK;
436 /** Create new node in filesystem.
438 * @param rfn Output pointer to newly created node if successful
439 * @param service_id Device identifier, where the filesystem is
440 * @param flags Flags for specification of new node parameters
442 * @return Error code
445 int ext4fs_create_node(fs_node_t **rfn, service_id_t service_id, int flags)
447 /* Allocate enode */
448 ext4fs_node_t *enode;
449 enode = malloc(sizeof(ext4fs_node_t));
450 if (enode == NULL)
451 return ENOMEM;
453 /* Allocate fs_node */
454 fs_node_t *fs_node;
455 fs_node = malloc(sizeof(fs_node_t));
456 if (fs_node == NULL) {
457 free(enode);
458 return ENOMEM;
461 /* Load instance */
462 ext4fs_instance_t *inst;
463 int rc = ext4fs_instance_get(service_id, &inst);
464 if (rc != EOK) {
465 free(enode);
466 free(fs_node);
467 return rc;
470 /* Allocate new i-node in filesystem */
471 ext4_inode_ref_t *inode_ref;
472 rc = ext4_filesystem_alloc_inode(inst->filesystem, &inode_ref, flags);
473 if (rc != EOK) {
474 free(enode);
475 free(fs_node);
476 return rc;
479 /* Do some interconnections in references */
480 enode->inode_ref = inode_ref;
481 enode->instance = inst;
482 enode->references = 1;
484 fibril_mutex_lock(&open_nodes_lock);
485 hash_table_insert(&open_nodes, &enode->link);
486 fibril_mutex_unlock(&open_nodes_lock);
487 inst->open_nodes_count++;
489 enode->inode_ref->dirty = true;
491 fs_node_initialize(fs_node);
492 fs_node->data = enode;
493 enode->fs_node = fs_node;
494 *rfn = fs_node;
496 return EOK;
499 /** Destroy existing node.
501 * @param fs Node to destroy
503 * @return Error code
506 int ext4fs_destroy_node(fs_node_t *fn)
508 /* If directory, check for children */
509 bool has_children;
510 int rc = ext4fs_has_children(&has_children, fn);
511 if (rc != EOK) {
512 ext4fs_node_put(fn);
513 return rc;
516 if (has_children) {
517 ext4fs_node_put(fn);
518 return EINVAL;
521 ext4fs_node_t *enode = EXT4FS_NODE(fn);
522 ext4_inode_ref_t *inode_ref = enode->inode_ref;
524 /* Release data blocks */
525 rc = ext4_filesystem_truncate_inode(inode_ref, 0);
526 if (rc != EOK) {
527 ext4fs_node_put(fn);
528 return rc;
532 * TODO: Sset real deletion time when it will be supported.
533 * Temporary set fake deletion time.
535 ext4_inode_set_deletion_time(inode_ref->inode, 0xdeadbeef);
536 inode_ref->dirty = true;
538 /* Free inode */
539 rc = ext4_filesystem_free_inode(inode_ref);
540 if (rc != EOK) {
541 ext4fs_node_put(fn);
542 return rc;
545 return ext4fs_node_put(fn);
548 /** Link the specfied node to directory.
550 * @param pfn Parent node to link in
551 * @param cfn Node to be linked
552 * @param name Name which will be assigned to directory entry
554 * @return Error code
557 int ext4fs_link(fs_node_t *pfn, fs_node_t *cfn, const char *name)
559 /* Check maximum name length */
560 if (str_size(name) > EXT4_DIRECTORY_FILENAME_LEN)
561 return ENAMETOOLONG;
563 ext4fs_node_t *parent = EXT4FS_NODE(pfn);
564 ext4fs_node_t *child = EXT4FS_NODE(cfn);
565 ext4_filesystem_t *fs = parent->instance->filesystem;
567 /* Add entry to parent directory */
568 int rc = ext4_directory_add_entry(parent->inode_ref, name,
569 child->inode_ref);
570 if (rc != EOK)
571 return rc;
573 /* Fill new dir -> add '.' and '..' entries */
574 if (ext4_inode_is_type(fs->superblock, child->inode_ref->inode,
575 EXT4_INODE_MODE_DIRECTORY)) {
576 rc = ext4_directory_add_entry(child->inode_ref, ".",
577 child->inode_ref);
578 if (rc != EOK) {
579 ext4_directory_remove_entry(parent->inode_ref, name);
580 return rc;
583 rc = ext4_directory_add_entry(child->inode_ref, "..",
584 parent->inode_ref);
585 if (rc != EOK) {
586 ext4_directory_remove_entry(parent->inode_ref, name);
587 ext4_directory_remove_entry(child->inode_ref, ".");
588 return rc;
591 /* Initialize directory index if supported */
592 if (ext4_superblock_has_feature_compatible(fs->superblock,
593 EXT4_FEATURE_COMPAT_DIR_INDEX)) {
594 rc = ext4_directory_dx_init(child->inode_ref);
595 if (rc != EOK)
596 return rc;
598 ext4_inode_set_flag(child->inode_ref->inode,
599 EXT4_INODE_FLAG_INDEX);
600 child->inode_ref->dirty = true;
603 uint16_t parent_links =
604 ext4_inode_get_links_count(parent->inode_ref->inode);
605 parent_links++;
606 ext4_inode_set_links_count(parent->inode_ref->inode, parent_links);
608 parent->inode_ref->dirty = true;
611 uint16_t child_links =
612 ext4_inode_get_links_count(child->inode_ref->inode);
613 child_links++;
614 ext4_inode_set_links_count(child->inode_ref->inode, child_links);
616 child->inode_ref->dirty = true;
618 return EOK;
621 /** Unlink node from specified directory.
623 * @param pfn Parent node to delete node from
624 * @param cfn Child node to be unlinked from directory
625 * @param name Name of entry that will be removed
627 * @return Error code
630 int ext4fs_unlink(fs_node_t *pfn, fs_node_t *cfn, const char *name)
632 bool has_children;
633 int rc = ext4fs_has_children(&has_children, cfn);
634 if (rc != EOK)
635 return rc;
637 /* Cannot unlink non-empty node */
638 if (has_children)
639 return ENOTEMPTY;
641 /* Remove entry from parent directory */
642 ext4_inode_ref_t *parent = EXT4FS_NODE(pfn)->inode_ref;
643 rc = ext4_directory_remove_entry(parent, name);
644 if (rc != EOK)
645 return rc;
647 /* Decrement links count */
648 ext4_inode_ref_t *child_inode_ref = EXT4FS_NODE(cfn)->inode_ref;
650 uint32_t lnk_count =
651 ext4_inode_get_links_count(child_inode_ref->inode);
652 lnk_count--;
654 /* If directory - handle links from parent */
655 if ((lnk_count <= 1) && (ext4fs_is_directory(cfn))) {
656 assert(lnk_count == 1);
658 lnk_count--;
660 ext4_inode_ref_t *parent_inode_ref = EXT4FS_NODE(pfn)->inode_ref;
662 uint32_t parent_lnk_count = ext4_inode_get_links_count(
663 parent_inode_ref->inode);
665 parent_lnk_count--;
666 ext4_inode_set_links_count(parent_inode_ref->inode, parent_lnk_count);
668 parent->dirty = true;
672 * TODO: Update timestamps of the parent
673 * (when we have wall-clock time).
675 * ext4_inode_set_change_inode_time(parent->inode, (uint32_t) now);
676 * ext4_inode_set_modification_time(parent->inode, (uint32_t) now);
677 * parent->dirty = true;
681 * TODO: Update timestamp for inode.
683 * ext4_inode_set_change_inode_time(child_inode_ref->inode,
684 * (uint32_t) now);
687 ext4_inode_set_links_count(child_inode_ref->inode, lnk_count);
688 child_inode_ref->dirty = true;
690 return EOK;
693 /** Check if specified node has children.
695 * For files is response allways false and check is executed only for directories.
697 * @param has_children Output value for response
698 * @param fn Node to check
700 * @return Error code
703 int ext4fs_has_children(bool *has_children, fs_node_t *fn)
705 ext4fs_node_t *enode = EXT4FS_NODE(fn);
706 ext4_filesystem_t *fs = enode->instance->filesystem;
708 /* Check if node is directory */
709 if (!ext4_inode_is_type(fs->superblock, enode->inode_ref->inode,
710 EXT4_INODE_MODE_DIRECTORY)) {
711 *has_children = false;
712 return EOK;
715 ext4_directory_iterator_t it;
716 int rc = ext4_directory_iterator_init(&it, enode->inode_ref, 0);
717 if (rc != EOK)
718 return rc;
720 /* Find a non-empty directory entry */
721 bool found = false;
722 while (it.current != NULL) {
723 if (it.current->inode != 0) {
724 uint16_t name_size =
725 ext4_directory_entry_ll_get_name_length(fs->superblock,
726 it.current);
727 if (!ext4fs_is_dots(it.current->name, name_size)) {
728 found = true;
729 break;
733 rc = ext4_directory_iterator_next(&it);
734 if (rc != EOK) {
735 ext4_directory_iterator_fini(&it);
736 return rc;
740 rc = ext4_directory_iterator_fini(&it);
741 if (rc != EOK)
742 return rc;
744 *has_children = found;
746 return EOK;
749 /** Unpack index number from node.
751 * @param fn Node to load index from
753 * @return Index number of i-node
756 fs_index_t ext4fs_index_get(fs_node_t *fn)
758 ext4fs_node_t *enode = EXT4FS_NODE(fn);
759 return enode->inode_ref->index;
762 /** Get real size of file / directory.
764 * @param fn Node to get size of
766 * @return Real size of node
769 aoff64_t ext4fs_size_get(fs_node_t *fn)
771 ext4fs_node_t *enode = EXT4FS_NODE(fn);
772 ext4_superblock_t *sb = enode->instance->filesystem->superblock;
773 return ext4_inode_get_size(sb, enode->inode_ref->inode);
776 /** Get number of links to specified node.
778 * @param fn Node to get links to
780 * @return Number of links
783 unsigned ext4fs_lnkcnt_get(fs_node_t *fn)
785 ext4fs_node_t *enode = EXT4FS_NODE(fn);
786 uint32_t lnkcnt = ext4_inode_get_links_count(enode->inode_ref->inode);
788 if (ext4fs_is_directory(fn)) {
789 if (lnkcnt > 1)
790 return 1;
791 else
792 return 0;
795 /* For regular files return real links count */
796 return lnkcnt;
799 /** Check if node is directory.
801 * @param fn Node to check
803 * @return Result of check
806 bool ext4fs_is_directory(fs_node_t *fn)
808 ext4fs_node_t *enode = EXT4FS_NODE(fn);
809 ext4_superblock_t *sb = enode->instance->filesystem->superblock;
811 return ext4_inode_is_type(sb, enode->inode_ref->inode,
812 EXT4_INODE_MODE_DIRECTORY);
815 /** Check if node is regular file.
817 * @param fn Node to check
819 * @return Result of check
822 bool ext4fs_is_file(fs_node_t *fn)
824 ext4fs_node_t *enode = EXT4FS_NODE(fn);
825 ext4_superblock_t *sb = enode->instance->filesystem->superblock;
827 return ext4_inode_is_type(sb, enode->inode_ref->inode,
828 EXT4_INODE_MODE_FILE);
831 /** Extract device identifier from node.
833 * @param node Node to extract id from
835 * @return id of device, where is the filesystem
838 service_id_t ext4fs_service_get(fs_node_t *fn)
840 ext4fs_node_t *enode = EXT4FS_NODE(fn);
841 return enode->instance->service_id;
844 int ext4fs_size_block(service_id_t service_id, uint32_t *size)
846 ext4fs_instance_t *inst;
847 int rc = ext4fs_instance_get(service_id, &inst);
848 if (rc != EOK)
849 return rc;
851 if (NULL == inst)
852 return ENOENT;
854 ext4_superblock_t *sb = inst->filesystem->superblock;
855 *size = ext4_superblock_get_block_size(sb);
857 return EOK;
860 int ext4fs_total_block_count(service_id_t service_id, uint64_t *count)
862 ext4fs_instance_t *inst;
863 int rc = ext4fs_instance_get(service_id, &inst);
864 if (rc != EOK)
865 return rc;
867 if (NULL == inst)
868 return ENOENT;
870 ext4_superblock_t *sb = inst->filesystem->superblock;
871 *count = ext4_superblock_get_blocks_count(sb);
873 return EOK;
876 int ext4fs_free_block_count(service_id_t service_id, uint64_t *count)
878 ext4fs_instance_t *inst;
879 int rc = ext4fs_instance_get(service_id, &inst);
880 if (rc != EOK)
881 return rc;
883 ext4_superblock_t *sb = inst->filesystem->superblock;
884 *count = ext4_superblock_get_free_blocks_count(sb);
886 return EOK;
890 * libfs operations.
892 libfs_ops_t ext4fs_libfs_ops = {
893 .root_get = ext4fs_root_get,
894 .match = ext4fs_match,
895 .node_get = ext4fs_node_get,
896 .node_open = ext4fs_node_open,
897 .node_put = ext4fs_node_put,
898 .create = ext4fs_create_node,
899 .destroy = ext4fs_destroy_node,
900 .link = ext4fs_link,
901 .unlink = ext4fs_unlink,
902 .has_children = ext4fs_has_children,
903 .index_get = ext4fs_index_get,
904 .size_get = ext4fs_size_get,
905 .lnkcnt_get = ext4fs_lnkcnt_get,
906 .is_directory = ext4fs_is_directory,
907 .is_file = ext4fs_is_file,
908 .service_get = ext4fs_service_get,
909 .size_block = ext4fs_size_block,
910 .total_block_count = ext4fs_total_block_count,
911 .free_block_count = ext4fs_free_block_count
915 * VFS operations.
918 /** Mount operation.
920 * Try to mount specified filesystem from device.
922 * @param service_id Identifier of device
923 * @param opts Mount options
924 * @param index Output value - index of root node
925 * @param size Output value - size of root node
926 * @param lnkcnt Output value - link count of root node
928 * @return Error code
931 static int ext4fs_mounted(service_id_t service_id, const char *opts,
932 fs_index_t *index, aoff64_t *size, unsigned *lnkcnt)
934 /* Allocate libext4 filesystem structure */
935 ext4_filesystem_t *fs = (ext4_filesystem_t *)
936 malloc(sizeof(ext4_filesystem_t));
937 if (fs == NULL)
938 return ENOMEM;
940 /* Allocate instance structure */
941 ext4fs_instance_t *inst = (ext4fs_instance_t *)
942 malloc(sizeof(ext4fs_instance_t));
943 if (inst == NULL) {
944 free(fs);
945 return ENOMEM;
948 enum cache_mode cmode;
949 if (str_cmp(opts, "wtcache") == 0)
950 cmode = CACHE_MODE_WT;
951 else
952 cmode = CACHE_MODE_WB;
954 /* Initialize the filesystem */
955 int rc = ext4_filesystem_init(fs, service_id, cmode);
956 if (rc != EOK) {
957 free(fs);
958 free(inst);
959 return rc;
962 /* Do some sanity checking */
963 rc = ext4_filesystem_check_sanity(fs);
964 if (rc != EOK) {
965 ext4_filesystem_fini(fs);
966 free(fs);
967 free(inst);
968 return rc;
971 /* Check flags */
972 bool read_only;
973 rc = ext4_filesystem_check_features(fs, &read_only);
974 if (rc != EOK) {
975 ext4_filesystem_fini(fs);
976 free(fs);
977 free(inst);
978 return rc;
981 /* Initialize instance */
982 link_initialize(&inst->link);
983 inst->service_id = service_id;
984 inst->filesystem = fs;
985 inst->open_nodes_count = 0;
987 /* Read root node */
988 fs_node_t *root_node;
989 rc = ext4fs_node_get_core(&root_node, inst, EXT4_INODE_ROOT_INDEX);
990 if (rc != EOK) {
991 ext4_filesystem_fini(fs);
992 free(fs);
993 free(inst);
994 return rc;
997 /* Add instance to the list */
998 fibril_mutex_lock(&instance_list_mutex);
999 list_append(&inst->link, &instance_list);
1000 fibril_mutex_unlock(&instance_list_mutex);
1002 ext4fs_node_t *enode = EXT4FS_NODE(root_node);
1004 *index = EXT4_INODE_ROOT_INDEX;
1005 *size = ext4_inode_get_size(fs->superblock, enode->inode_ref->inode);
1006 *lnkcnt = 1;
1008 return ext4fs_node_put(root_node);
1011 /** Unmount operation.
1013 * Correctly release the filesystem.
1015 * @param service_id Device to be unmounted
1017 * @return Error code
1020 static int ext4fs_unmounted(service_id_t service_id)
1022 ext4fs_instance_t *inst;
1023 int rc = ext4fs_instance_get(service_id, &inst);
1024 if (rc != EOK)
1025 return rc;
1027 fibril_mutex_lock(&open_nodes_lock);
1029 if (inst->open_nodes_count != 0) {
1030 fibril_mutex_unlock(&open_nodes_lock);
1031 return EBUSY;
1034 /* Remove the instance from the list */
1035 fibril_mutex_lock(&instance_list_mutex);
1036 list_remove(&inst->link);
1037 fibril_mutex_unlock(&instance_list_mutex);
1039 fibril_mutex_unlock(&open_nodes_lock);
1041 return ext4_filesystem_fini(inst->filesystem);
1044 /** Read bytes from node.
1046 * @param service_id Device to read data from
1047 * @param index Number of node to read from
1048 * @param pos Position where the read should be started
1049 * @param rbytes Output value, where the real size was returned
1051 * @return Error code
1054 static int ext4fs_read(service_id_t service_id, fs_index_t index, aoff64_t pos,
1055 size_t *rbytes)
1058 * Receive the read request.
1060 ipc_callid_t callid;
1061 size_t size;
1062 if (!async_data_read_receive(&callid, &size)) {
1063 async_answer_0(callid, EINVAL);
1064 return EINVAL;
1067 ext4fs_instance_t *inst;
1068 int rc = ext4fs_instance_get(service_id, &inst);
1069 if (rc != EOK) {
1070 async_answer_0(callid, rc);
1071 return rc;
1074 /* Load i-node */
1075 ext4_inode_ref_t *inode_ref;
1076 rc = ext4_filesystem_get_inode_ref(inst->filesystem, index, &inode_ref);
1077 if (rc != EOK) {
1078 async_answer_0(callid, rc);
1079 return rc;
1082 /* Read from i-node by type */
1083 if (ext4_inode_is_type(inst->filesystem->superblock, inode_ref->inode,
1084 EXT4_INODE_MODE_FILE)) {
1085 rc = ext4fs_read_file(callid, pos, size, inst, inode_ref,
1086 rbytes);
1087 } else if (ext4_inode_is_type(inst->filesystem->superblock,
1088 inode_ref->inode, EXT4_INODE_MODE_DIRECTORY)) {
1089 rc = ext4fs_read_directory(callid, pos, size, inst, inode_ref,
1090 rbytes);
1091 } else {
1092 /* Other inode types not supported */
1093 async_answer_0(callid, ENOTSUP);
1094 rc = ENOTSUP;
1097 int const rc2 = ext4_filesystem_put_inode_ref(inode_ref);
1099 return rc == EOK ? rc2 : rc;
1102 /** Check if filename is dot or dotdot (reserved names).
1104 * @param name Name to check
1105 * @param name_size Length of string name
1107 * @return Result of the check
1110 bool ext4fs_is_dots(const uint8_t *name, size_t name_size)
1112 if ((name_size == 1) && (name[0] == '.'))
1113 return true;
1115 if ((name_size == 2) && (name[0] == '.') && (name[1] == '.'))
1116 return true;
1118 return false;
1121 /** Read data from directory.
1123 * @param callid IPC id of call (for communication)
1124 * @param pos Position to start reading from
1125 * @param size How many bytes to read
1126 * @param inst Filesystem instance
1127 * @param inode_ref Node to read data from
1128 * @param rbytes Output value to return real number of bytes was read
1130 * @return Error code
1133 int ext4fs_read_directory(ipc_callid_t callid, aoff64_t pos, size_t size,
1134 ext4fs_instance_t *inst, ext4_inode_ref_t *inode_ref, size_t *rbytes)
1136 ext4_directory_iterator_t it;
1137 int rc = ext4_directory_iterator_init(&it, inode_ref, pos);
1138 if (rc != EOK) {
1139 async_answer_0(callid, rc);
1140 return rc;
1144 * Find next interesting directory entry.
1145 * We want to skip . and .. entries
1146 * as these are not used in HelenOS
1148 bool found = false;
1149 while (it.current != NULL) {
1150 if (it.current->inode == 0)
1151 goto skip;
1153 uint16_t name_size = ext4_directory_entry_ll_get_name_length(
1154 inst->filesystem->superblock, it.current);
1156 /* Skip . and .. */
1157 if (ext4fs_is_dots(it.current->name, name_size))
1158 goto skip;
1161 * The on-disk entry does not contain \0 at the end
1162 * end of entry name, so we copy it to new buffer
1163 * and add the \0 at the end
1165 uint8_t *buf = malloc(name_size + 1);
1166 if (buf == NULL) {
1167 ext4_directory_iterator_fini(&it);
1168 async_answer_0(callid, ENOMEM);
1169 return ENOMEM;
1172 memcpy(buf, &it.current->name, name_size);
1173 *(buf + name_size) = 0;
1174 found = true;
1176 (void) async_data_read_finalize(callid, buf, name_size + 1);
1177 free(buf);
1178 break;
1180 skip:
1181 rc = ext4_directory_iterator_next(&it);
1182 if (rc != EOK) {
1183 ext4_directory_iterator_fini(&it);
1184 async_answer_0(callid, rc);
1185 return rc;
1189 uint64_t next;
1190 if (found) {
1191 rc = ext4_directory_iterator_next(&it);
1192 if (rc != EOK)
1193 return rc;
1195 next = it.current_offset;
1198 rc = ext4_directory_iterator_fini(&it);
1199 if (rc != EOK)
1200 return rc;
1202 /* Prepare return values */
1203 if (found) {
1204 *rbytes = next - pos;
1205 return EOK;
1206 } else {
1207 async_answer_0(callid, ENOENT);
1208 return ENOENT;
1212 /** Read data from file.
1214 * @param callid IPC id of call (for communication)
1215 * @param pos Position to start reading from
1216 * @param size How many bytes to read
1217 * @param inst Filesystem instance
1218 * @param inode_ref Node to read data from
1219 * @param rbytes Output value to return real number of bytes was read
1221 * @return Error code
1224 int ext4fs_read_file(ipc_callid_t callid, aoff64_t pos, size_t size,
1225 ext4fs_instance_t *inst, ext4_inode_ref_t *inode_ref, size_t *rbytes)
1227 ext4_superblock_t *sb = inst->filesystem->superblock;
1228 uint64_t file_size = ext4_inode_get_size(sb, inode_ref->inode);
1230 if (pos >= file_size) {
1231 /* Read 0 bytes successfully */
1232 async_data_read_finalize(callid, NULL, 0);
1233 *rbytes = 0;
1234 return EOK;
1237 /* For now, we only read data from one block at a time */
1238 uint32_t block_size = ext4_superblock_get_block_size(sb);
1239 aoff64_t file_block = pos / block_size;
1240 uint32_t offset_in_block = pos % block_size;
1241 uint32_t bytes = min(block_size - offset_in_block, size);
1243 /* Handle end of file */
1244 if (pos + bytes > file_size)
1245 bytes = file_size - pos;
1247 /* Get the real block number */
1248 uint32_t fs_block;
1249 int rc = ext4_filesystem_get_inode_data_block_index(inode_ref,
1250 file_block, &fs_block);
1251 if (rc != EOK) {
1252 async_answer_0(callid, rc);
1253 return rc;
1257 * Check for sparse file.
1258 * If ext4_filesystem_get_inode_data_block_index returned
1259 * fs_block == 0, it means that the given block is not allocated for the
1260 * file and we need to return a buffer of zeros
1262 uint8_t *buffer;
1263 if (fs_block == 0) {
1264 buffer = malloc(bytes);
1265 if (buffer == NULL) {
1266 async_answer_0(callid, ENOMEM);
1267 return ENOMEM;
1270 memset(buffer, 0, bytes);
1272 rc = async_data_read_finalize(callid, buffer, bytes);
1273 *rbytes = bytes;
1275 free(buffer);
1276 return rc;
1279 /* Usual case - we need to read a block from device */
1280 block_t *block;
1281 rc = block_get(&block, inst->service_id, fs_block, BLOCK_FLAGS_NONE);
1282 if (rc != EOK) {
1283 async_answer_0(callid, rc);
1284 return rc;
1287 assert(offset_in_block + bytes <= block_size);
1288 rc = async_data_read_finalize(callid, block->data + offset_in_block, bytes);
1289 if (rc != EOK) {
1290 block_put(block);
1291 return rc;
1294 rc = block_put(block);
1295 if (rc != EOK)
1296 return rc;
1298 *rbytes = bytes;
1299 return EOK;
1302 /** Write bytes to file
1304 * @param service_id Device identifier
1305 * @param index I-node number of file
1306 * @param pos Position in file to start reading from
1307 * @param wbytes Output value - real number of written bytes
1308 * @param nsize Output value - new size of i-node
1310 * @return Error code
1313 static int ext4fs_write(service_id_t service_id, fs_index_t index, aoff64_t pos,
1314 size_t *wbytes, aoff64_t *nsize)
1316 fs_node_t *fn;
1317 int rc = ext4fs_node_get(&fn, service_id, index);
1318 if (rc != EOK)
1319 return rc;
1321 ipc_callid_t callid;
1322 size_t len;
1323 if (!async_data_write_receive(&callid, &len)) {
1324 rc = EINVAL;
1325 async_answer_0(callid, rc);
1326 goto exit;
1329 ext4fs_node_t *enode = EXT4FS_NODE(fn);
1330 ext4_filesystem_t *fs = enode->instance->filesystem;
1332 uint32_t block_size = ext4_superblock_get_block_size(fs->superblock);
1334 /* Prevent writing to more than one block */
1335 uint32_t bytes = min(len, block_size - (pos % block_size));
1337 int flags = BLOCK_FLAGS_NONE;
1338 if (bytes == block_size)
1339 flags = BLOCK_FLAGS_NOREAD;
1341 uint32_t iblock = pos / block_size;
1342 uint32_t fblock;
1344 /* Load inode */
1345 ext4_inode_ref_t *inode_ref = enode->inode_ref;
1346 rc = ext4_filesystem_get_inode_data_block_index(inode_ref, iblock,
1347 &fblock);
1348 if (rc != EOK) {
1349 async_answer_0(callid, rc);
1350 goto exit;
1353 /* Check for sparse file */
1354 if (fblock == 0) {
1355 if ((ext4_superblock_has_feature_incompatible(fs->superblock,
1356 EXT4_FEATURE_INCOMPAT_EXTENTS)) &&
1357 (ext4_inode_has_flag(inode_ref->inode, EXT4_INODE_FLAG_EXTENTS))) {
1358 uint32_t last_iblock =
1359 ext4_inode_get_size(fs->superblock, inode_ref->inode) /
1360 block_size;
1362 while (last_iblock < iblock) {
1363 rc = ext4_extent_append_block(inode_ref, &last_iblock,
1364 &fblock, true);
1365 if (rc != EOK) {
1366 async_answer_0(callid, rc);
1367 goto exit;
1371 rc = ext4_extent_append_block(inode_ref, &last_iblock,
1372 &fblock, false);
1373 if (rc != EOK) {
1374 async_answer_0(callid, rc);
1375 goto exit;
1377 } else {
1378 rc = ext4_balloc_alloc_block(inode_ref, &fblock);
1379 if (rc != EOK) {
1380 async_answer_0(callid, rc);
1381 goto exit;
1384 rc = ext4_filesystem_set_inode_data_block_index(inode_ref,
1385 iblock, fblock);
1386 if (rc != EOK) {
1387 ext4_balloc_free_block(inode_ref, fblock);
1388 async_answer_0(callid, rc);
1389 goto exit;
1393 flags = BLOCK_FLAGS_NOREAD;
1394 inode_ref->dirty = true;
1397 /* Load target block */
1398 block_t *write_block;
1399 rc = block_get(&write_block, service_id, fblock, flags);
1400 if (rc != EOK) {
1401 async_answer_0(callid, rc);
1402 goto exit;
1405 if (flags == BLOCK_FLAGS_NOREAD)
1406 memset(write_block->data, 0, block_size);
1408 rc = async_data_write_finalize(callid, write_block->data +
1409 (pos % block_size), bytes);
1410 if (rc != EOK)
1411 goto exit;
1413 write_block->dirty = true;
1415 rc = block_put(write_block);
1416 if (rc != EOK)
1417 goto exit;
1419 /* Do some counting */
1420 uint32_t old_inode_size = ext4_inode_get_size(fs->superblock,
1421 inode_ref->inode);
1422 if (pos + bytes > old_inode_size) {
1423 ext4_inode_set_size(inode_ref->inode, pos + bytes);
1424 inode_ref->dirty = true;
1427 *nsize = ext4_inode_get_size(fs->superblock, inode_ref->inode);
1428 *wbytes = bytes;
1430 exit:
1433 int const rc2 = ext4fs_node_put(fn);
1434 return rc == EOK ? rc2 : rc;
1437 /** Truncate file.
1439 * Only the direction to shorter file is supported.
1441 * @param service_id Device identifier
1442 * @param index Index if node to truncated
1443 * @param new_size New size of file
1445 * @return Error code
1448 static int ext4fs_truncate(service_id_t service_id, fs_index_t index,
1449 aoff64_t new_size)
1451 fs_node_t *fn;
1452 int rc = ext4fs_node_get(&fn, service_id, index);
1453 if (rc != EOK)
1454 return rc;
1456 ext4fs_node_t *enode = EXT4FS_NODE(fn);
1457 ext4_inode_ref_t *inode_ref = enode->inode_ref;
1459 rc = ext4_filesystem_truncate_inode(inode_ref, new_size);
1460 int const rc2 = ext4fs_node_put(fn);
1462 return rc == EOK ? rc2 : rc;
1465 /** Close file.
1467 * @param service_id Device identifier
1468 * @param index I-node number
1470 * @return Error code
1473 static int ext4fs_close(service_id_t service_id, fs_index_t index)
1475 return EOK;
1478 /** Destroy node specified by index.
1480 * @param service_id Device identifier
1481 * @param index I-node to destroy
1483 * @return Error code
1486 static int ext4fs_destroy(service_id_t service_id, fs_index_t index)
1488 fs_node_t *fn;
1489 int rc = ext4fs_node_get(&fn, service_id, index);
1490 if (rc != EOK)
1491 return rc;
1493 /* Destroy the inode */
1494 return ext4fs_destroy_node(fn);
1497 /** Enforce inode synchronization (write) to device.
1499 * @param service_id Device identifier
1500 * @param index I-node number.
1503 static int ext4fs_sync(service_id_t service_id, fs_index_t index)
1505 fs_node_t *fn;
1506 int rc = ext4fs_node_get(&fn, service_id, index);
1507 if (rc != EOK)
1508 return rc;
1510 ext4fs_node_t *enode = EXT4FS_NODE(fn);
1511 enode->inode_ref->dirty = true;
1513 return ext4fs_node_put(fn);
1516 /** VFS operations
1519 vfs_out_ops_t ext4fs_ops = {
1520 .mounted = ext4fs_mounted,
1521 .unmounted = ext4fs_unmounted,
1522 .read = ext4fs_read,
1523 .write = ext4fs_write,
1524 .truncate = ext4fs_truncate,
1525 .close = ext4fs_close,
1526 .destroy = ext4fs_destroy,
1527 .sync = ext4fs_sync
1531 * @}