source: mainline/uspace/lib/ext4/src/ops.c@ 5e801dc

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 5e801dc was 5e801dc, checked in by GitHub <noreply@…>, 6 years ago

Indicate and enforce constness of hash table key in certain functions (#158)

The assumption here is that modifying key in the hash/equal functions in something completely unexpected, and not something you would ever want to do intentionally, so it makes sense to disallow it entirely to get that extra level of checking.

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