source: mainline/uspace/lib/ext4/src/ops.c

Last change on this file was 0db0df2, checked in by Jiří Zárevúcky <zarevucky.jiri@…>, 3 months ago

Hash table improvements

Implement hash_table_foreach macro, analogous to list_foreach.

Remove superfluous argument to hash_table_find_next().
(If the user needs to recheck the part of the list already
checked by hash_table_find(), they can just rerun that function.)

Add hash argument to hash_table_ops_t::key_equal.
The big change here is that users with big keys can store the hash
value alongside key in their entries, and for the low low cost of
sizeof(size_t) bytes eliminate a bunch of expensive key comparisons.

Also added a hash function for strings and arbitrary data.
Found this one by asking ChatGPT, because the latency of accesses
to my book collection is currently a couple of hours.

+ Some drive-by unused #include removal.

  • 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(ipc_call_t *, aoff64_t, size_t,
61 ext4_instance_t *, ext4_inode_ref_t *, size_t *);
62static errno_t ext4_read_file(ipc_call_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(const void *key_arg)
104{
105 const node_key_t *key = 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(const void *key_arg, size_t hash, const ht_link_t *item)
116{
117 const node_key_t *key = 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 const 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/** Get service ID for a service-special file.
813 *
814 * @param node Node to extract id from
815 *
816 * @return Service ID for a service-special file, zero for a regular file.
817 *
818 */
819service_id_t ext4_service_get(fs_node_t *fn)
820{
821 return 0;
822}
823
824errno_t ext4_size_block(service_id_t service_id, uint32_t *size)
825{
826 ext4_instance_t *inst;
827 errno_t rc = ext4_instance_get(service_id, &inst);
828 if (rc != EOK)
829 return rc;
830
831 if (NULL == inst)
832 return ENOENT;
833
834 ext4_superblock_t *sb = inst->filesystem->superblock;
835 *size = ext4_superblock_get_block_size(sb);
836
837 return EOK;
838}
839
840errno_t ext4_total_block_count(service_id_t service_id, uint64_t *count)
841{
842 ext4_instance_t *inst;
843 errno_t rc = ext4_instance_get(service_id, &inst);
844 if (rc != EOK)
845 return rc;
846
847 if (NULL == inst)
848 return ENOENT;
849
850 ext4_superblock_t *sb = inst->filesystem->superblock;
851 *count = ext4_superblock_get_blocks_count(sb);
852
853 return EOK;
854}
855
856errno_t ext4_free_block_count(service_id_t service_id, uint64_t *count)
857{
858 ext4_instance_t *inst;
859 errno_t rc = ext4_instance_get(service_id, &inst);
860 if (rc != EOK)
861 return rc;
862
863 ext4_superblock_t *sb = inst->filesystem->superblock;
864 *count = ext4_superblock_get_free_blocks_count(sb);
865
866 return EOK;
867}
868
869/*
870 * libfs operations.
871 */
872libfs_ops_t ext4_libfs_ops = {
873 .root_get = ext4_root_get,
874 .match = ext4_match,
875 .node_get = ext4_node_get,
876 .node_open = ext4_node_open,
877 .node_put = ext4_node_put,
878 .create = ext4_create_node,
879 .destroy = ext4_destroy_node,
880 .link = ext4_link,
881 .unlink = ext4_unlink,
882 .has_children = ext4_has_children,
883 .index_get = ext4_index_get,
884 .size_get = ext4_size_get,
885 .lnkcnt_get = ext4_lnkcnt_get,
886 .is_directory = ext4_is_directory,
887 .is_file = ext4_is_file,
888 .service_get = ext4_service_get,
889 .size_block = ext4_size_block,
890 .total_block_count = ext4_total_block_count,
891 .free_block_count = ext4_free_block_count
892};
893
894/*
895 * VFS operations.
896 */
897
898/** Probe operation.
899 *
900 * Try to get information about specified filesystem from device.
901 *
902 * @param sevice_id Service ID
903 * @param info Place to store information
904 *
905 * @return Error code
906 */
907static errno_t ext4_fsprobe(service_id_t service_id, vfs_fs_probe_info_t *info)
908{
909 ext4_fs_probe_info_t pinfo;
910 errno_t rc;
911
912 rc = ext4_filesystem_probe(service_id, &pinfo);
913 if (rc != EOK)
914 return rc;
915
916 memcpy(info->label, pinfo.vol_name, sizeof(pinfo.vol_name));
917 return EOK;
918}
919
920/** Mount operation.
921 *
922 * Try to mount specified filesystem from device.
923 *
924 * @param service_id Identifier of device
925 * @param opts Mount options
926 * @param index Output value - index of root node
927 * @param size Output value - size of root node
928 *
929 * @return Error code
930 *
931 */
932static errno_t ext4_mounted(service_id_t service_id, const char *opts,
933 fs_index_t *index, aoff64_t *size)
934{
935 ext4_filesystem_t *fs;
936
937 /* Allocate instance structure */
938 ext4_instance_t *inst = (ext4_instance_t *)
939 malloc(sizeof(ext4_instance_t));
940 if (inst == NULL)
941 return ENOMEM;
942
943 enum cache_mode cmode;
944 if (str_cmp(opts, "wtcache") == 0)
945 cmode = CACHE_MODE_WT;
946 else
947 cmode = CACHE_MODE_WB;
948
949 /* Initialize instance */
950 link_initialize(&inst->link);
951 inst->service_id = service_id;
952 inst->open_nodes_count = 0;
953
954 /* Initialize the filesystem */
955 aoff64_t rnsize;
956 errno_t rc = ext4_filesystem_open(inst, service_id, cmode, &rnsize, &fs);
957 if (rc != EOK) {
958 free(inst);
959 return rc;
960 }
961
962 /* Add instance to the list */
963 fibril_mutex_lock(&instance_list_mutex);
964 list_append(&inst->link, &instance_list);
965 fibril_mutex_unlock(&instance_list_mutex);
966
967 *index = EXT4_INODE_ROOT_INDEX;
968 *size = rnsize;
969
970 return EOK;
971}
972
973/** Unmount operation.
974 *
975 * Correctly release the filesystem.
976 *
977 * @param service_id Device to be unmounted
978 *
979 * @return Error code
980 *
981 */
982static errno_t ext4_unmounted(service_id_t service_id)
983{
984 ext4_instance_t *inst;
985 errno_t rc = ext4_instance_get(service_id, &inst);
986 if (rc != EOK)
987 return rc;
988
989 fibril_mutex_lock(&open_nodes_lock);
990
991 if (inst->open_nodes_count != 0) {
992 fibril_mutex_unlock(&open_nodes_lock);
993 return EBUSY;
994 }
995
996 /* Remove the instance from the list */
997 fibril_mutex_lock(&instance_list_mutex);
998 list_remove(&inst->link);
999 fibril_mutex_unlock(&instance_list_mutex);
1000
1001 fibril_mutex_unlock(&open_nodes_lock);
1002
1003 rc = ext4_filesystem_close(inst->filesystem);
1004 if (rc != EOK) {
1005 fibril_mutex_lock(&instance_list_mutex);
1006 list_append(&inst->link, &instance_list);
1007 fibril_mutex_unlock(&instance_list_mutex);
1008 }
1009
1010 free(inst);
1011 return EOK;
1012}
1013
1014/** Read bytes from node.
1015 *
1016 * @param service_id Device to read data from
1017 * @param index Number of node to read from
1018 * @param pos Position where the read should be started
1019 * @param rbytes Output value, where the real size was returned
1020 *
1021 * @return Error code
1022 *
1023 */
1024static errno_t ext4_read(service_id_t service_id, fs_index_t index, aoff64_t pos,
1025 size_t *rbytes)
1026{
1027 /*
1028 * Receive the read request.
1029 */
1030 ipc_call_t call;
1031 size_t size;
1032 if (!async_data_read_receive(&call, &size)) {
1033 async_answer_0(&call, EINVAL);
1034 return EINVAL;
1035 }
1036
1037 ext4_instance_t *inst;
1038 errno_t rc = ext4_instance_get(service_id, &inst);
1039 if (rc != EOK) {
1040 async_answer_0(&call, rc);
1041 return rc;
1042 }
1043
1044 /* Load i-node */
1045 ext4_inode_ref_t *inode_ref;
1046 rc = ext4_filesystem_get_inode_ref(inst->filesystem, index, &inode_ref);
1047 if (rc != EOK) {
1048 async_answer_0(&call, rc);
1049 return rc;
1050 }
1051
1052 /* Read from i-node by type */
1053 if (ext4_inode_is_type(inst->filesystem->superblock, inode_ref->inode,
1054 EXT4_INODE_MODE_FILE)) {
1055 rc = ext4_read_file(&call, pos, size, inst, inode_ref,
1056 rbytes);
1057 } else if (ext4_inode_is_type(inst->filesystem->superblock,
1058 inode_ref->inode, EXT4_INODE_MODE_DIRECTORY)) {
1059 rc = ext4_read_directory(&call, pos, size, inst, inode_ref,
1060 rbytes);
1061 } else {
1062 /* Other inode types not supported */
1063 async_answer_0(&call, ENOTSUP);
1064 rc = ENOTSUP;
1065 }
1066
1067 errno_t const rc2 = ext4_filesystem_put_inode_ref(inode_ref);
1068
1069 return rc == EOK ? rc2 : rc;
1070}
1071
1072/** Check if filename is dot or dotdot (reserved names).
1073 *
1074 * @param name Name to check
1075 * @param name_size Length of string name
1076 *
1077 * @return Result of the check
1078 *
1079 */
1080bool ext4_is_dots(const uint8_t *name, size_t name_size)
1081{
1082 if ((name_size == 1) && (name[0] == '.'))
1083 return true;
1084
1085 if ((name_size == 2) && (name[0] == '.') && (name[1] == '.'))
1086 return true;
1087
1088 return false;
1089}
1090
1091/** Read data from directory.
1092 *
1093 * @param call IPC call
1094 * @param pos Position to start reading from
1095 * @param size How many bytes to read
1096 * @param inst Filesystem instance
1097 * @param inode_ref Node to read data from
1098 * @param rbytes Output value to return real number of bytes was read
1099 *
1100 * @return Error code
1101 *
1102 */
1103errno_t ext4_read_directory(ipc_call_t *call, aoff64_t pos, size_t size,
1104 ext4_instance_t *inst, ext4_inode_ref_t *inode_ref, size_t *rbytes)
1105{
1106 ext4_directory_iterator_t it;
1107 errno_t rc = ext4_directory_iterator_init(&it, inode_ref, pos);
1108 if (rc != EOK) {
1109 async_answer_0(call, rc);
1110 return rc;
1111 }
1112
1113 /*
1114 * Find next interesting directory entry.
1115 * We want to skip . and .. entries
1116 * as these are not used in HelenOS
1117 */
1118 bool found = false;
1119 while (it.current != NULL) {
1120 if (it.current->inode == 0)
1121 goto skip;
1122
1123 uint16_t name_size = ext4_directory_entry_ll_get_name_length(
1124 inst->filesystem->superblock, it.current);
1125
1126 /* Skip . and .. */
1127 if (ext4_is_dots(it.current->name, name_size))
1128 goto skip;
1129
1130 /*
1131 * The on-disk entry does not contain \0 at the end
1132 * end of entry name, so we copy it to new buffer
1133 * and add the \0 at the end
1134 */
1135 uint8_t *buf = malloc(name_size + 1);
1136 if (buf == NULL) {
1137 ext4_directory_iterator_fini(&it);
1138 async_answer_0(call, ENOMEM);
1139 return ENOMEM;
1140 }
1141
1142 memcpy(buf, &it.current->name, name_size);
1143 *(buf + name_size) = 0;
1144 found = true;
1145
1146 (void) async_data_read_finalize(call, buf, name_size + 1);
1147 free(buf);
1148 break;
1149
1150 skip:
1151 rc = ext4_directory_iterator_next(&it);
1152 if (rc != EOK) {
1153 ext4_directory_iterator_fini(&it);
1154 async_answer_0(call, rc);
1155 return rc;
1156 }
1157 }
1158
1159 uint64_t next;
1160 if (found) {
1161 rc = ext4_directory_iterator_next(&it);
1162 if (rc != EOK)
1163 return rc;
1164
1165 next = it.current_offset;
1166 }
1167
1168 rc = ext4_directory_iterator_fini(&it);
1169 if (rc != EOK)
1170 return rc;
1171
1172 /* Prepare return values */
1173 if (found) {
1174 *rbytes = next - pos;
1175 return EOK;
1176 } else {
1177 async_answer_0(call, ENOENT);
1178 return ENOENT;
1179 }
1180}
1181
1182/** Read data from file.
1183 *
1184 * @param call IPC call
1185 * @param pos Position to start reading from
1186 * @param size How many bytes to read
1187 * @param inst Filesystem instance
1188 * @param inode_ref Node to read data from
1189 * @param rbytes Output value to return real number of bytes was read
1190 *
1191 * @return Error code
1192 *
1193 */
1194errno_t ext4_read_file(ipc_call_t *call, aoff64_t pos, size_t size,
1195 ext4_instance_t *inst, ext4_inode_ref_t *inode_ref, size_t *rbytes)
1196{
1197 ext4_superblock_t *sb = inst->filesystem->superblock;
1198 uint64_t file_size = ext4_inode_get_size(sb, inode_ref->inode);
1199
1200 if (pos >= file_size) {
1201 /* Read 0 bytes successfully */
1202 async_data_read_finalize(call, NULL, 0);
1203 *rbytes = 0;
1204 return EOK;
1205 }
1206
1207 /* For now, we only read data from one block at a time */
1208 uint32_t block_size = ext4_superblock_get_block_size(sb);
1209 aoff64_t file_block = pos / block_size;
1210 uint32_t offset_in_block = pos % block_size;
1211 uint32_t bytes = min(block_size - offset_in_block, size);
1212
1213 /* Handle end of file */
1214 if (pos + bytes > file_size)
1215 bytes = file_size - pos;
1216
1217 /* Get the real block number */
1218 uint32_t fs_block;
1219 errno_t rc = ext4_filesystem_get_inode_data_block_index(inode_ref,
1220 file_block, &fs_block);
1221 if (rc != EOK) {
1222 async_answer_0(call, rc);
1223 return rc;
1224 }
1225
1226 /*
1227 * Check for sparse file.
1228 * If ext4_filesystem_get_inode_data_block_index returned
1229 * fs_block == 0, it means that the given block is not allocated for the
1230 * file and we need to return a buffer of zeros
1231 */
1232 uint8_t *buffer;
1233 if (fs_block == 0) {
1234 buffer = malloc(bytes);
1235 if (buffer == NULL) {
1236 async_answer_0(call, ENOMEM);
1237 return ENOMEM;
1238 }
1239
1240 memset(buffer, 0, bytes);
1241
1242 rc = async_data_read_finalize(call, buffer, bytes);
1243 *rbytes = bytes;
1244
1245 free(buffer);
1246 return rc;
1247 }
1248
1249 /* Usual case - we need to read a block from device */
1250 block_t *block;
1251 rc = block_get(&block, inst->service_id, fs_block, BLOCK_FLAGS_NONE);
1252 if (rc != EOK) {
1253 async_answer_0(call, rc);
1254 return rc;
1255 }
1256
1257 assert(offset_in_block + bytes <= block_size);
1258 rc = async_data_read_finalize(call, block->data + offset_in_block, bytes);
1259 if (rc != EOK) {
1260 block_put(block);
1261 return rc;
1262 }
1263
1264 rc = block_put(block);
1265 if (rc != EOK)
1266 return rc;
1267
1268 *rbytes = bytes;
1269 return EOK;
1270}
1271
1272/** Write bytes to file
1273 *
1274 * @param service_id Device identifier
1275 * @param index I-node number of file
1276 * @param pos Position in file to start reading from
1277 * @param wbytes Output value - real number of written bytes
1278 * @param nsize Output value - new size of i-node
1279 *
1280 * @return Error code
1281 *
1282 */
1283static errno_t ext4_write(service_id_t service_id, fs_index_t index, aoff64_t pos,
1284 size_t *wbytes, aoff64_t *nsize)
1285{
1286 fs_node_t *fn;
1287 errno_t rc2;
1288 errno_t rc = ext4_node_get(&fn, service_id, index);
1289 if (rc != EOK)
1290 return rc;
1291
1292 ipc_call_t call;
1293 size_t len;
1294 if (!async_data_write_receive(&call, &len)) {
1295 rc = EINVAL;
1296 async_answer_0(&call, rc);
1297 goto exit;
1298 }
1299
1300 ext4_node_t *enode = EXT4_NODE(fn);
1301 ext4_filesystem_t *fs = enode->instance->filesystem;
1302
1303 uint32_t block_size = ext4_superblock_get_block_size(fs->superblock);
1304
1305 /* Prevent writing to more than one block */
1306 uint32_t bytes = min(len, block_size - (pos % block_size));
1307
1308 int flags = BLOCK_FLAGS_NONE;
1309 if (bytes == block_size)
1310 flags = BLOCK_FLAGS_NOREAD;
1311
1312 uint32_t iblock = pos / block_size;
1313 uint32_t fblock;
1314
1315 /* Load inode */
1316 ext4_inode_ref_t *inode_ref = enode->inode_ref;
1317 rc = ext4_filesystem_get_inode_data_block_index(inode_ref, iblock,
1318 &fblock);
1319 if (rc != EOK) {
1320 async_answer_0(&call, rc);
1321 goto exit;
1322 }
1323
1324 /* Check for sparse file */
1325 if (fblock == 0) {
1326 if ((ext4_superblock_has_feature_incompatible(fs->superblock,
1327 EXT4_FEATURE_INCOMPAT_EXTENTS)) &&
1328 (ext4_inode_has_flag(inode_ref->inode, EXT4_INODE_FLAG_EXTENTS))) {
1329 uint32_t last_iblock =
1330 ext4_inode_get_size(fs->superblock, inode_ref->inode) /
1331 block_size;
1332
1333 while (last_iblock < iblock) {
1334 rc = ext4_extent_append_block(inode_ref, &last_iblock,
1335 &fblock, true);
1336 if (rc != EOK) {
1337 async_answer_0(&call, rc);
1338 goto exit;
1339 }
1340 }
1341
1342 rc = ext4_extent_append_block(inode_ref, &last_iblock,
1343 &fblock, false);
1344 if (rc != EOK) {
1345 async_answer_0(&call, rc);
1346 goto exit;
1347 }
1348 } else {
1349 rc = ext4_balloc_alloc_block(inode_ref, &fblock);
1350 if (rc != EOK) {
1351 async_answer_0(&call, rc);
1352 goto exit;
1353 }
1354
1355 rc = ext4_filesystem_set_inode_data_block_index(inode_ref,
1356 iblock, fblock);
1357 if (rc != EOK) {
1358 ext4_balloc_free_block(inode_ref, fblock);
1359 async_answer_0(&call, rc);
1360 goto exit;
1361 }
1362 }
1363
1364 flags = BLOCK_FLAGS_NOREAD;
1365 inode_ref->dirty = true;
1366 }
1367
1368 /* Load target block */
1369 block_t *write_block;
1370 rc = block_get(&write_block, service_id, fblock, flags);
1371 if (rc != EOK) {
1372 async_answer_0(&call, rc);
1373 goto exit;
1374 }
1375
1376 if (flags == BLOCK_FLAGS_NOREAD)
1377 memset(write_block->data, 0, block_size);
1378
1379 rc = async_data_write_finalize(&call, write_block->data +
1380 (pos % block_size), bytes);
1381 if (rc != EOK) {
1382 block_put(write_block);
1383 goto exit;
1384 }
1385
1386 write_block->dirty = true;
1387
1388 rc = block_put(write_block);
1389 if (rc != EOK)
1390 goto exit;
1391
1392 /* Do some counting */
1393 uint32_t old_inode_size = ext4_inode_get_size(fs->superblock,
1394 inode_ref->inode);
1395 if (pos + bytes > old_inode_size) {
1396 ext4_inode_set_size(inode_ref->inode, pos + bytes);
1397 inode_ref->dirty = true;
1398 }
1399
1400 *nsize = ext4_inode_get_size(fs->superblock, inode_ref->inode);
1401 *wbytes = bytes;
1402
1403exit:
1404 rc2 = ext4_node_put(fn);
1405 return rc == EOK ? rc2 : rc;
1406}
1407
1408/** Truncate file.
1409 *
1410 * Only the direction to shorter file is supported.
1411 *
1412 * @param service_id Device identifier
1413 * @param index Index if node to truncated
1414 * @param new_size New size of file
1415 *
1416 * @return Error code
1417 *
1418 */
1419static errno_t ext4_truncate(service_id_t service_id, fs_index_t index,
1420 aoff64_t new_size)
1421{
1422 fs_node_t *fn;
1423 errno_t rc = ext4_node_get(&fn, service_id, index);
1424 if (rc != EOK)
1425 return rc;
1426
1427 ext4_node_t *enode = EXT4_NODE(fn);
1428 ext4_inode_ref_t *inode_ref = enode->inode_ref;
1429
1430 rc = ext4_filesystem_truncate_inode(inode_ref, new_size);
1431 errno_t const rc2 = ext4_node_put(fn);
1432
1433 return rc == EOK ? rc2 : rc;
1434}
1435
1436/** Close file.
1437 *
1438 * @param service_id Device identifier
1439 * @param index I-node number
1440 *
1441 * @return Error code
1442 *
1443 */
1444static errno_t ext4_close(service_id_t service_id, fs_index_t index)
1445{
1446 return EOK;
1447}
1448
1449/** Destroy node specified by index.
1450 *
1451 * @param service_id Device identifier
1452 * @param index I-node to destroy
1453 *
1454 * @return Error code
1455 *
1456 */
1457static errno_t ext4_destroy(service_id_t service_id, fs_index_t index)
1458{
1459 fs_node_t *fn;
1460 errno_t rc = ext4_node_get(&fn, service_id, index);
1461 if (rc != EOK)
1462 return rc;
1463
1464 /* Destroy the inode */
1465 return ext4_destroy_node(fn);
1466}
1467
1468/** Enforce inode synchronization (write) to device.
1469 *
1470 * @param service_id Device identifier
1471 * @param index I-node number.
1472 *
1473 */
1474static errno_t ext4_sync(service_id_t service_id, fs_index_t index)
1475{
1476 fs_node_t *fn;
1477 errno_t rc = ext4_node_get(&fn, service_id, index);
1478 if (rc != EOK)
1479 return rc;
1480
1481 ext4_node_t *enode = EXT4_NODE(fn);
1482 enode->inode_ref->dirty = true;
1483
1484 return ext4_node_put(fn);
1485}
1486
1487/** VFS operations
1488 *
1489 */
1490vfs_out_ops_t ext4_ops = {
1491 .fsprobe = ext4_fsprobe,
1492 .mounted = ext4_mounted,
1493 .unmounted = ext4_unmounted,
1494 .read = ext4_read,
1495 .write = ext4_write,
1496 .truncate = ext4_truncate,
1497 .close = ext4_close,
1498 .destroy = ext4_destroy,
1499 .sync = ext4_sync
1500};
1501
1502/**
1503 * @}
1504 */
Note: See TracBrowser for help on using the repository browser.