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

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since e0a4686 was a35b458, checked in by Jiří Zárevúcky <zarevucky.jiri@…>, 8 years ago

style: Remove trailing whitespace on _all_ lines, including empty ones, for particular file types.

Command used: tools/srepl '\s\+$' '' -- *.c *.h *.py *.sh *.s *.S *.ag

Currently, whitespace on empty lines is very inconsistent.
There are two basic choices: Either remove the whitespace, or keep empty lines
indented to the level of surrounding code. The former is AFAICT more common,
and also much easier to do automatically.

Alternatively, we could write script for automatic indentation, and use that
instead. However, if such a script exists, it's possible to use the indented
style locally, by having the editor apply relevant conversions on load/save,
without affecting remote repository. IMO, it makes more sense to adopt
the simpler rule.

  • 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_callid_t, aoff64_t, size_t,
61 ext4_instance_t *, ext4_inode_ref_t *, size_t *);
62static errno_t ext4_read_file(ipc_callid_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 *);
73 errno_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
222 if (!ext4_inode_is_type(fs->superblock, eparent->inode_ref->inode,
223 EXT4_INODE_MODE_DIRECTORY))
224 return ENOTDIR;
225
226 /* Try to find entry */
227 ext4_directory_search_result_t result;
228 errno_t rc = ext4_directory_find_entry(&result, eparent->inode_ref,
229 component);
230 if (rc != EOK) {
231 if (rc == ENOENT) {
232 *rfn = NULL;
233 return EOK;
234 }
235
236 return rc;
237 }
238
239 /* Load node from search result */
240 uint32_t inode = ext4_directory_entry_ll_get_inode(result.dentry);
241 rc = ext4_node_get_core(rfn, eparent->instance, inode);
242 if (rc != EOK)
243 goto exit;
244
245exit:
246 ;
247
248 /* Destroy search result structure */
249 errno_t const rc2 = ext4_directory_destroy_result(&result);
250 return rc == EOK ? rc2 : rc;
251}
252
253/** Get node specified by index
254 *
255 * It's wrapper for node_put_core operation
256 *
257 * @param rfn Output pointer to loaded node if operation successful
258 * @param service_id Device identifier
259 * @param index Node index (here i-node number)
260 *
261 * @return Error code
262 *
263 */
264errno_t ext4_node_get(fs_node_t **rfn, service_id_t service_id, fs_index_t index)
265{
266 ext4_instance_t *inst;
267 errno_t rc = ext4_instance_get(service_id, &inst);
268 if (rc != EOK)
269 return rc;
270
271 return ext4_node_get_core(rfn, inst, index);
272}
273
274/** Main function for getting node from the filesystem.
275 *
276 * @param rfn Output point to loaded node if operation successful
277 * @param inst Instance of filesystem
278 * @param index Index of node (i-node number)
279 *
280 * @return Error code
281 *
282 */
283errno_t ext4_node_get_core(fs_node_t **rfn, ext4_instance_t *inst,
284 fs_index_t index)
285{
286 fibril_mutex_lock(&open_nodes_lock);
287
288 /* Check if the node is not already open */
289 node_key_t key = {
290 .service_id = inst->service_id,
291 .index = index
292 };
293
294 ht_link_t *already_open = hash_table_find(&open_nodes, &key);
295 ext4_node_t *enode = NULL;
296 if (already_open) {
297 enode = hash_table_get_inst(already_open, ext4_node_t, link);
298 *rfn = enode->fs_node;
299 enode->references++;
300
301 fibril_mutex_unlock(&open_nodes_lock);
302 return EOK;
303 }
304
305 /* Prepare new enode */
306 enode = malloc(sizeof(ext4_node_t));
307 if (enode == NULL) {
308 fibril_mutex_unlock(&open_nodes_lock);
309 return ENOMEM;
310 }
311
312 /* Prepare new fs_node and initialize */
313 fs_node_t *fs_node = malloc(sizeof(fs_node_t));
314 if (fs_node == NULL) {
315 free(enode);
316 fibril_mutex_unlock(&open_nodes_lock);
317 return ENOMEM;
318 }
319
320 fs_node_initialize(fs_node);
321
322 /* Load i-node from filesystem */
323 ext4_inode_ref_t *inode_ref;
324 errno_t rc = ext4_filesystem_get_inode_ref(inst->filesystem, index,
325 &inode_ref);
326 if (rc != EOK) {
327 free(enode);
328 free(fs_node);
329 fibril_mutex_unlock(&open_nodes_lock);
330 return rc;
331 }
332
333 /* Initialize enode */
334 enode->inode_ref = inode_ref;
335 enode->instance = inst;
336 enode->references = 1;
337 enode->fs_node = fs_node;
338
339 fs_node->data = enode;
340 *rfn = fs_node;
341
342 hash_table_insert(&open_nodes, &enode->link);
343 inst->open_nodes_count++;
344
345 fibril_mutex_unlock(&open_nodes_lock);
346
347 return EOK;
348}
349
350/** Put previously loaded node.
351 *
352 * @param enode Node to put back
353 *
354 * @return Error code
355 *
356 */
357static errno_t ext4_node_put_core(ext4_node_t *enode)
358{
359 hash_table_remove_item(&open_nodes, &enode->link);
360 assert(enode->instance->open_nodes_count > 0);
361 enode->instance->open_nodes_count--;
362
363 /* Put inode back in filesystem */
364 errno_t rc = ext4_filesystem_put_inode_ref(enode->inode_ref);
365 if (rc != EOK)
366 return rc;
367
368 /* Destroy data structure */
369 free(enode->fs_node);
370 free(enode);
371
372 return EOK;
373}
374
375/** Open node.
376 *
377 * This operation is stateless in this driver.
378 *
379 * @param fn Node to open
380 *
381 * @return EOK
382 *
383 */
384errno_t ext4_node_open(fs_node_t *fn)
385{
386 /* Stateless operation */
387 return EOK;
388}
389
390/** Put previously loaded node.
391 *
392 * A wrapper for node_put_core operation
393 *
394 * @param fn Node to put back
395 * @return Error code
396 *
397 */
398errno_t ext4_node_put(fs_node_t *fn)
399{
400 fibril_mutex_lock(&open_nodes_lock);
401
402 ext4_node_t *enode = EXT4_NODE(fn);
403 assert(enode->references > 0);
404 enode->references--;
405 if (enode->references == 0) {
406 errno_t rc = ext4_node_put_core(enode);
407 if (rc != EOK) {
408 fibril_mutex_unlock(&open_nodes_lock);
409 return rc;
410 }
411 }
412
413 fibril_mutex_unlock(&open_nodes_lock);
414
415 return EOK;
416}
417
418/** Create new node in filesystem.
419 *
420 * @param rfn Output pointer to newly created node if successful
421 * @param service_id Device identifier, where the filesystem is
422 * @param flags Flags for specification of new node parameters
423 *
424 * @return Error code
425 *
426 */
427errno_t ext4_create_node(fs_node_t **rfn, service_id_t service_id, int flags)
428{
429 /* Allocate enode */
430 ext4_node_t *enode;
431 enode = malloc(sizeof(ext4_node_t));
432 if (enode == NULL)
433 return ENOMEM;
434
435 /* Allocate fs_node */
436 fs_node_t *fs_node;
437 fs_node = malloc(sizeof(fs_node_t));
438 if (fs_node == NULL) {
439 free(enode);
440 return ENOMEM;
441 }
442
443 /* Load instance */
444 ext4_instance_t *inst;
445 errno_t rc = ext4_instance_get(service_id, &inst);
446 if (rc != EOK) {
447 free(enode);
448 free(fs_node);
449 return rc;
450 }
451
452 /* Allocate new i-node in filesystem */
453 ext4_inode_ref_t *inode_ref;
454 rc = ext4_filesystem_alloc_inode(inst->filesystem, &inode_ref, flags);
455 if (rc != EOK) {
456 free(enode);
457 free(fs_node);
458 return rc;
459 }
460
461 /* Do some interconnections in references */
462 enode->inode_ref = inode_ref;
463 enode->instance = inst;
464 enode->references = 1;
465
466 fibril_mutex_lock(&open_nodes_lock);
467 hash_table_insert(&open_nodes, &enode->link);
468 fibril_mutex_unlock(&open_nodes_lock);
469 inst->open_nodes_count++;
470
471 enode->inode_ref->dirty = true;
472
473 fs_node_initialize(fs_node);
474 fs_node->data = enode;
475 enode->fs_node = fs_node;
476 *rfn = fs_node;
477
478 return EOK;
479}
480
481/** Destroy existing node.
482 *
483 * @param fs Node to destroy
484 *
485 * @return Error code
486 *
487 */
488errno_t ext4_destroy_node(fs_node_t *fn)
489{
490 /* If directory, check for children */
491 bool has_children;
492 errno_t rc = ext4_has_children(&has_children, fn);
493 if (rc != EOK) {
494 ext4_node_put(fn);
495 return rc;
496 }
497
498 if (has_children) {
499 ext4_node_put(fn);
500 return EINVAL;
501 }
502
503 ext4_node_t *enode = EXT4_NODE(fn);
504 ext4_inode_ref_t *inode_ref = enode->inode_ref;
505
506 /* Release data blocks */
507 rc = ext4_filesystem_truncate_inode(inode_ref, 0);
508 if (rc != EOK) {
509 ext4_node_put(fn);
510 return rc;
511 }
512
513 /*
514 * TODO: Sset real deletion time when it will be supported.
515 * Temporary set fake deletion time.
516 */
517 ext4_inode_set_deletion_time(inode_ref->inode, 0xdeadbeef);
518 inode_ref->dirty = true;
519
520 /* Free inode */
521 rc = ext4_filesystem_free_inode(inode_ref);
522 if (rc != EOK) {
523 ext4_node_put(fn);
524 return rc;
525 }
526
527 return ext4_node_put(fn);
528}
529
530/** Link the specfied node to directory.
531 *
532 * @param pfn Parent node to link in
533 * @param cfn Node to be linked
534 * @param name Name which will be assigned to directory entry
535 *
536 * @return Error code
537 *
538 */
539errno_t ext4_link(fs_node_t *pfn, fs_node_t *cfn, const char *name)
540{
541 /* Check maximum name length */
542 if (str_size(name) > EXT4_DIRECTORY_FILENAME_LEN)
543 return ENAMETOOLONG;
544
545 ext4_node_t *parent = EXT4_NODE(pfn);
546 ext4_node_t *child = EXT4_NODE(cfn);
547 ext4_filesystem_t *fs = parent->instance->filesystem;
548
549 /* Add entry to parent directory */
550 errno_t rc = ext4_directory_add_entry(parent->inode_ref, name,
551 child->inode_ref);
552 if (rc != EOK)
553 return rc;
554
555 /* Fill new dir -> add '.' and '..' entries */
556 if (ext4_inode_is_type(fs->superblock, child->inode_ref->inode,
557 EXT4_INODE_MODE_DIRECTORY)) {
558 rc = ext4_directory_add_entry(child->inode_ref, ".",
559 child->inode_ref);
560 if (rc != EOK) {
561 ext4_directory_remove_entry(parent->inode_ref, name);
562 return rc;
563 }
564
565 rc = ext4_directory_add_entry(child->inode_ref, "..",
566 parent->inode_ref);
567 if (rc != EOK) {
568 ext4_directory_remove_entry(parent->inode_ref, name);
569 ext4_directory_remove_entry(child->inode_ref, ".");
570 return rc;
571 }
572
573 /* Initialize directory index if supported */
574 if (ext4_superblock_has_feature_compatible(fs->superblock,
575 EXT4_FEATURE_COMPAT_DIR_INDEX)) {
576 rc = ext4_directory_dx_init(child->inode_ref);
577 if (rc != EOK)
578 return rc;
579
580 ext4_inode_set_flag(child->inode_ref->inode,
581 EXT4_INODE_FLAG_INDEX);
582 child->inode_ref->dirty = true;
583 }
584
585 uint16_t parent_links =
586 ext4_inode_get_links_count(parent->inode_ref->inode);
587 parent_links++;
588 ext4_inode_set_links_count(parent->inode_ref->inode, parent_links);
589
590 parent->inode_ref->dirty = true;
591 }
592
593 uint16_t child_links =
594 ext4_inode_get_links_count(child->inode_ref->inode);
595 child_links++;
596 ext4_inode_set_links_count(child->inode_ref->inode, child_links);
597
598 child->inode_ref->dirty = true;
599
600 return EOK;
601}
602
603/** Unlink node from specified directory.
604 *
605 * @param pfn Parent node to delete node from
606 * @param cfn Child node to be unlinked from directory
607 * @param name Name of entry that will be removed
608 *
609 * @return Error code
610 *
611 */
612errno_t ext4_unlink(fs_node_t *pfn, fs_node_t *cfn, const char *name)
613{
614 bool has_children;
615 errno_t rc = ext4_has_children(&has_children, cfn);
616 if (rc != EOK)
617 return rc;
618
619 /* Cannot unlink non-empty node */
620 if (has_children)
621 return ENOTEMPTY;
622
623 /* Remove entry from parent directory */
624 ext4_inode_ref_t *parent = EXT4_NODE(pfn)->inode_ref;
625 rc = ext4_directory_remove_entry(parent, name);
626 if (rc != EOK)
627 return rc;
628
629 /* Decrement links count */
630 ext4_inode_ref_t *child_inode_ref = EXT4_NODE(cfn)->inode_ref;
631
632 uint32_t lnk_count =
633 ext4_inode_get_links_count(child_inode_ref->inode);
634 lnk_count--;
635
636 /* If directory - handle links from parent */
637 if ((lnk_count <= 1) && (ext4_is_directory(cfn))) {
638 assert(lnk_count == 1);
639
640 lnk_count--;
641
642 ext4_inode_ref_t *parent_inode_ref = EXT4_NODE(pfn)->inode_ref;
643
644 uint32_t parent_lnk_count = ext4_inode_get_links_count(
645 parent_inode_ref->inode);
646
647 parent_lnk_count--;
648 ext4_inode_set_links_count(parent_inode_ref->inode, parent_lnk_count);
649
650 parent->dirty = true;
651 }
652
653 /*
654 * TODO: Update timestamps of the parent
655 * (when we have wall-clock time).
656 *
657 * ext4_inode_set_change_inode_time(parent->inode, (uint32_t) now);
658 * ext4_inode_set_modification_time(parent->inode, (uint32_t) now);
659 * parent->dirty = true;
660 */
661
662 /*
663 * TODO: Update timestamp for inode.
664 *
665 * ext4_inode_set_change_inode_time(child_inode_ref->inode,
666 * (uint32_t) now);
667 */
668
669 ext4_inode_set_links_count(child_inode_ref->inode, lnk_count);
670 child_inode_ref->dirty = true;
671
672 return EOK;
673}
674
675/** Check if specified node has children.
676 *
677 * For files is response allways false and check is executed only for directories.
678 *
679 * @param has_children Output value for response
680 * @param fn Node to check
681 *
682 * @return Error code
683 *
684 */
685errno_t ext4_has_children(bool *has_children, fs_node_t *fn)
686{
687 ext4_node_t *enode = EXT4_NODE(fn);
688 ext4_filesystem_t *fs = enode->instance->filesystem;
689
690 /* Check if node is directory */
691 if (!ext4_inode_is_type(fs->superblock, enode->inode_ref->inode,
692 EXT4_INODE_MODE_DIRECTORY)) {
693 *has_children = false;
694 return EOK;
695 }
696
697 ext4_directory_iterator_t it;
698 errno_t rc = ext4_directory_iterator_init(&it, enode->inode_ref, 0);
699 if (rc != EOK)
700 return rc;
701
702 /* Find a non-empty directory entry */
703 bool found = false;
704 while (it.current != NULL) {
705 if (it.current->inode != 0) {
706 uint16_t name_size =
707 ext4_directory_entry_ll_get_name_length(fs->superblock,
708 it.current);
709 if (!ext4_is_dots(it.current->name, name_size)) {
710 found = true;
711 break;
712 }
713 }
714
715 rc = ext4_directory_iterator_next(&it);
716 if (rc != EOK) {
717 ext4_directory_iterator_fini(&it);
718 return rc;
719 }
720 }
721
722 rc = ext4_directory_iterator_fini(&it);
723 if (rc != EOK)
724 return rc;
725
726 *has_children = found;
727
728 return EOK;
729}
730
731/** Unpack index number from node.
732 *
733 * @param fn Node to load index from
734 *
735 * @return Index number of i-node
736 *
737 */
738fs_index_t ext4_index_get(fs_node_t *fn)
739{
740 ext4_node_t *enode = EXT4_NODE(fn);
741 return enode->inode_ref->index;
742}
743
744/** Get real size of file / directory.
745 *
746 * @param fn Node to get size of
747 *
748 * @return Real size of node
749 *
750 */
751aoff64_t ext4_size_get(fs_node_t *fn)
752{
753 ext4_node_t *enode = EXT4_NODE(fn);
754 ext4_superblock_t *sb = enode->instance->filesystem->superblock;
755 return ext4_inode_get_size(sb, enode->inode_ref->inode);
756}
757
758/** Get number of links to specified node.
759 *
760 * @param fn Node to get links to
761 *
762 * @return Number of links
763 *
764 */
765unsigned ext4_lnkcnt_get(fs_node_t *fn)
766{
767 ext4_node_t *enode = EXT4_NODE(fn);
768 uint32_t lnkcnt = ext4_inode_get_links_count(enode->inode_ref->inode);
769
770 if (ext4_is_directory(fn)) {
771 if (lnkcnt > 1)
772 return 1;
773 else
774 return 0;
775 }
776
777 /* For regular files return real links count */
778 return lnkcnt;
779}
780
781/** Check if node is directory.
782 *
783 * @param fn Node to check
784 *
785 * @return Result of check
786 *
787 */
788bool ext4_is_directory(fs_node_t *fn)
789{
790 ext4_node_t *enode = EXT4_NODE(fn);
791 ext4_superblock_t *sb = enode->instance->filesystem->superblock;
792
793 return ext4_inode_is_type(sb, enode->inode_ref->inode,
794 EXT4_INODE_MODE_DIRECTORY);
795}
796
797/** Check if node is regular file.
798 *
799 * @param fn Node to check
800 *
801 * @return Result of check
802 *
803 */
804bool ext4_is_file(fs_node_t *fn)
805{
806 ext4_node_t *enode = EXT4_NODE(fn);
807 ext4_superblock_t *sb = enode->instance->filesystem->superblock;
808
809 return ext4_inode_is_type(sb, enode->inode_ref->inode,
810 EXT4_INODE_MODE_FILE);
811}
812
813/** Extract device identifier from node.
814 *
815 * @param node Node to extract id from
816 *
817 * @return id of device, where is the filesystem
818 *
819 */
820service_id_t ext4_service_get(fs_node_t *fn)
821{
822 ext4_node_t *enode = EXT4_NODE(fn);
823 return enode->instance->service_id;
824}
825
826errno_t ext4_size_block(service_id_t service_id, uint32_t *size)
827{
828 ext4_instance_t *inst;
829 errno_t rc = ext4_instance_get(service_id, &inst);
830 if (rc != EOK)
831 return rc;
832
833 if (NULL == inst)
834 return ENOENT;
835
836 ext4_superblock_t *sb = inst->filesystem->superblock;
837 *size = ext4_superblock_get_block_size(sb);
838
839 return EOK;
840}
841
842errno_t ext4_total_block_count(service_id_t service_id, uint64_t *count)
843{
844 ext4_instance_t *inst;
845 errno_t rc = ext4_instance_get(service_id, &inst);
846 if (rc != EOK)
847 return rc;
848
849 if (NULL == inst)
850 return ENOENT;
851
852 ext4_superblock_t *sb = inst->filesystem->superblock;
853 *count = ext4_superblock_get_blocks_count(sb);
854
855 return EOK;
856}
857
858errno_t ext4_free_block_count(service_id_t service_id, uint64_t *count)
859{
860 ext4_instance_t *inst;
861 errno_t rc = ext4_instance_get(service_id, &inst);
862 if (rc != EOK)
863 return rc;
864
865 ext4_superblock_t *sb = inst->filesystem->superblock;
866 *count = ext4_superblock_get_free_blocks_count(sb);
867
868 return EOK;
869}
870
871/*
872 * libfs operations.
873 */
874libfs_ops_t ext4_libfs_ops = {
875 .root_get = ext4_root_get,
876 .match = ext4_match,
877 .node_get = ext4_node_get,
878 .node_open = ext4_node_open,
879 .node_put = ext4_node_put,
880 .create = ext4_create_node,
881 .destroy = ext4_destroy_node,
882 .link = ext4_link,
883 .unlink = ext4_unlink,
884 .has_children = ext4_has_children,
885 .index_get = ext4_index_get,
886 .size_get = ext4_size_get,
887 .lnkcnt_get = ext4_lnkcnt_get,
888 .is_directory = ext4_is_directory,
889 .is_file = ext4_is_file,
890 .service_get = ext4_service_get,
891 .size_block = ext4_size_block,
892 .total_block_count = ext4_total_block_count,
893 .free_block_count = ext4_free_block_count
894};
895
896/*
897 * VFS operations.
898 */
899
900/** Probe operation.
901 *
902 * Try to get information about specified filesystem from device.
903 *
904 * @param sevice_id Service ID
905 * @param info Place to store information
906 *
907 * @return Error code
908 */
909static errno_t ext4_fsprobe(service_id_t service_id, vfs_fs_probe_info_t *info)
910{
911 return ext4_filesystem_probe(service_id);
912}
913
914/** Mount operation.
915 *
916 * Try to mount specified filesystem from device.
917 *
918 * @param service_id Identifier of device
919 * @param opts Mount options
920 * @param index Output value - index of root node
921 * @param size Output value - size of root node
922 *
923 * @return Error code
924 *
925 */
926static errno_t ext4_mounted(service_id_t service_id, const char *opts,
927 fs_index_t *index, aoff64_t *size)
928{
929 ext4_filesystem_t *fs;
930
931 /* Allocate instance structure */
932 ext4_instance_t *inst = (ext4_instance_t *)
933 malloc(sizeof(ext4_instance_t));
934 if (inst == NULL)
935 return ENOMEM;
936
937 enum cache_mode cmode;
938 if (str_cmp(opts, "wtcache") == 0)
939 cmode = CACHE_MODE_WT;
940 else
941 cmode = CACHE_MODE_WB;
942
943 /* Initialize instance */
944 link_initialize(&inst->link);
945 inst->service_id = service_id;
946 inst->open_nodes_count = 0;
947
948 /* Initialize the filesystem */
949 aoff64_t rnsize;
950 errno_t rc = ext4_filesystem_open(inst, service_id, cmode, &rnsize, &fs);
951 if (rc != EOK) {
952 free(inst);
953 return rc;
954 }
955
956 /* Add instance to the list */
957 fibril_mutex_lock(&instance_list_mutex);
958 list_append(&inst->link, &instance_list);
959 fibril_mutex_unlock(&instance_list_mutex);
960
961 *index = EXT4_INODE_ROOT_INDEX;
962 *size = rnsize;
963
964 return EOK;
965}
966
967/** Unmount operation.
968 *
969 * Correctly release the filesystem.
970 *
971 * @param service_id Device to be unmounted
972 *
973 * @return Error code
974 *
975 */
976static errno_t ext4_unmounted(service_id_t service_id)
977{
978 ext4_instance_t *inst;
979 errno_t rc = ext4_instance_get(service_id, &inst);
980 if (rc != EOK)
981 return rc;
982
983 fibril_mutex_lock(&open_nodes_lock);
984
985 if (inst->open_nodes_count != 0) {
986 fibril_mutex_unlock(&open_nodes_lock);
987 return EBUSY;
988 }
989
990 /* Remove the instance from the list */
991 fibril_mutex_lock(&instance_list_mutex);
992 list_remove(&inst->link);
993 fibril_mutex_unlock(&instance_list_mutex);
994
995 fibril_mutex_unlock(&open_nodes_lock);
996
997 rc = ext4_filesystem_close(inst->filesystem);
998 if (rc != EOK) {
999 fibril_mutex_lock(&instance_list_mutex);
1000 list_append(&inst->link, &instance_list);
1001 fibril_mutex_unlock(&instance_list_mutex);
1002 }
1003
1004 free(inst);
1005 return EOK;
1006}
1007
1008/** Read bytes from node.
1009 *
1010 * @param service_id Device to read data from
1011 * @param index Number of node to read from
1012 * @param pos Position where the read should be started
1013 * @param rbytes Output value, where the real size was returned
1014 *
1015 * @return Error code
1016 *
1017 */
1018static errno_t ext4_read(service_id_t service_id, fs_index_t index, aoff64_t pos,
1019 size_t *rbytes)
1020{
1021 /*
1022 * Receive the read request.
1023 */
1024 ipc_callid_t callid;
1025 size_t size;
1026 if (!async_data_read_receive(&callid, &size)) {
1027 async_answer_0(callid, EINVAL);
1028 return EINVAL;
1029 }
1030
1031 ext4_instance_t *inst;
1032 errno_t rc = ext4_instance_get(service_id, &inst);
1033 if (rc != EOK) {
1034 async_answer_0(callid, rc);
1035 return rc;
1036 }
1037
1038 /* Load i-node */
1039 ext4_inode_ref_t *inode_ref;
1040 rc = ext4_filesystem_get_inode_ref(inst->filesystem, index, &inode_ref);
1041 if (rc != EOK) {
1042 async_answer_0(callid, rc);
1043 return rc;
1044 }
1045
1046 /* Read from i-node by type */
1047 if (ext4_inode_is_type(inst->filesystem->superblock, inode_ref->inode,
1048 EXT4_INODE_MODE_FILE)) {
1049 rc = ext4_read_file(callid, pos, size, inst, inode_ref,
1050 rbytes);
1051 } else if (ext4_inode_is_type(inst->filesystem->superblock,
1052 inode_ref->inode, EXT4_INODE_MODE_DIRECTORY)) {
1053 rc = ext4_read_directory(callid, pos, size, inst, inode_ref,
1054 rbytes);
1055 } else {
1056 /* Other inode types not supported */
1057 async_answer_0(callid, ENOTSUP);
1058 rc = ENOTSUP;
1059 }
1060
1061 errno_t const rc2 = ext4_filesystem_put_inode_ref(inode_ref);
1062
1063 return rc == EOK ? rc2 : rc;
1064}
1065
1066/** Check if filename is dot or dotdot (reserved names).
1067 *
1068 * @param name Name to check
1069 * @param name_size Length of string name
1070 *
1071 * @return Result of the check
1072 *
1073 */
1074bool ext4_is_dots(const uint8_t *name, size_t name_size)
1075{
1076 if ((name_size == 1) && (name[0] == '.'))
1077 return true;
1078
1079 if ((name_size == 2) && (name[0] == '.') && (name[1] == '.'))
1080 return true;
1081
1082 return false;
1083}
1084
1085/** Read data from directory.
1086 *
1087 * @param callid IPC id of call (for communication)
1088 * @param pos Position to start reading from
1089 * @param size How many bytes to read
1090 * @param inst Filesystem instance
1091 * @param inode_ref Node to read data from
1092 * @param rbytes Output value to return real number of bytes was read
1093 *
1094 * @return Error code
1095 *
1096 */
1097errno_t ext4_read_directory(ipc_callid_t callid, aoff64_t pos, size_t size,
1098 ext4_instance_t *inst, ext4_inode_ref_t *inode_ref, size_t *rbytes)
1099{
1100 ext4_directory_iterator_t it;
1101 errno_t rc = ext4_directory_iterator_init(&it, inode_ref, pos);
1102 if (rc != EOK) {
1103 async_answer_0(callid, rc);
1104 return rc;
1105 }
1106
1107 /*
1108 * Find next interesting directory entry.
1109 * We want to skip . and .. entries
1110 * as these are not used in HelenOS
1111 */
1112 bool found = false;
1113 while (it.current != NULL) {
1114 if (it.current->inode == 0)
1115 goto skip;
1116
1117 uint16_t name_size = ext4_directory_entry_ll_get_name_length(
1118 inst->filesystem->superblock, it.current);
1119
1120 /* Skip . and .. */
1121 if (ext4_is_dots(it.current->name, name_size))
1122 goto skip;
1123
1124 /*
1125 * The on-disk entry does not contain \0 at the end
1126 * end of entry name, so we copy it to new buffer
1127 * and add the \0 at the end
1128 */
1129 uint8_t *buf = malloc(name_size + 1);
1130 if (buf == NULL) {
1131 ext4_directory_iterator_fini(&it);
1132 async_answer_0(callid, ENOMEM);
1133 return ENOMEM;
1134 }
1135
1136 memcpy(buf, &it.current->name, name_size);
1137 *(buf + name_size) = 0;
1138 found = true;
1139
1140 (void) async_data_read_finalize(callid, buf, name_size + 1);
1141 free(buf);
1142 break;
1143
1144skip:
1145 rc = ext4_directory_iterator_next(&it);
1146 if (rc != EOK) {
1147 ext4_directory_iterator_fini(&it);
1148 async_answer_0(callid, rc);
1149 return rc;
1150 }
1151 }
1152
1153 uint64_t next;
1154 if (found) {
1155 rc = ext4_directory_iterator_next(&it);
1156 if (rc != EOK)
1157 return rc;
1158
1159 next = it.current_offset;
1160 }
1161
1162 rc = ext4_directory_iterator_fini(&it);
1163 if (rc != EOK)
1164 return rc;
1165
1166 /* Prepare return values */
1167 if (found) {
1168 *rbytes = next - pos;
1169 return EOK;
1170 } else {
1171 async_answer_0(callid, ENOENT);
1172 return ENOENT;
1173 }
1174}
1175
1176/** Read data from file.
1177 *
1178 * @param callid IPC id of call (for communication)
1179 * @param pos Position to start reading from
1180 * @param size How many bytes to read
1181 * @param inst Filesystem instance
1182 * @param inode_ref Node to read data from
1183 * @param rbytes Output value to return real number of bytes was read
1184 *
1185 * @return Error code
1186 *
1187 */
1188errno_t ext4_read_file(ipc_callid_t callid, aoff64_t pos, size_t size,
1189 ext4_instance_t *inst, ext4_inode_ref_t *inode_ref, size_t *rbytes)
1190{
1191 ext4_superblock_t *sb = inst->filesystem->superblock;
1192 uint64_t file_size = ext4_inode_get_size(sb, inode_ref->inode);
1193
1194 if (pos >= file_size) {
1195 /* Read 0 bytes successfully */
1196 async_data_read_finalize(callid, NULL, 0);
1197 *rbytes = 0;
1198 return EOK;
1199 }
1200
1201 /* For now, we only read data from one block at a time */
1202 uint32_t block_size = ext4_superblock_get_block_size(sb);
1203 aoff64_t file_block = pos / block_size;
1204 uint32_t offset_in_block = pos % block_size;
1205 uint32_t bytes = min(block_size - offset_in_block, size);
1206
1207 /* Handle end of file */
1208 if (pos + bytes > file_size)
1209 bytes = file_size - pos;
1210
1211 /* Get the real block number */
1212 uint32_t fs_block;
1213 errno_t rc = ext4_filesystem_get_inode_data_block_index(inode_ref,
1214 file_block, &fs_block);
1215 if (rc != EOK) {
1216 async_answer_0(callid, rc);
1217 return rc;
1218 }
1219
1220 /*
1221 * Check for sparse file.
1222 * If ext4_filesystem_get_inode_data_block_index returned
1223 * fs_block == 0, it means that the given block is not allocated for the
1224 * file and we need to return a buffer of zeros
1225 */
1226 uint8_t *buffer;
1227 if (fs_block == 0) {
1228 buffer = malloc(bytes);
1229 if (buffer == NULL) {
1230 async_answer_0(callid, ENOMEM);
1231 return ENOMEM;
1232 }
1233
1234 memset(buffer, 0, bytes);
1235
1236 rc = async_data_read_finalize(callid, buffer, bytes);
1237 *rbytes = bytes;
1238
1239 free(buffer);
1240 return rc;
1241 }
1242
1243 /* Usual case - we need to read a block from device */
1244 block_t *block;
1245 rc = block_get(&block, inst->service_id, fs_block, BLOCK_FLAGS_NONE);
1246 if (rc != EOK) {
1247 async_answer_0(callid, rc);
1248 return rc;
1249 }
1250
1251 assert(offset_in_block + bytes <= block_size);
1252 rc = async_data_read_finalize(callid, block->data + offset_in_block, bytes);
1253 if (rc != EOK) {
1254 block_put(block);
1255 return rc;
1256 }
1257
1258 rc = block_put(block);
1259 if (rc != EOK)
1260 return rc;
1261
1262 *rbytes = bytes;
1263 return EOK;
1264}
1265
1266/** Write bytes to file
1267 *
1268 * @param service_id Device identifier
1269 * @param index I-node number of file
1270 * @param pos Position in file to start reading from
1271 * @param wbytes Output value - real number of written bytes
1272 * @param nsize Output value - new size of i-node
1273 *
1274 * @return Error code
1275 *
1276 */
1277static errno_t ext4_write(service_id_t service_id, fs_index_t index, aoff64_t pos,
1278 size_t *wbytes, aoff64_t *nsize)
1279{
1280 fs_node_t *fn;
1281 errno_t rc = ext4_node_get(&fn, service_id, index);
1282 if (rc != EOK)
1283 return rc;
1284
1285 ipc_callid_t callid;
1286 size_t len;
1287 if (!async_data_write_receive(&callid, &len)) {
1288 rc = EINVAL;
1289 async_answer_0(callid, 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(callid, 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(callid, 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(callid, rc);
1339 goto exit;
1340 }
1341 } else {
1342 rc = ext4_balloc_alloc_block(inode_ref, &fblock);
1343 if (rc != EOK) {
1344 async_answer_0(callid, 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(callid, 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(callid, 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(callid, 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 ;
1398
1399 errno_t const rc2 = ext4_node_put(fn);
1400 return rc == EOK ? rc2 : rc;
1401}
1402
1403/** Truncate file.
1404 *
1405 * Only the direction to shorter file is supported.
1406 *
1407 * @param service_id Device identifier
1408 * @param index Index if node to truncated
1409 * @param new_size New size of file
1410 *
1411 * @return Error code
1412 *
1413 */
1414static errno_t ext4_truncate(service_id_t service_id, fs_index_t index,
1415 aoff64_t new_size)
1416{
1417 fs_node_t *fn;
1418 errno_t rc = ext4_node_get(&fn, service_id, index);
1419 if (rc != EOK)
1420 return rc;
1421
1422 ext4_node_t *enode = EXT4_NODE(fn);
1423 ext4_inode_ref_t *inode_ref = enode->inode_ref;
1424
1425 rc = ext4_filesystem_truncate_inode(inode_ref, new_size);
1426 errno_t const rc2 = ext4_node_put(fn);
1427
1428 return rc == EOK ? rc2 : rc;
1429}
1430
1431/** Close file.
1432 *
1433 * @param service_id Device identifier
1434 * @param index I-node number
1435 *
1436 * @return Error code
1437 *
1438 */
1439static errno_t ext4_close(service_id_t service_id, fs_index_t index)
1440{
1441 return EOK;
1442}
1443
1444/** Destroy node specified by index.
1445 *
1446 * @param service_id Device identifier
1447 * @param index I-node to destroy
1448 *
1449 * @return Error code
1450 *
1451 */
1452static errno_t ext4_destroy(service_id_t service_id, fs_index_t index)
1453{
1454 fs_node_t *fn;
1455 errno_t rc = ext4_node_get(&fn, service_id, index);
1456 if (rc != EOK)
1457 return rc;
1458
1459 /* Destroy the inode */
1460 return ext4_destroy_node(fn);
1461}
1462
1463/** Enforce inode synchronization (write) to device.
1464 *
1465 * @param service_id Device identifier
1466 * @param index I-node number.
1467 *
1468 */
1469static errno_t ext4_sync(service_id_t service_id, fs_index_t index)
1470{
1471 fs_node_t *fn;
1472 errno_t rc = ext4_node_get(&fn, service_id, index);
1473 if (rc != EOK)
1474 return rc;
1475
1476 ext4_node_t *enode = EXT4_NODE(fn);
1477 enode->inode_ref->dirty = true;
1478
1479 return ext4_node_put(fn);
1480}
1481
1482/** VFS operations
1483 *
1484 */
1485vfs_out_ops_t ext4_ops = {
1486 .fsprobe = ext4_fsprobe,
1487 .mounted = ext4_mounted,
1488 .unmounted = ext4_unmounted,
1489 .read = ext4_read,
1490 .write = ext4_write,
1491 .truncate = ext4_truncate,
1492 .close = ext4_close,
1493 .destroy = ext4_destroy,
1494 .sync = ext4_sync
1495};
1496
1497/**
1498 * @}
1499 */
Note: See TracBrowser for help on using the repository browser.