source: mainline/uspace/lib/ext4/src/ops.c@ 76f566d

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 76f566d was 7c3fb9b, checked in by Jiri Svoboda <jiri@…>, 7 years ago

Fix block comment formatting (ccheck).

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