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

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since c84146d3 was c84146d3, checked in by Manuele Conti <conti.ma@…>, 12 years ago

Replace name operations from total_block and free_block to total_block_count and
free_block_count

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