source: mainline/uspace/lib/ext4/src/ops.c@ 09ab0a9a

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 09ab0a9a was 984a9ba, checked in by Martin Decky <martin@…>, 7 years ago

do not expose the call capability handler from the async framework

Keep the call capability handler encapsulated within the async framework
and do not expose it explicitly via its API. Use the pointer to
ipc_call_t as the sole object identifying an IPC call in the code that
uses the async framework.

This plugs a major leak in the abstraction and also simplifies both the
async framework (slightly) and all IPC servers.

  • Property mode set to 100644
File size: 34.8 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(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 ipc_call_t call;
1024 size_t size;
1025 if (!async_data_read_receive(&call, &size)) {
1026 async_answer_0(&call, 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(&call, 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(&call, 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(&call, 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(&call, pos, size, inst, inode_ref,
1053 rbytes);
1054 } else {
1055 /* Other inode types not supported */
1056 async_answer_0(&call, 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 call IPC call
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(ipc_call_t *call, 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(call, 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(call, 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(call, 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(call, 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(call, ENOENT);
1171 return ENOENT;
1172 }
1173}
1174
1175/** Read data from file.
1176 *
1177 * @param call IPC call
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(ipc_call_t *call, 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(call, 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(call, 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(call, ENOMEM);
1230 return ENOMEM;
1231 }
1232
1233 memset(buffer, 0, bytes);
1234
1235 rc = async_data_read_finalize(call, 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(call, rc);
1247 return rc;
1248 }
1249
1250 assert(offset_in_block + bytes <= block_size);
1251 rc = async_data_read_finalize(call, 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 ipc_call_t call;
1286 size_t len;
1287 if (!async_data_write_receive(&call, &len)) {
1288 rc = EINVAL;
1289 async_answer_0(&call, 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(&call, 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(&call, 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(&call, rc);
1339 goto exit;
1340 }
1341 } else {
1342 rc = ext4_balloc_alloc_block(inode_ref, &fblock);
1343 if (rc != EOK) {
1344 async_answer_0(&call, 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(&call, 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(&call, 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(&call, 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.