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

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

ext4 code review and coding style cleanup

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