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

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since d1538a1 was d1538a1, checked in by Martin Sucha <sucha14@…>, 13 years ago

Add missing copyright headers to ext4

Those files are based on ext2 filesystem driver code.

  • Property mode set to 100644
File size: 34.8 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);
103
104/* Static variables */
105
106static LIST_INITIALIZE(instance_list);
107static FIBRIL_MUTEX_INITIALIZE(instance_list_mutex);
108static hash_table_t open_nodes;
109static FIBRIL_MUTEX_INITIALIZE(open_nodes_lock);
110
111/* Hash table interface for open nodes hash table */
112
113typedef struct {
114 service_id_t service_id;
115 fs_index_t index;
116} node_key_t;
117
118static size_t open_nodes_key_hash(void *key_arg)
119{
120 node_key_t *key = (node_key_t *)key_arg;
121 return hash_combine(key->service_id, key->index);
122}
123
124static size_t open_nodes_hash(const ht_link_t *item)
125{
126 ext4fs_node_t *enode = hash_table_get_inst(item, ext4fs_node_t, link);
127 return hash_combine(enode->instance->service_id, enode->inode_ref->index);
128}
129
130static bool open_nodes_key_equal(void *key_arg, const ht_link_t *item)
131{
132 node_key_t *key = (node_key_t *)key_arg;
133 ext4fs_node_t *enode = hash_table_get_inst(item, ext4fs_node_t, link);
134
135 return key->service_id == enode->instance->service_id
136 && key->index == enode->inode_ref->index;
137}
138
139static hash_table_ops_t open_nodes_ops = {
140 .hash = open_nodes_hash,
141 .key_hash = open_nodes_key_hash,
142 .key_equal = open_nodes_key_equal,
143 .equal = NULL,
144 .remove_callback = NULL,
145};
146
147/** Basic initialization of the driver.
148 *
149 * This is only needed to create the hash table
150 * for storing open nodes.
151 *
152 * @return Error code
153 *
154 */
155int ext4fs_global_init(void)
156{
157 if (!hash_table_create(&open_nodes, 0, 0, &open_nodes_ops))
158 return ENOMEM;
159
160 return EOK;
161}
162
163/* Finalization of the driver.
164 *
165 * This is only needed to destroy the hash table.
166 *
167 * @return Error code
168 */
169int ext4fs_global_fini(void)
170{
171 hash_table_destroy(&open_nodes);
172 return EOK;
173}
174
175/*
176 * Ext4 libfs operations.
177 */
178
179/** Get instance from internal table by service_id.
180 *
181 * @param service_id Device identifier
182 * @param inst Output instance if successful operation
183 *
184 * @return Error code
185 *
186 */
187int ext4fs_instance_get(service_id_t service_id, ext4fs_instance_t **inst)
188{
189 fibril_mutex_lock(&instance_list_mutex);
190
191 if (list_empty(&instance_list)) {
192 fibril_mutex_unlock(&instance_list_mutex);
193 return EINVAL;
194 }
195
196 list_foreach(instance_list, link) {
197 ext4fs_instance_t *tmp =
198 list_get_instance(link, ext4fs_instance_t, link);
199
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
840/*
841 * libfs operations.
842 */
843libfs_ops_t ext4fs_libfs_ops = {
844 .root_get = ext4fs_root_get,
845 .match = ext4fs_match,
846 .node_get = ext4fs_node_get,
847 .node_open = ext4fs_node_open,
848 .node_put = ext4fs_node_put,
849 .create = ext4fs_create_node,
850 .destroy = ext4fs_destroy_node,
851 .link = ext4fs_link,
852 .unlink = ext4fs_unlink,
853 .has_children = ext4fs_has_children,
854 .index_get = ext4fs_index_get,
855 .size_get = ext4fs_size_get,
856 .lnkcnt_get = ext4fs_lnkcnt_get,
857 .is_directory = ext4fs_is_directory,
858 .is_file = ext4fs_is_file,
859 .service_get = ext4fs_service_get
860};
861
862/*
863 * VFS operations.
864 */
865
866/** Mount operation.
867 *
868 * Try to mount specified filesystem from device.
869 *
870 * @param service_id Identifier of device
871 * @param opts Mount options
872 * @param index Output value - index of root node
873 * @param size Output value - size of root node
874 * @param lnkcnt Output value - link count of root node
875 *
876 * @return Error code
877 *
878 */
879static int ext4fs_mounted(service_id_t service_id, const char *opts,
880 fs_index_t *index, aoff64_t *size, unsigned *lnkcnt)
881{
882 /* Allocate libext4 filesystem structure */
883 ext4_filesystem_t *fs = (ext4_filesystem_t *)
884 malloc(sizeof(ext4_filesystem_t));
885 if (fs == NULL)
886 return ENOMEM;
887
888 /* Allocate instance structure */
889 ext4fs_instance_t *inst = (ext4fs_instance_t *)
890 malloc(sizeof(ext4fs_instance_t));
891 if (inst == NULL) {
892 free(fs);
893 return ENOMEM;
894 }
895
896 enum cache_mode cmode;
897 if (str_cmp(opts, "wtcache") == 0)
898 cmode = CACHE_MODE_WT;
899 else
900 cmode = CACHE_MODE_WB;
901
902 /* Initialize the filesystem */
903 int rc = ext4_filesystem_init(fs, service_id, cmode);
904 if (rc != EOK) {
905 free(fs);
906 free(inst);
907 return rc;
908 }
909
910 /* Do some sanity checking */
911 rc = ext4_filesystem_check_sanity(fs);
912 if (rc != EOK) {
913 ext4_filesystem_fini(fs);
914 free(fs);
915 free(inst);
916 return rc;
917 }
918
919 /* Check flags */
920 bool read_only;
921 rc = ext4_filesystem_check_features(fs, &read_only);
922 if (rc != EOK) {
923 ext4_filesystem_fini(fs);
924 free(fs);
925 free(inst);
926 return rc;
927 }
928
929 /* Initialize instance */
930 link_initialize(&inst->link);
931 inst->service_id = service_id;
932 inst->filesystem = fs;
933 inst->open_nodes_count = 0;
934
935 /* Read root node */
936 fs_node_t *root_node;
937 rc = ext4fs_node_get_core(&root_node, inst, EXT4_INODE_ROOT_INDEX);
938 if (rc != EOK) {
939 ext4_filesystem_fini(fs);
940 free(fs);
941 free(inst);
942 return rc;
943 }
944
945 /* Add instance to the list */
946 fibril_mutex_lock(&instance_list_mutex);
947 list_append(&inst->link, &instance_list);
948 fibril_mutex_unlock(&instance_list_mutex);
949
950 ext4fs_node_t *enode = EXT4FS_NODE(root_node);
951
952 *index = EXT4_INODE_ROOT_INDEX;
953 *size = ext4_inode_get_size(fs->superblock, enode->inode_ref->inode);
954 *lnkcnt = 1;
955
956 ext4fs_node_put(root_node);
957
958 return EOK;
959}
960
961/** Unmount operation.
962 *
963 * Correctly release the filesystem.
964 *
965 * @param service_id Device to be unmounted
966 *
967 * @return Error code
968 *
969 */
970static int ext4fs_unmounted(service_id_t service_id)
971{
972 ext4fs_instance_t *inst;
973 int rc = ext4fs_instance_get(service_id, &inst);
974 if (rc != EOK)
975 return rc;
976
977 fibril_mutex_lock(&open_nodes_lock);
978
979 if (inst->open_nodes_count != 0) {
980 fibril_mutex_unlock(&open_nodes_lock);
981 return EBUSY;
982 }
983
984 /* Remove the instance from the list */
985 fibril_mutex_lock(&instance_list_mutex);
986 list_remove(&inst->link);
987 fibril_mutex_unlock(&instance_list_mutex);
988
989 fibril_mutex_unlock(&open_nodes_lock);
990
991 return ext4_filesystem_fini(inst->filesystem);
992}
993
994/** Read bytes from node.
995 *
996 * @param service_id Device to read data from
997 * @param index Number of node to read from
998 * @param pos Position where the read should be started
999 * @param rbytes Output value, where the real size was returned
1000 *
1001 * @return Error code
1002 *
1003 */
1004static int ext4fs_read(service_id_t service_id, fs_index_t index, aoff64_t pos,
1005 size_t *rbytes)
1006{
1007 /*
1008 * Receive the read request.
1009 */
1010 ipc_callid_t callid;
1011 size_t size;
1012 if (!async_data_read_receive(&callid, &size)) {
1013 async_answer_0(callid, EINVAL);
1014 return EINVAL;
1015 }
1016
1017 ext4fs_instance_t *inst;
1018 int rc = ext4fs_instance_get(service_id, &inst);
1019 if (rc != EOK) {
1020 async_answer_0(callid, rc);
1021 return rc;
1022 }
1023
1024 /* Load i-node */
1025 ext4_inode_ref_t *inode_ref;
1026 rc = ext4_filesystem_get_inode_ref(inst->filesystem, index, &inode_ref);
1027 if (rc != EOK) {
1028 async_answer_0(callid, rc);
1029 return rc;
1030 }
1031
1032 /* Read from i-node by type */
1033 if (ext4_inode_is_type(inst->filesystem->superblock, inode_ref->inode,
1034 EXT4_INODE_MODE_FILE)) {
1035 rc = ext4fs_read_file(callid, pos, size, inst, inode_ref,
1036 rbytes);
1037 } else if (ext4_inode_is_type(inst->filesystem->superblock,
1038 inode_ref->inode, EXT4_INODE_MODE_DIRECTORY)) {
1039 rc = ext4fs_read_directory(callid, pos, size, inst, inode_ref,
1040 rbytes);
1041 } else {
1042 /* Other inode types not supported */
1043 async_answer_0(callid, ENOTSUP);
1044 rc = ENOTSUP;
1045 }
1046
1047 ext4_filesystem_put_inode_ref(inode_ref);
1048
1049 return rc;
1050}
1051
1052/** Check if filename is dot or dotdot (reserved names).
1053 *
1054 * @param name Name to check
1055 * @param name_size Length of string name
1056 *
1057 * @return Result of the check
1058 *
1059 */
1060bool ext4fs_is_dots(const uint8_t *name, size_t name_size)
1061{
1062 if ((name_size == 1) && (name[0] == '.'))
1063 return true;
1064
1065 if ((name_size == 2) && (name[0] == '.') && (name[1] == '.'))
1066 return true;
1067
1068 return false;
1069}
1070
1071/** Read data from directory.
1072 *
1073 * @param callid IPC id of call (for communication)
1074 * @param pos Position to start reading from
1075 * @param size How many bytes to read
1076 * @param inst Filesystem instance
1077 * @param inode_ref Node to read data from
1078 * @param rbytes Output value to return real number of bytes was read
1079 *
1080 * @return Error code
1081 *
1082 */
1083int ext4fs_read_directory(ipc_callid_t callid, aoff64_t pos, size_t size,
1084 ext4fs_instance_t *inst, ext4_inode_ref_t *inode_ref, size_t *rbytes)
1085{
1086 ext4_directory_iterator_t it;
1087 int rc = ext4_directory_iterator_init(&it, inode_ref, pos);
1088 if (rc != EOK) {
1089 async_answer_0(callid, rc);
1090 return rc;
1091 }
1092
1093 /*
1094 * Find next interesting directory entry.
1095 * We want to skip . and .. entries
1096 * as these are not used in HelenOS
1097 */
1098 bool found = false;
1099 while (it.current != NULL) {
1100 if (it.current->inode == 0)
1101 goto skip;
1102
1103 uint16_t name_size = ext4_directory_entry_ll_get_name_length(
1104 inst->filesystem->superblock, it.current);
1105
1106 /* Skip . and .. */
1107 if (ext4fs_is_dots(it.current->name, name_size))
1108 goto skip;
1109
1110 /*
1111 * The on-disk entry does not contain \0 at the end
1112 * end of entry name, so we copy it to new buffer
1113 * and add the \0 at the end
1114 */
1115 uint8_t *buf = malloc(name_size + 1);
1116 if (buf == NULL) {
1117 ext4_directory_iterator_fini(&it);
1118 async_answer_0(callid, ENOMEM);
1119 return ENOMEM;
1120 }
1121
1122 memcpy(buf, &it.current->name, name_size);
1123 *(buf + name_size) = 0;
1124 found = true;
1125
1126 (void) async_data_read_finalize(callid, buf, name_size + 1);
1127 free(buf);
1128 break;
1129
1130skip:
1131 rc = ext4_directory_iterator_next(&it);
1132 if (rc != EOK) {
1133 ext4_directory_iterator_fini(&it);
1134 async_answer_0(callid, rc);
1135 return rc;
1136 }
1137 }
1138
1139 uint64_t next;
1140 if (found) {
1141 rc = ext4_directory_iterator_next(&it);
1142 if (rc != EOK)
1143 return rc;
1144
1145 next = it.current_offset;
1146 }
1147
1148 rc = ext4_directory_iterator_fini(&it);
1149 if (rc != EOK)
1150 return rc;
1151
1152 /* Prepare return values */
1153 if (found) {
1154 *rbytes = next - pos;
1155 return EOK;
1156 } else {
1157 async_answer_0(callid, ENOENT);
1158 return ENOENT;
1159 }
1160}
1161
1162/** Read data from file.
1163 *
1164 * @param callid IPC id of call (for communication)
1165 * @param pos Position to start reading from
1166 * @param size How many bytes to read
1167 * @param inst Filesystem instance
1168 * @param inode_ref Node to read data from
1169 * @param rbytes Output value to return real number of bytes was read
1170 *
1171 * @return Error code
1172 *
1173 */
1174int ext4fs_read_file(ipc_callid_t callid, aoff64_t pos, size_t size,
1175 ext4fs_instance_t *inst, ext4_inode_ref_t *inode_ref, size_t *rbytes)
1176{
1177 ext4_superblock_t *sb = inst->filesystem->superblock;
1178 uint64_t file_size = ext4_inode_get_size(sb, inode_ref->inode);
1179
1180 if (pos >= file_size) {
1181 /* Read 0 bytes successfully */
1182 async_data_read_finalize(callid, NULL, 0);
1183 *rbytes = 0;
1184 return EOK;
1185 }
1186
1187 /* For now, we only read data from one block at a time */
1188 uint32_t block_size = ext4_superblock_get_block_size(sb);
1189 aoff64_t file_block = pos / block_size;
1190 uint32_t offset_in_block = pos % block_size;
1191 uint32_t bytes = min(block_size - offset_in_block, size);
1192
1193 /* Handle end of file */
1194 if (pos + bytes > file_size)
1195 bytes = file_size - pos;
1196
1197 /* Get the real block number */
1198 uint32_t fs_block;
1199 int rc = ext4_filesystem_get_inode_data_block_index(inode_ref,
1200 file_block, &fs_block);
1201 if (rc != EOK) {
1202 async_answer_0(callid, rc);
1203 return rc;
1204 }
1205
1206 /*
1207 * Check for sparse file.
1208 * If ext4_filesystem_get_inode_data_block_index returned
1209 * fs_block == 0, it means that the given block is not allocated for the
1210 * file and we need to return a buffer of zeros
1211 */
1212 uint8_t *buffer;
1213 if (fs_block == 0) {
1214 buffer = malloc(bytes);
1215 if (buffer == NULL) {
1216 async_answer_0(callid, ENOMEM);
1217 return ENOMEM;
1218 }
1219
1220 memset(buffer, 0, bytes);
1221
1222 async_data_read_finalize(callid, buffer, bytes);
1223 *rbytes = bytes;
1224
1225 free(buffer);
1226 return EOK;
1227 }
1228
1229 /* Usual case - we need to read a block from device */
1230 block_t *block;
1231 rc = block_get(&block, inst->service_id, fs_block, BLOCK_FLAGS_NONE);
1232 if (rc != EOK) {
1233 async_answer_0(callid, rc);
1234 return rc;
1235 }
1236
1237 assert(offset_in_block + bytes <= block_size);
1238 async_data_read_finalize(callid, block->data + offset_in_block, bytes);
1239
1240 rc = block_put(block);
1241 if (rc != EOK)
1242 return rc;
1243
1244 *rbytes = bytes;
1245 return EOK;
1246}
1247
1248/** Write bytes to file
1249 *
1250 * @param service_id Device identifier
1251 * @param index I-node number of file
1252 * @param pos Position in file to start reading from
1253 * @param wbytes Output value - real number of written bytes
1254 * @param nsize Output value - new size of i-node
1255 *
1256 * @return Error code
1257 *
1258 */
1259static int ext4fs_write(service_id_t service_id, fs_index_t index, aoff64_t pos,
1260 size_t *wbytes, aoff64_t *nsize)
1261{
1262 fs_node_t *fn;
1263 int rc = ext4fs_node_get(&fn, service_id, index);
1264 if (rc != EOK)
1265 return rc;
1266
1267 ipc_callid_t callid;
1268 size_t len;
1269 if (!async_data_write_receive(&callid, &len)) {
1270 ext4fs_node_put(fn);
1271 async_answer_0(callid, EINVAL);
1272 return EINVAL;
1273 }
1274
1275 ext4fs_node_t *enode = EXT4FS_NODE(fn);
1276 ext4_filesystem_t *fs = enode->instance->filesystem;
1277
1278 uint32_t block_size = ext4_superblock_get_block_size(fs->superblock);
1279
1280 /* Prevent writing to more than one block */
1281 uint32_t bytes = min(len, block_size - (pos % block_size));
1282
1283 int flags = BLOCK_FLAGS_NONE;
1284 if (bytes == block_size)
1285 flags = BLOCK_FLAGS_NOREAD;
1286
1287 uint32_t iblock = pos / block_size;
1288 uint32_t fblock;
1289
1290 /* Load inode */
1291 ext4_inode_ref_t *inode_ref = enode->inode_ref;
1292 rc = ext4_filesystem_get_inode_data_block_index(inode_ref, iblock,
1293 &fblock);
1294 if (rc != EOK) {
1295 ext4fs_node_put(fn);
1296 async_answer_0(callid, rc);
1297 return rc;
1298 }
1299
1300 /* Check for sparse file */
1301 if (fblock == 0) {
1302 if ((ext4_superblock_has_feature_incompatible(fs->superblock,
1303 EXT4_FEATURE_INCOMPAT_EXTENTS)) &&
1304 (ext4_inode_has_flag(inode_ref->inode, EXT4_INODE_FLAG_EXTENTS))) {
1305 uint32_t last_iblock =
1306 ext4_inode_get_size(fs->superblock, inode_ref->inode) /
1307 block_size;
1308
1309 while (last_iblock < iblock) {
1310 rc = ext4_extent_append_block(inode_ref, &last_iblock,
1311 &fblock, true);
1312 if (rc != EOK) {
1313 ext4fs_node_put(fn);
1314 async_answer_0(callid, rc);
1315 return rc;
1316 }
1317 }
1318
1319 rc = ext4_extent_append_block(inode_ref, &last_iblock,
1320 &fblock, false);
1321 if (rc != EOK) {
1322 ext4fs_node_put(fn);
1323 async_answer_0(callid, rc);
1324 return rc;
1325 }
1326 } else {
1327 rc = ext4_balloc_alloc_block(inode_ref, &fblock);
1328 if (rc != EOK) {
1329 ext4fs_node_put(fn);
1330 async_answer_0(callid, rc);
1331 return rc;
1332 }
1333
1334 rc = ext4_filesystem_set_inode_data_block_index(inode_ref,
1335 iblock, fblock);
1336 if (rc != EOK) {
1337 ext4_balloc_free_block(inode_ref, fblock);
1338 ext4fs_node_put(fn);
1339 async_answer_0(callid, rc);
1340 return rc;
1341 }
1342 }
1343
1344 flags = BLOCK_FLAGS_NOREAD;
1345 inode_ref->dirty = true;
1346 }
1347
1348 /* Load target block */
1349 block_t *write_block;
1350 rc = block_get(&write_block, service_id, fblock, flags);
1351 if (rc != EOK) {
1352 ext4fs_node_put(fn);
1353 async_answer_0(callid, rc);
1354 return rc;
1355 }
1356
1357 if (flags == BLOCK_FLAGS_NOREAD)
1358 memset(write_block->data, 0, block_size);
1359
1360 rc = async_data_write_finalize(callid, write_block->data +
1361 (pos % block_size), bytes);
1362 if (rc != EOK) {
1363 ext4fs_node_put(fn);
1364 return rc;
1365 }
1366
1367 write_block->dirty = true;
1368
1369 rc = block_put(write_block);
1370 if (rc != EOK) {
1371 ext4fs_node_put(fn);
1372 return rc;
1373 }
1374
1375 /* Do some counting */
1376 uint32_t old_inode_size = ext4_inode_get_size(fs->superblock,
1377 inode_ref->inode);
1378 if (pos + bytes > old_inode_size) {
1379 ext4_inode_set_size(inode_ref->inode, pos + bytes);
1380 inode_ref->dirty = true;
1381 }
1382
1383 *nsize = ext4_inode_get_size(fs->superblock, inode_ref->inode);
1384 *wbytes = bytes;
1385
1386 return ext4fs_node_put(fn);
1387}
1388
1389/** Truncate file.
1390 *
1391 * Only the direction to shorter file is supported.
1392 *
1393 * @param service_id Device identifier
1394 * @param index Index if node to truncated
1395 * @param new_size New size of file
1396 *
1397 * @return Error code
1398 *
1399 */
1400static int ext4fs_truncate(service_id_t service_id, fs_index_t index,
1401 aoff64_t new_size)
1402{
1403 fs_node_t *fn;
1404 int rc = ext4fs_node_get(&fn, service_id, index);
1405 if (rc != EOK)
1406 return rc;
1407
1408 ext4fs_node_t *enode = EXT4FS_NODE(fn);
1409 ext4_inode_ref_t *inode_ref = enode->inode_ref;
1410
1411 rc = ext4_filesystem_truncate_inode(inode_ref, new_size);
1412 ext4fs_node_put(fn);
1413
1414 return rc;
1415}
1416
1417/** Close file.
1418 *
1419 * @param service_id Device identifier
1420 * @param index I-node number
1421 *
1422 * @return Error code
1423 *
1424 */
1425static int ext4fs_close(service_id_t service_id, fs_index_t index)
1426{
1427 return EOK;
1428}
1429
1430/** Destroy node specified by index.
1431 *
1432 * @param service_id Device identifier
1433 * @param index I-node to destroy
1434 *
1435 * @return Error code
1436 *
1437 */
1438static int ext4fs_destroy(service_id_t service_id, fs_index_t index)
1439{
1440 fs_node_t *fn;
1441 int rc = ext4fs_node_get(&fn, service_id, index);
1442 if (rc != EOK)
1443 return rc;
1444
1445 /* Destroy the inode */
1446 return ext4fs_destroy_node(fn);
1447}
1448
1449/** Enforce inode synchronization (write) to device.
1450 *
1451 * @param service_id Device identifier
1452 * @param index I-node number.
1453 *
1454 */
1455static int ext4fs_sync(service_id_t service_id, fs_index_t index)
1456{
1457 fs_node_t *fn;
1458 int rc = ext4fs_node_get(&fn, service_id, index);
1459 if (rc != EOK)
1460 return rc;
1461
1462 ext4fs_node_t *enode = EXT4FS_NODE(fn);
1463 enode->inode_ref->dirty = true;
1464
1465 return ext4fs_node_put(fn);
1466}
1467
1468/** VFS operations
1469 *
1470 */
1471vfs_out_ops_t ext4fs_ops = {
1472 .mounted = ext4fs_mounted,
1473 .unmounted = ext4fs_unmounted,
1474 .read = ext4fs_read,
1475 .write = ext4fs_write,
1476 .truncate = ext4fs_truncate,
1477 .close = ext4fs_close,
1478 .destroy = ext4fs_destroy,
1479 .sync = ext4fs_sync
1480};
1481
1482/**
1483 * @}
1484 */
Note: See TracBrowser for help on using the repository browser.