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

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

Copyright update

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