source: mainline/uspace/srv/fs/ext4fs/ext4fs_ops.c@ 1fff583

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 1fff583 was 1fff583, checked in by Frantisek Princ <fp860328@…>, 13 years ago

comments

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