source: mainline/uspace/srv/fs/ext4fs/ext4fs_ops.c@ d2663d8

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since d2663d8 was cc8044e, checked in by Maurizio Lombardi <m.lombardi85@…>, 12 years ago

There is no need to check wether the inst pointer is NULL or not

  • Property mode set to 100644
File size: 36.0 KB
Line 
1/*
2 * Copyright (c) 2011 Martin Sucha
3 * Copyright (c) 2012 Frantisek Princ
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
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.
17 *
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.
28 */
29
30/** @addtogroup fs
31 * @{
32 */
33/**
34 * @file ext4fs_ops.c
35 * @brief VFS operations for ext4 filesystem.
36 */
37
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"
49
50#define EXT4FS_NODE(node) \
51 ((node) ? (ext4fs_node_t *) (node)->data : NULL)
52
53/**
54 * Type for holding an instance of mounted partition.
55 */
56typedef 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;
62
63/**
64 * Type for wrapping common fs_node and add some useful pointers.
65 */
66typedef 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;
73
74/* Forward declarations of auxiliary functions */
75
76static int ext4fs_read_directory(ipc_callid_t, aoff64_t, size_t,
77 ext4fs_instance_t *, ext4_inode_ref_t *, size_t *);
78static int ext4fs_read_file(ipc_callid_t, aoff64_t, size_t, ext4fs_instance_t *,
79 ext4_inode_ref_t *, size_t *);
80static bool ext4fs_is_dots(const uint8_t *, size_t);
81static int ext4fs_instance_get(service_id_t, ext4fs_instance_t **);
82static int ext4fs_node_get_core(fs_node_t **, ext4fs_instance_t *, fs_index_t);
83static int ext4fs_node_put_core(ext4fs_node_t *);
84
85/* Forward declarations of ext4 libfs operations. */
86
87static int ext4fs_root_get(fs_node_t **, service_id_t);
88static int ext4fs_match(fs_node_t **, fs_node_t *, const char *);
89static int ext4fs_node_get(fs_node_t **, service_id_t, fs_index_t);
90static int ext4fs_node_open(fs_node_t *);
91static int ext4fs_node_put(fs_node_t *);
92static int ext4fs_create_node(fs_node_t **, service_id_t, int);
93static int ext4fs_destroy_node(fs_node_t *);
94static int ext4fs_link(fs_node_t *, fs_node_t *, const char *);
95static int ext4fs_unlink(fs_node_t *, fs_node_t *, const char *);
96static int ext4fs_has_children(bool *, fs_node_t *);
97static fs_index_t ext4fs_index_get(fs_node_t *);
98static aoff64_t ext4fs_size_get(fs_node_t *);
99static unsigned ext4fs_lnkcnt_get(fs_node_t *);
100static bool ext4fs_is_directory(fs_node_t *);
101static bool ext4fs_is_file(fs_node_t *node);
102static service_id_t ext4fs_service_get(fs_node_t *node);
103static int ext4fs_size_block(service_id_t, uint32_t *);
104static int ext4fs_total_block_count(service_id_t, uint64_t *);
105static int ext4fs_free_block_count(service_id_t, uint64_t *);
106
107/* Static variables */
108
109static LIST_INITIALIZE(instance_list);
110static FIBRIL_MUTEX_INITIALIZE(instance_list_mutex);
111static hash_table_t open_nodes;
112static FIBRIL_MUTEX_INITIALIZE(open_nodes_lock);
113
114/* Hash table interface for open nodes hash table */
115
116typedef struct {
117 service_id_t service_id;
118 fs_index_t index;
119} node_key_t;
120
121static size_t open_nodes_key_hash(void *key_arg)
122{
123 node_key_t *key = (node_key_t *)key_arg;
124 return hash_combine(key->service_id, key->index);
125}
126
127static size_t open_nodes_hash(const ht_link_t *item)
128{
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);
131}
132
133static bool open_nodes_key_equal(void *key_arg, const ht_link_t *item)
134{
135 node_key_t *key = (node_key_t *)key_arg;
136 ext4fs_node_t *enode = hash_table_get_inst(item, ext4fs_node_t, link);
137
138 return key->service_id == enode->instance->service_id
139 && key->index == enode->inode_ref->index;
140}
141
142static 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,
148};
149
150/** Basic initialization of the driver.
151 *
152 * This is only needed to create the hash table
153 * for storing open nodes.
154 *
155 * @return Error code
156 *
157 */
158int ext4fs_global_init(void)
159{
160 if (!hash_table_create(&open_nodes, 0, 0, &open_nodes_ops))
161 return ENOMEM;
162
163 return EOK;
164}
165
166/* Finalization of the driver.
167 *
168 * This is only needed to destroy the hash table.
169 *
170 * @return Error code
171 */
172int ext4fs_global_fini(void)
173{
174 hash_table_destroy(&open_nodes);
175 return EOK;
176}
177
178/*
179 * Ext4 libfs operations.
180 */
181
182/** Get instance from internal table by service_id.
183 *
184 * @param service_id Device identifier
185 * @param inst Output instance if successful operation
186 *
187 * @return Error code
188 *
189 */
190int ext4fs_instance_get(service_id_t service_id, ext4fs_instance_t **inst)
191{
192 fibril_mutex_lock(&instance_list_mutex);
193
194 if (list_empty(&instance_list)) {
195 fibril_mutex_unlock(&instance_list_mutex);
196 return EINVAL;
197 }
198
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;
204 }
205 }
206
207 fibril_mutex_unlock(&instance_list_mutex);
208 return EINVAL;
209}
210
211/** Get root node of filesystem specified by service_id.
212 *
213 * @param rfn Output pointer to loaded node
214 * @param service_id Device to load root node from
215 *
216 * @return Error code
217 *
218 */
219int ext4fs_root_get(fs_node_t **rfn, service_id_t service_id)
220{
221 return ext4fs_node_get(rfn, service_id, EXT4_INODE_ROOT_INDEX);
222}
223
224/** Check if specified name (component) matches with any directory entry.
225 *
226 * If match is found, load and return matching node.
227 *
228 * @param rfn Output pointer to node if operation successful
229 * @param pfn Parent directory node
230 * @param component Name to check directory for
231 *
232 * @return Error code
233 *
234 */
235int ext4fs_match(fs_node_t **rfn, fs_node_t *pfn, const char *component)
236{
237 ext4fs_node_t *eparent = EXT4FS_NODE(pfn);
238 ext4_filesystem_t *fs = eparent->instance->filesystem;
239
240 if (!ext4_inode_is_type(fs->superblock, eparent->inode_ref->inode,
241 EXT4_INODE_MODE_DIRECTORY))
242 return ENOTDIR;
243
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;
252 }
253
254 return rc;
255 }
256
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 return rc;
262
263 /* Destroy search result structure */
264 return ext4_directory_destroy_result(&result);
265}
266
267/** Get node specified by index
268 *
269 * It's wrapper for node_put_core operation
270 *
271 * @param rfn Output pointer to loaded node if operation successful
272 * @param service_id Device identifier
273 * @param index Node index (here i-node number)
274 *
275 * @return Error code
276 *
277 */
278int ext4fs_node_get(fs_node_t **rfn, service_id_t service_id, fs_index_t index)
279{
280 ext4fs_instance_t *inst;
281 int rc = ext4fs_instance_get(service_id, &inst);
282 if (rc != EOK)
283 return rc;
284
285 return ext4fs_node_get_core(rfn, inst, index);
286}
287
288/** Main function for getting node from the filesystem.
289 *
290 * @param rfn Output point to loaded node if operation successful
291 * @param inst Instance of filesystem
292 * @param index Index of node (i-node number)
293 *
294 * @return Error code
295 *
296 */
297int ext4fs_node_get_core(fs_node_t **rfn, ext4fs_instance_t *inst,
298 fs_index_t index)
299{
300 fibril_mutex_lock(&open_nodes_lock);
301
302 /* Check if the node is not already open */
303 node_key_t key = {
304 .service_id = inst->service_id,
305 .index = index
306 };
307
308 ht_link_t *already_open = hash_table_find(&open_nodes, &key);
309 ext4fs_node_t *enode = NULL;
310 if (already_open) {
311 enode = hash_table_get_inst(already_open, ext4fs_node_t, link);
312 *rfn = enode->fs_node;
313 enode->references++;
314
315 fibril_mutex_unlock(&open_nodes_lock);
316 return EOK;
317 }
318
319 /* Prepare new enode */
320 enode = malloc(sizeof(ext4fs_node_t));
321 if (enode == NULL) {
322 fibril_mutex_unlock(&open_nodes_lock);
323 return ENOMEM;
324 }
325
326 /* Prepare new fs_node and initialize */
327 fs_node_t *fs_node = malloc(sizeof(fs_node_t));
328 if (fs_node == NULL) {
329 free(enode);
330 fibril_mutex_unlock(&open_nodes_lock);
331 return ENOMEM;
332 }
333
334 fs_node_initialize(fs_node);
335
336 /* Load i-node from filesystem */
337 ext4_inode_ref_t *inode_ref;
338 int rc = ext4_filesystem_get_inode_ref(inst->filesystem, index,
339 &inode_ref);
340 if (rc != EOK) {
341 free(enode);
342 free(fs_node);
343 fibril_mutex_unlock(&open_nodes_lock);
344 return rc;
345 }
346
347 /* Initialize enode */
348 enode->inode_ref = inode_ref;
349 enode->instance = inst;
350 enode->references = 1;
351 enode->fs_node = fs_node;
352
353 fs_node->data = enode;
354 *rfn = fs_node;
355
356 hash_table_insert(&open_nodes, &enode->link);
357 inst->open_nodes_count++;
358
359 fibril_mutex_unlock(&open_nodes_lock);
360
361 return EOK;
362}
363
364/** Put previously loaded node.
365 *
366 * @param enode Node to put back
367 *
368 * @return Error code
369 *
370 */
371int ext4fs_node_put_core(ext4fs_node_t *enode)
372{
373 hash_table_remove_item(&open_nodes, &enode->link);
374 assert(enode->instance->open_nodes_count > 0);
375 enode->instance->open_nodes_count--;
376
377 /* Put inode back in filesystem */
378 int rc = ext4_filesystem_put_inode_ref(enode->inode_ref);
379 if (rc != EOK)
380 return rc;
381
382 /* Destroy data structure */
383 free(enode->fs_node);
384 free(enode);
385
386 return EOK;
387}
388
389/** Open node.
390 *
391 * This operation is stateless in this driver.
392 *
393 * @param fn Node to open
394 *
395 * @return EOK
396 *
397 */
398int ext4fs_node_open(fs_node_t *fn)
399{
400 /* Stateless operation */
401 return EOK;
402}
403
404/** Put previously loaded node.
405 *
406 * A wrapper for node_put_core operation
407 *
408 * @param fn Node to put back
409 * @return Error code
410 *
411 */
412int ext4fs_node_put(fs_node_t *fn)
413{
414 fibril_mutex_lock(&open_nodes_lock);
415
416 ext4fs_node_t *enode = EXT4FS_NODE(fn);
417 assert(enode->references > 0);
418 enode->references--;
419 if (enode->references == 0) {
420 int rc = ext4fs_node_put_core(enode);
421 if (rc != EOK) {
422 fibril_mutex_unlock(&open_nodes_lock);
423 return rc;
424 }
425 }
426
427 fibril_mutex_unlock(&open_nodes_lock);
428
429 return EOK;
430}
431
432/** Create new node in filesystem.
433 *
434 * @param rfn Output pointer to newly created node if successful
435 * @param service_id Device identifier, where the filesystem is
436 * @param flags Flags for specification of new node parameters
437 *
438 * @return Error code
439 *
440 */
441int ext4fs_create_node(fs_node_t **rfn, service_id_t service_id, int flags)
442{
443 /* Allocate enode */
444 ext4fs_node_t *enode;
445 enode = malloc(sizeof(ext4fs_node_t));
446 if (enode == NULL)
447 return ENOMEM;
448
449 /* Allocate fs_node */
450 fs_node_t *fs_node;
451 fs_node = malloc(sizeof(fs_node_t));
452 if (fs_node == NULL) {
453 free(enode);
454 return ENOMEM;
455 }
456
457 /* Load instance */
458 ext4fs_instance_t *inst;
459 int rc = ext4fs_instance_get(service_id, &inst);
460 if (rc != EOK) {
461 free(enode);
462 free(fs_node);
463 return rc;
464 }
465
466 /* Allocate new i-node in filesystem */
467 ext4_inode_ref_t *inode_ref;
468 rc = ext4_filesystem_alloc_inode(inst->filesystem, &inode_ref, flags);
469 if (rc != EOK) {
470 free(enode);
471 free(fs_node);
472 return rc;
473 }
474
475 /* Do some interconnections in references */
476 enode->inode_ref = inode_ref;
477 enode->instance = inst;
478 enode->references = 1;
479
480 fibril_mutex_lock(&open_nodes_lock);
481 hash_table_insert(&open_nodes, &enode->link);
482 fibril_mutex_unlock(&open_nodes_lock);
483 inst->open_nodes_count++;
484
485 enode->inode_ref->dirty = true;
486
487 fs_node_initialize(fs_node);
488 fs_node->data = enode;
489 enode->fs_node = fs_node;
490 *rfn = fs_node;
491
492 return EOK;
493}
494
495/** Destroy existing node.
496 *
497 * @param fs Node to destroy
498 *
499 * @return Error code
500 *
501 */
502int ext4fs_destroy_node(fs_node_t *fn)
503{
504 /* If directory, check for children */
505 bool has_children;
506 int rc = ext4fs_has_children(&has_children, fn);
507 if (rc != EOK) {
508 ext4fs_node_put(fn);
509 return rc;
510 }
511
512 if (has_children) {
513 ext4fs_node_put(fn);
514 return EINVAL;
515 }
516
517 ext4fs_node_t *enode = EXT4FS_NODE(fn);
518 ext4_inode_ref_t *inode_ref = enode->inode_ref;
519
520 /* Release data blocks */
521 rc = ext4_filesystem_truncate_inode(inode_ref, 0);
522 if (rc != EOK) {
523 ext4fs_node_put(fn);
524 return rc;
525 }
526
527 /*
528 * TODO: Sset real deletion time when it will be supported.
529 * Temporary set fake deletion time.
530 */
531 ext4_inode_set_deletion_time(inode_ref->inode, 0xdeadbeef);
532 inode_ref->dirty = true;
533
534 /* Free inode */
535 rc = ext4_filesystem_free_inode(inode_ref);
536 if (rc != EOK) {
537 ext4fs_node_put(fn);
538 return rc;
539 }
540
541 return ext4fs_node_put(fn);
542}
543
544/** Link the specfied node to directory.
545 *
546 * @param pfn Parent node to link in
547 * @param cfn Node to be linked
548 * @param name Name which will be assigned to directory entry
549 *
550 * @return Error code
551 *
552 */
553int ext4fs_link(fs_node_t *pfn, fs_node_t *cfn, const char *name)
554{
555 /* Check maximum name length */
556 if (str_size(name) > EXT4_DIRECTORY_FILENAME_LEN)
557 return ENAMETOOLONG;
558
559 ext4fs_node_t *parent = EXT4FS_NODE(pfn);
560 ext4fs_node_t *child = EXT4FS_NODE(cfn);
561 ext4_filesystem_t *fs = parent->instance->filesystem;
562
563 /* Add entry to parent directory */
564 int rc = ext4_directory_add_entry(parent->inode_ref, name,
565 child->inode_ref);
566 if (rc != EOK)
567 return rc;
568
569 /* Fill new dir -> add '.' and '..' entries */
570 if (ext4_inode_is_type(fs->superblock, child->inode_ref->inode,
571 EXT4_INODE_MODE_DIRECTORY)) {
572 rc = ext4_directory_add_entry(child->inode_ref, ".",
573 child->inode_ref);
574 if (rc != EOK) {
575 ext4_directory_remove_entry(parent->inode_ref, name);
576 return rc;
577 }
578
579 rc = ext4_directory_add_entry(child->inode_ref, "..",
580 parent->inode_ref);
581 if (rc != EOK) {
582 ext4_directory_remove_entry(parent->inode_ref, name);
583 ext4_directory_remove_entry(child->inode_ref, ".");
584 return rc;
585 }
586
587 /* Initialize directory index if supported */
588 if (ext4_superblock_has_feature_compatible(fs->superblock,
589 EXT4_FEATURE_COMPAT_DIR_INDEX)) {
590 rc = ext4_directory_dx_init(child->inode_ref);
591 if (rc != EOK)
592 return rc;
593
594 ext4_inode_set_flag(child->inode_ref->inode,
595 EXT4_INODE_FLAG_INDEX);
596 child->inode_ref->dirty = true;
597 }
598
599 uint16_t parent_links =
600 ext4_inode_get_links_count(parent->inode_ref->inode);
601 parent_links++;
602 ext4_inode_set_links_count(parent->inode_ref->inode, parent_links);
603
604 parent->inode_ref->dirty = true;
605 }
606
607 uint16_t child_links =
608 ext4_inode_get_links_count(child->inode_ref->inode);
609 child_links++;
610 ext4_inode_set_links_count(child->inode_ref->inode, child_links);
611
612 child->inode_ref->dirty = true;
613
614 return EOK;
615}
616
617/** Unlink node from specified directory.
618 *
619 * @param pfn Parent node to delete node from
620 * @param cfn Child node to be unlinked from directory
621 * @param name Name of entry that will be removed
622 *
623 * @return Error code
624 *
625 */
626int ext4fs_unlink(fs_node_t *pfn, fs_node_t *cfn, const char *name)
627{
628 bool has_children;
629 int rc = ext4fs_has_children(&has_children, cfn);
630 if (rc != EOK)
631 return rc;
632
633 /* Cannot unlink non-empty node */
634 if (has_children)
635 return ENOTEMPTY;
636
637 /* Remove entry from parent directory */
638 ext4_inode_ref_t *parent = EXT4FS_NODE(pfn)->inode_ref;
639 rc = ext4_directory_remove_entry(parent, name);
640 if (rc != EOK)
641 return rc;
642
643 /* Decrement links count */
644 ext4_inode_ref_t *child_inode_ref = EXT4FS_NODE(cfn)->inode_ref;
645
646 uint32_t lnk_count =
647 ext4_inode_get_links_count(child_inode_ref->inode);
648 lnk_count--;
649
650 /* If directory - handle links from parent */
651 if ((lnk_count <= 1) && (ext4fs_is_directory(cfn))) {
652 assert(lnk_count == 1);
653
654 lnk_count--;
655
656 ext4_inode_ref_t *parent_inode_ref = EXT4FS_NODE(pfn)->inode_ref;
657
658 uint32_t parent_lnk_count = ext4_inode_get_links_count(
659 parent_inode_ref->inode);
660
661 parent_lnk_count--;
662 ext4_inode_set_links_count(parent_inode_ref->inode, parent_lnk_count);
663
664 parent->dirty = true;
665 }
666
667 /*
668 * TODO: Update timestamps of the parent
669 * (when we have wall-clock time).
670 *
671 * ext4_inode_set_change_inode_time(parent->inode, (uint32_t) now);
672 * ext4_inode_set_modification_time(parent->inode, (uint32_t) now);
673 * parent->dirty = true;
674 */
675
676 /*
677 * TODO: Update timestamp for inode.
678 *
679 * ext4_inode_set_change_inode_time(child_inode_ref->inode,
680 * (uint32_t) now);
681 */
682
683 ext4_inode_set_links_count(child_inode_ref->inode, lnk_count);
684 child_inode_ref->dirty = true;
685
686 return EOK;
687}
688
689/** Check if specified node has children.
690 *
691 * For files is response allways false and check is executed only for directories.
692 *
693 * @param has_children Output value for response
694 * @param fn Node to check
695 *
696 * @return Error code
697 *
698 */
699int ext4fs_has_children(bool *has_children, fs_node_t *fn)
700{
701 ext4fs_node_t *enode = EXT4FS_NODE(fn);
702 ext4_filesystem_t *fs = enode->instance->filesystem;
703
704 /* Check if node is directory */
705 if (!ext4_inode_is_type(fs->superblock, enode->inode_ref->inode,
706 EXT4_INODE_MODE_DIRECTORY)) {
707 *has_children = false;
708 return EOK;
709 }
710
711 ext4_directory_iterator_t it;
712 int rc = ext4_directory_iterator_init(&it, enode->inode_ref, 0);
713 if (rc != EOK)
714 return rc;
715
716 /* Find a non-empty directory entry */
717 bool found = false;
718 while (it.current != NULL) {
719 if (it.current->inode != 0) {
720 uint16_t name_size =
721 ext4_directory_entry_ll_get_name_length(fs->superblock,
722 it.current);
723 if (!ext4fs_is_dots(it.current->name, name_size)) {
724 found = true;
725 break;
726 }
727 }
728
729 rc = ext4_directory_iterator_next(&it);
730 if (rc != EOK) {
731 ext4_directory_iterator_fini(&it);
732 return rc;
733 }
734 }
735
736 rc = ext4_directory_iterator_fini(&it);
737 if (rc != EOK)
738 return rc;
739
740 *has_children = found;
741
742 return EOK;
743}
744
745/** Unpack index number from node.
746 *
747 * @param fn Node to load index from
748 *
749 * @return Index number of i-node
750 *
751 */
752fs_index_t ext4fs_index_get(fs_node_t *fn)
753{
754 ext4fs_node_t *enode = EXT4FS_NODE(fn);
755 return enode->inode_ref->index;
756}
757
758/** Get real size of file / directory.
759 *
760 * @param fn Node to get size of
761 *
762 * @return Real size of node
763 *
764 */
765aoff64_t ext4fs_size_get(fs_node_t *fn)
766{
767 ext4fs_node_t *enode = EXT4FS_NODE(fn);
768 ext4_superblock_t *sb = enode->instance->filesystem->superblock;
769 return ext4_inode_get_size(sb, enode->inode_ref->inode);
770}
771
772/** Get number of links to specified node.
773 *
774 * @param fn Node to get links to
775 *
776 * @return Number of links
777 *
778 */
779unsigned ext4fs_lnkcnt_get(fs_node_t *fn)
780{
781 ext4fs_node_t *enode = EXT4FS_NODE(fn);
782 uint32_t lnkcnt = ext4_inode_get_links_count(enode->inode_ref->inode);
783
784 if (ext4fs_is_directory(fn)) {
785 if (lnkcnt > 1)
786 return 1;
787 else
788 return 0;
789 }
790
791 /* For regular files return real links count */
792 return lnkcnt;
793}
794
795/** Check if node is directory.
796 *
797 * @param fn Node to check
798 *
799 * @return Result of check
800 *
801 */
802bool ext4fs_is_directory(fs_node_t *fn)
803{
804 ext4fs_node_t *enode = EXT4FS_NODE(fn);
805 ext4_superblock_t *sb = enode->instance->filesystem->superblock;
806
807 return ext4_inode_is_type(sb, enode->inode_ref->inode,
808 EXT4_INODE_MODE_DIRECTORY);
809}
810
811/** Check if node is regular file.
812 *
813 * @param fn Node to check
814 *
815 * @return Result of check
816 *
817 */
818bool ext4fs_is_file(fs_node_t *fn)
819{
820 ext4fs_node_t *enode = EXT4FS_NODE(fn);
821 ext4_superblock_t *sb = enode->instance->filesystem->superblock;
822
823 return ext4_inode_is_type(sb, enode->inode_ref->inode,
824 EXT4_INODE_MODE_FILE);
825}
826
827/** Extract device identifier from node.
828 *
829 * @param node Node to extract id from
830 *
831 * @return id of device, where is the filesystem
832 *
833 */
834service_id_t ext4fs_service_get(fs_node_t *fn)
835{
836 ext4fs_node_t *enode = EXT4FS_NODE(fn);
837 return enode->instance->service_id;
838}
839
840int ext4fs_size_block(service_id_t service_id, uint32_t *size)
841{
842 ext4fs_instance_t *inst;
843 int rc = ext4fs_instance_get(service_id, &inst);
844 if (rc != EOK)
845 return rc;
846
847 if (NULL == inst)
848 return ENOENT;
849
850 ext4_superblock_t *sb = inst->filesystem->superblock;
851 *size = ext4_superblock_get_block_size(sb);
852
853 return EOK;
854}
855
856int ext4fs_total_block_count(service_id_t service_id, uint64_t *count)
857{
858 ext4fs_instance_t *inst;
859 int rc = ext4fs_instance_get(service_id, &inst);
860 if (rc != EOK)
861 return rc;
862
863 if (NULL == inst)
864 return ENOENT;
865
866 ext4_superblock_t *sb = inst->filesystem->superblock;
867 *count = ext4_superblock_get_blocks_count(sb);
868
869 return EOK;
870}
871
872int ext4fs_free_block_count(service_id_t service_id, uint64_t *count)
873{
874 ext4fs_instance_t *inst;
875 int rc = ext4fs_instance_get(service_id, &inst);
876 if (rc != EOK)
877 return rc;
878
879 ext4_superblock_t *sb = inst->filesystem->superblock;
880 *count = ext4_superblock_get_free_blocks_count(sb);
881
882 return EOK;
883}
884
885/*
886 * libfs operations.
887 */
888libfs_ops_t ext4fs_libfs_ops = {
889 .root_get = ext4fs_root_get,
890 .match = ext4fs_match,
891 .node_get = ext4fs_node_get,
892 .node_open = ext4fs_node_open,
893 .node_put = ext4fs_node_put,
894 .create = ext4fs_create_node,
895 .destroy = ext4fs_destroy_node,
896 .link = ext4fs_link,
897 .unlink = ext4fs_unlink,
898 .has_children = ext4fs_has_children,
899 .index_get = ext4fs_index_get,
900 .size_get = ext4fs_size_get,
901 .lnkcnt_get = ext4fs_lnkcnt_get,
902 .is_directory = ext4fs_is_directory,
903 .is_file = ext4fs_is_file,
904 .service_get = ext4fs_service_get,
905 .size_block = ext4fs_size_block,
906 .total_block_count = ext4fs_total_block_count,
907 .free_block_count = ext4fs_free_block_count
908};
909
910/*
911 * VFS operations.
912 */
913
914/** Mount operation.
915 *
916 * Try to mount specified filesystem from device.
917 *
918 * @param service_id Identifier of device
919 * @param opts Mount options
920 * @param index Output value - index of root node
921 * @param size Output value - size of root node
922 * @param lnkcnt Output value - link count of root node
923 *
924 * @return Error code
925 *
926 */
927static int ext4fs_mounted(service_id_t service_id, const char *opts,
928 fs_index_t *index, aoff64_t *size, unsigned *lnkcnt)
929{
930 /* Allocate libext4 filesystem structure */
931 ext4_filesystem_t *fs = (ext4_filesystem_t *)
932 malloc(sizeof(ext4_filesystem_t));
933 if (fs == NULL)
934 return ENOMEM;
935
936 /* Allocate instance structure */
937 ext4fs_instance_t *inst = (ext4fs_instance_t *)
938 malloc(sizeof(ext4fs_instance_t));
939 if (inst == NULL) {
940 free(fs);
941 return ENOMEM;
942 }
943
944 enum cache_mode cmode;
945 if (str_cmp(opts, "wtcache") == 0)
946 cmode = CACHE_MODE_WT;
947 else
948 cmode = CACHE_MODE_WB;
949
950 /* Initialize the filesystem */
951 int rc = ext4_filesystem_init(fs, service_id, cmode);
952 if (rc != EOK) {
953 free(fs);
954 free(inst);
955 return rc;
956 }
957
958 /* Do some sanity checking */
959 rc = ext4_filesystem_check_sanity(fs);
960 if (rc != EOK) {
961 ext4_filesystem_fini(fs);
962 free(fs);
963 free(inst);
964 return rc;
965 }
966
967 /* Check flags */
968 bool read_only;
969 rc = ext4_filesystem_check_features(fs, &read_only);
970 if (rc != EOK) {
971 ext4_filesystem_fini(fs);
972 free(fs);
973 free(inst);
974 return rc;
975 }
976
977 /* Initialize instance */
978 link_initialize(&inst->link);
979 inst->service_id = service_id;
980 inst->filesystem = fs;
981 inst->open_nodes_count = 0;
982
983 /* Read root node */
984 fs_node_t *root_node;
985 rc = ext4fs_node_get_core(&root_node, inst, EXT4_INODE_ROOT_INDEX);
986 if (rc != EOK) {
987 ext4_filesystem_fini(fs);
988 free(fs);
989 free(inst);
990 return rc;
991 }
992
993 /* Add instance to the list */
994 fibril_mutex_lock(&instance_list_mutex);
995 list_append(&inst->link, &instance_list);
996 fibril_mutex_unlock(&instance_list_mutex);
997
998 ext4fs_node_t *enode = EXT4FS_NODE(root_node);
999
1000 *index = EXT4_INODE_ROOT_INDEX;
1001 *size = ext4_inode_get_size(fs->superblock, enode->inode_ref->inode);
1002 *lnkcnt = 1;
1003
1004 ext4fs_node_put(root_node);
1005
1006 return EOK;
1007}
1008
1009/** Unmount operation.
1010 *
1011 * Correctly release the filesystem.
1012 *
1013 * @param service_id Device to be unmounted
1014 *
1015 * @return Error code
1016 *
1017 */
1018static int ext4fs_unmounted(service_id_t service_id)
1019{
1020 ext4fs_instance_t *inst;
1021 int rc = ext4fs_instance_get(service_id, &inst);
1022 if (rc != EOK)
1023 return rc;
1024
1025 fibril_mutex_lock(&open_nodes_lock);
1026
1027 if (inst->open_nodes_count != 0) {
1028 fibril_mutex_unlock(&open_nodes_lock);
1029 return EBUSY;
1030 }
1031
1032 /* Remove the instance from the list */
1033 fibril_mutex_lock(&instance_list_mutex);
1034 list_remove(&inst->link);
1035 fibril_mutex_unlock(&instance_list_mutex);
1036
1037 fibril_mutex_unlock(&open_nodes_lock);
1038
1039 return ext4_filesystem_fini(inst->filesystem);
1040}
1041
1042/** Read bytes from node.
1043 *
1044 * @param service_id Device to read data from
1045 * @param index Number of node to read from
1046 * @param pos Position where the read should be started
1047 * @param rbytes Output value, where the real size was returned
1048 *
1049 * @return Error code
1050 *
1051 */
1052static int ext4fs_read(service_id_t service_id, fs_index_t index, aoff64_t pos,
1053 size_t *rbytes)
1054{
1055 /*
1056 * Receive the read request.
1057 */
1058 ipc_callid_t callid;
1059 size_t size;
1060 if (!async_data_read_receive(&callid, &size)) {
1061 async_answer_0(callid, EINVAL);
1062 return EINVAL;
1063 }
1064
1065 ext4fs_instance_t *inst;
1066 int rc = ext4fs_instance_get(service_id, &inst);
1067 if (rc != EOK) {
1068 async_answer_0(callid, rc);
1069 return rc;
1070 }
1071
1072 /* Load i-node */
1073 ext4_inode_ref_t *inode_ref;
1074 rc = ext4_filesystem_get_inode_ref(inst->filesystem, index, &inode_ref);
1075 if (rc != EOK) {
1076 async_answer_0(callid, rc);
1077 return rc;
1078 }
1079
1080 /* Read from i-node by type */
1081 if (ext4_inode_is_type(inst->filesystem->superblock, inode_ref->inode,
1082 EXT4_INODE_MODE_FILE)) {
1083 rc = ext4fs_read_file(callid, pos, size, inst, inode_ref,
1084 rbytes);
1085 } else if (ext4_inode_is_type(inst->filesystem->superblock,
1086 inode_ref->inode, EXT4_INODE_MODE_DIRECTORY)) {
1087 rc = ext4fs_read_directory(callid, pos, size, inst, inode_ref,
1088 rbytes);
1089 } else {
1090 /* Other inode types not supported */
1091 async_answer_0(callid, ENOTSUP);
1092 rc = ENOTSUP;
1093 }
1094
1095 ext4_filesystem_put_inode_ref(inode_ref);
1096
1097 return rc;
1098}
1099
1100/** Check if filename is dot or dotdot (reserved names).
1101 *
1102 * @param name Name to check
1103 * @param name_size Length of string name
1104 *
1105 * @return Result of the check
1106 *
1107 */
1108bool ext4fs_is_dots(const uint8_t *name, size_t name_size)
1109{
1110 if ((name_size == 1) && (name[0] == '.'))
1111 return true;
1112
1113 if ((name_size == 2) && (name[0] == '.') && (name[1] == '.'))
1114 return true;
1115
1116 return false;
1117}
1118
1119/** Read data from directory.
1120 *
1121 * @param callid IPC id of call (for communication)
1122 * @param pos Position to start reading from
1123 * @param size How many bytes to read
1124 * @param inst Filesystem instance
1125 * @param inode_ref Node to read data from
1126 * @param rbytes Output value to return real number of bytes was read
1127 *
1128 * @return Error code
1129 *
1130 */
1131int ext4fs_read_directory(ipc_callid_t callid, aoff64_t pos, size_t size,
1132 ext4fs_instance_t *inst, ext4_inode_ref_t *inode_ref, size_t *rbytes)
1133{
1134 ext4_directory_iterator_t it;
1135 int rc = ext4_directory_iterator_init(&it, inode_ref, pos);
1136 if (rc != EOK) {
1137 async_answer_0(callid, rc);
1138 return rc;
1139 }
1140
1141 /*
1142 * Find next interesting directory entry.
1143 * We want to skip . and .. entries
1144 * as these are not used in HelenOS
1145 */
1146 bool found = false;
1147 while (it.current != NULL) {
1148 if (it.current->inode == 0)
1149 goto skip;
1150
1151 uint16_t name_size = ext4_directory_entry_ll_get_name_length(
1152 inst->filesystem->superblock, it.current);
1153
1154 /* Skip . and .. */
1155 if (ext4fs_is_dots(it.current->name, name_size))
1156 goto skip;
1157
1158 /*
1159 * The on-disk entry does not contain \0 at the end
1160 * end of entry name, so we copy it to new buffer
1161 * and add the \0 at the end
1162 */
1163 uint8_t *buf = malloc(name_size + 1);
1164 if (buf == NULL) {
1165 ext4_directory_iterator_fini(&it);
1166 async_answer_0(callid, ENOMEM);
1167 return ENOMEM;
1168 }
1169
1170 memcpy(buf, &it.current->name, name_size);
1171 *(buf + name_size) = 0;
1172 found = true;
1173
1174 (void) async_data_read_finalize(callid, buf, name_size + 1);
1175 free(buf);
1176 break;
1177
1178skip:
1179 rc = ext4_directory_iterator_next(&it);
1180 if (rc != EOK) {
1181 ext4_directory_iterator_fini(&it);
1182 async_answer_0(callid, rc);
1183 return rc;
1184 }
1185 }
1186
1187 uint64_t next;
1188 if (found) {
1189 rc = ext4_directory_iterator_next(&it);
1190 if (rc != EOK)
1191 return rc;
1192
1193 next = it.current_offset;
1194 }
1195
1196 rc = ext4_directory_iterator_fini(&it);
1197 if (rc != EOK)
1198 return rc;
1199
1200 /* Prepare return values */
1201 if (found) {
1202 *rbytes = next - pos;
1203 return EOK;
1204 } else {
1205 async_answer_0(callid, ENOENT);
1206 return ENOENT;
1207 }
1208}
1209
1210/** Read data from file.
1211 *
1212 * @param callid IPC id of call (for communication)
1213 * @param pos Position to start reading from
1214 * @param size How many bytes to read
1215 * @param inst Filesystem instance
1216 * @param inode_ref Node to read data from
1217 * @param rbytes Output value to return real number of bytes was read
1218 *
1219 * @return Error code
1220 *
1221 */
1222int ext4fs_read_file(ipc_callid_t callid, aoff64_t pos, size_t size,
1223 ext4fs_instance_t *inst, ext4_inode_ref_t *inode_ref, size_t *rbytes)
1224{
1225 ext4_superblock_t *sb = inst->filesystem->superblock;
1226 uint64_t file_size = ext4_inode_get_size(sb, inode_ref->inode);
1227
1228 if (pos >= file_size) {
1229 /* Read 0 bytes successfully */
1230 async_data_read_finalize(callid, NULL, 0);
1231 *rbytes = 0;
1232 return EOK;
1233 }
1234
1235 /* For now, we only read data from one block at a time */
1236 uint32_t block_size = ext4_superblock_get_block_size(sb);
1237 aoff64_t file_block = pos / block_size;
1238 uint32_t offset_in_block = pos % block_size;
1239 uint32_t bytes = min(block_size - offset_in_block, size);
1240
1241 /* Handle end of file */
1242 if (pos + bytes > file_size)
1243 bytes = file_size - pos;
1244
1245 /* Get the real block number */
1246 uint32_t fs_block;
1247 int rc = ext4_filesystem_get_inode_data_block_index(inode_ref,
1248 file_block, &fs_block);
1249 if (rc != EOK) {
1250 async_answer_0(callid, rc);
1251 return rc;
1252 }
1253
1254 /*
1255 * Check for sparse file.
1256 * If ext4_filesystem_get_inode_data_block_index returned
1257 * fs_block == 0, it means that the given block is not allocated for the
1258 * file and we need to return a buffer of zeros
1259 */
1260 uint8_t *buffer;
1261 if (fs_block == 0) {
1262 buffer = malloc(bytes);
1263 if (buffer == NULL) {
1264 async_answer_0(callid, ENOMEM);
1265 return ENOMEM;
1266 }
1267
1268 memset(buffer, 0, bytes);
1269
1270 async_data_read_finalize(callid, buffer, bytes);
1271 *rbytes = bytes;
1272
1273 free(buffer);
1274 return EOK;
1275 }
1276
1277 /* Usual case - we need to read a block from device */
1278 block_t *block;
1279 rc = block_get(&block, inst->service_id, fs_block, BLOCK_FLAGS_NONE);
1280 if (rc != EOK) {
1281 async_answer_0(callid, rc);
1282 return rc;
1283 }
1284
1285 assert(offset_in_block + bytes <= block_size);
1286 async_data_read_finalize(callid, block->data + offset_in_block, bytes);
1287
1288 rc = block_put(block);
1289 if (rc != EOK)
1290 return rc;
1291
1292 *rbytes = bytes;
1293 return EOK;
1294}
1295
1296/** Write bytes to file
1297 *
1298 * @param service_id Device identifier
1299 * @param index I-node number of file
1300 * @param pos Position in file to start reading from
1301 * @param wbytes Output value - real number of written bytes
1302 * @param nsize Output value - new size of i-node
1303 *
1304 * @return Error code
1305 *
1306 */
1307static int ext4fs_write(service_id_t service_id, fs_index_t index, aoff64_t pos,
1308 size_t *wbytes, aoff64_t *nsize)
1309{
1310 fs_node_t *fn;
1311 int rc = ext4fs_node_get(&fn, service_id, index);
1312 if (rc != EOK)
1313 return rc;
1314
1315 ipc_callid_t callid;
1316 size_t len;
1317 if (!async_data_write_receive(&callid, &len)) {
1318 ext4fs_node_put(fn);
1319 async_answer_0(callid, EINVAL);
1320 return EINVAL;
1321 }
1322
1323 ext4fs_node_t *enode = EXT4FS_NODE(fn);
1324 ext4_filesystem_t *fs = enode->instance->filesystem;
1325
1326 uint32_t block_size = ext4_superblock_get_block_size(fs->superblock);
1327
1328 /* Prevent writing to more than one block */
1329 uint32_t bytes = min(len, block_size - (pos % block_size));
1330
1331 int flags = BLOCK_FLAGS_NONE;
1332 if (bytes == block_size)
1333 flags = BLOCK_FLAGS_NOREAD;
1334
1335 uint32_t iblock = pos / block_size;
1336 uint32_t fblock;
1337
1338 /* Load inode */
1339 ext4_inode_ref_t *inode_ref = enode->inode_ref;
1340 rc = ext4_filesystem_get_inode_data_block_index(inode_ref, iblock,
1341 &fblock);
1342 if (rc != EOK) {
1343 ext4fs_node_put(fn);
1344 async_answer_0(callid, rc);
1345 return rc;
1346 }
1347
1348 /* Check for sparse file */
1349 if (fblock == 0) {
1350 if ((ext4_superblock_has_feature_incompatible(fs->superblock,
1351 EXT4_FEATURE_INCOMPAT_EXTENTS)) &&
1352 (ext4_inode_has_flag(inode_ref->inode, EXT4_INODE_FLAG_EXTENTS))) {
1353 uint32_t last_iblock =
1354 ext4_inode_get_size(fs->superblock, inode_ref->inode) /
1355 block_size;
1356
1357 while (last_iblock < iblock) {
1358 rc = ext4_extent_append_block(inode_ref, &last_iblock,
1359 &fblock, true);
1360 if (rc != EOK) {
1361 ext4fs_node_put(fn);
1362 async_answer_0(callid, rc);
1363 return rc;
1364 }
1365 }
1366
1367 rc = ext4_extent_append_block(inode_ref, &last_iblock,
1368 &fblock, false);
1369 if (rc != EOK) {
1370 ext4fs_node_put(fn);
1371 async_answer_0(callid, rc);
1372 return rc;
1373 }
1374 } else {
1375 rc = ext4_balloc_alloc_block(inode_ref, &fblock);
1376 if (rc != EOK) {
1377 ext4fs_node_put(fn);
1378 async_answer_0(callid, rc);
1379 return rc;
1380 }
1381
1382 rc = ext4_filesystem_set_inode_data_block_index(inode_ref,
1383 iblock, fblock);
1384 if (rc != EOK) {
1385 ext4_balloc_free_block(inode_ref, fblock);
1386 ext4fs_node_put(fn);
1387 async_answer_0(callid, rc);
1388 return rc;
1389 }
1390 }
1391
1392 flags = BLOCK_FLAGS_NOREAD;
1393 inode_ref->dirty = true;
1394 }
1395
1396 /* Load target block */
1397 block_t *write_block;
1398 rc = block_get(&write_block, service_id, fblock, flags);
1399 if (rc != EOK) {
1400 ext4fs_node_put(fn);
1401 async_answer_0(callid, rc);
1402 return rc;
1403 }
1404
1405 if (flags == BLOCK_FLAGS_NOREAD)
1406 memset(write_block->data, 0, block_size);
1407
1408 rc = async_data_write_finalize(callid, write_block->data +
1409 (pos % block_size), bytes);
1410 if (rc != EOK) {
1411 ext4fs_node_put(fn);
1412 return rc;
1413 }
1414
1415 write_block->dirty = true;
1416
1417 rc = block_put(write_block);
1418 if (rc != EOK) {
1419 ext4fs_node_put(fn);
1420 return rc;
1421 }
1422
1423 /* Do some counting */
1424 uint32_t old_inode_size = ext4_inode_get_size(fs->superblock,
1425 inode_ref->inode);
1426 if (pos + bytes > old_inode_size) {
1427 ext4_inode_set_size(inode_ref->inode, pos + bytes);
1428 inode_ref->dirty = true;
1429 }
1430
1431 *nsize = ext4_inode_get_size(fs->superblock, inode_ref->inode);
1432 *wbytes = bytes;
1433
1434 return ext4fs_node_put(fn);
1435}
1436
1437/** Truncate file.
1438 *
1439 * Only the direction to shorter file is supported.
1440 *
1441 * @param service_id Device identifier
1442 * @param index Index if node to truncated
1443 * @param new_size New size of file
1444 *
1445 * @return Error code
1446 *
1447 */
1448static int ext4fs_truncate(service_id_t service_id, fs_index_t index,
1449 aoff64_t new_size)
1450{
1451 fs_node_t *fn;
1452 int rc = ext4fs_node_get(&fn, service_id, index);
1453 if (rc != EOK)
1454 return rc;
1455
1456 ext4fs_node_t *enode = EXT4FS_NODE(fn);
1457 ext4_inode_ref_t *inode_ref = enode->inode_ref;
1458
1459 rc = ext4_filesystem_truncate_inode(inode_ref, new_size);
1460 ext4fs_node_put(fn);
1461
1462 return rc;
1463}
1464
1465/** Close file.
1466 *
1467 * @param service_id Device identifier
1468 * @param index I-node number
1469 *
1470 * @return Error code
1471 *
1472 */
1473static int ext4fs_close(service_id_t service_id, fs_index_t index)
1474{
1475 return EOK;
1476}
1477
1478/** Destroy node specified by index.
1479 *
1480 * @param service_id Device identifier
1481 * @param index I-node to destroy
1482 *
1483 * @return Error code
1484 *
1485 */
1486static int ext4fs_destroy(service_id_t service_id, fs_index_t index)
1487{
1488 fs_node_t *fn;
1489 int rc = ext4fs_node_get(&fn, service_id, index);
1490 if (rc != EOK)
1491 return rc;
1492
1493 /* Destroy the inode */
1494 return ext4fs_destroy_node(fn);
1495}
1496
1497/** Enforce inode synchronization (write) to device.
1498 *
1499 * @param service_id Device identifier
1500 * @param index I-node number.
1501 *
1502 */
1503static int ext4fs_sync(service_id_t service_id, fs_index_t index)
1504{
1505 fs_node_t *fn;
1506 int rc = ext4fs_node_get(&fn, service_id, index);
1507 if (rc != EOK)
1508 return rc;
1509
1510 ext4fs_node_t *enode = EXT4FS_NODE(fn);
1511 enode->inode_ref->dirty = true;
1512
1513 return ext4fs_node_put(fn);
1514}
1515
1516/** VFS operations
1517 *
1518 */
1519vfs_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
1528};
1529
1530/**
1531 * @}
1532 */
Note: See TracBrowser for help on using the repository browser.