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

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 2d53cfc was 062d900, checked in by Jakub Jermar <jakub@…>, 13 years ago

Cherrypick userspace hash table changes from lp:~adam-hraska+lp/helenos/rcu/.

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