source: mainline/uspace/srv/fs/cdfs/cdfs_ops.c@ 5e801dc

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

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

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

  • Property mode set to 100644
File size: 30.8 KB
Line 
1/*
2 * Copyright (c) 2011 Martin Decky
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 cdfs
30 * @{
31 */
32
33/**
34 * @file cdfs_ops.c
35 * @brief Implementation of VFS operations for the cdfs file system
36 * server.
37 */
38
39#include "cdfs_ops.h"
40#include <stdbool.h>
41#include <adt/list.h>
42#include <adt/hash_table.h>
43#include <adt/hash.h>
44#include <mem.h>
45#include <loc.h>
46#include <libfs.h>
47#include <errno.h>
48#include <block.h>
49#include <scsi/mmc.h>
50#include <stdlib.h>
51#include <str.h>
52#include <byteorder.h>
53#include <macros.h>
54#include <unaligned.h>
55
56#include "cdfs.h"
57#include "cdfs_endian.h"
58
59/** Standard CD-ROM block size */
60#define BLOCK_SIZE 2048
61
62#define NODE_CACHE_SIZE 200
63
64/** All root nodes have index 0 */
65#define CDFS_SOME_ROOT 0
66
67#define CDFS_NODE(node) ((node) ? (cdfs_node_t *)(node)->data : NULL)
68#define FS_NODE(node) ((node) ? (node)->fs_node : NULL)
69
70#define CDFS_STANDARD_IDENT "CD001"
71
72enum {
73 CDFS_NAME_CURDIR = '\x00',
74 CDFS_NAME_PARENTDIR = '\x01'
75};
76
77typedef enum {
78 VOL_DESC_BOOT = 0,
79 VOL_DESC_PRIMARY = 1,
80 VOL_DESC_SUPPLEMENTARY = 2,
81 VOL_DESC_VOL_PARTITION = 3,
82 VOL_DESC_SET_TERMINATOR = 255
83} vol_desc_type_t;
84
85typedef struct {
86 uint8_t system_ident[32];
87 uint8_t ident[32];
88} __attribute__((packed)) cdfs_vol_desc_boot_t;
89
90typedef struct {
91 uint8_t year[4];
92 uint8_t mon[2];
93 uint8_t day[2];
94
95 uint8_t hour[2];
96 uint8_t min[2];
97 uint8_t sec[2];
98 uint8_t msec[2];
99
100 uint8_t offset;
101} __attribute__((packed)) cdfs_datetime_t;
102
103typedef struct {
104 uint8_t year; /**< Since 1900 */
105 uint8_t mon;
106 uint8_t day;
107
108 uint8_t hour;
109 uint8_t min;
110 uint8_t sec;
111
112 uint8_t offset;
113} __attribute__((packed)) cdfs_timestamp_t;
114
115typedef enum {
116 DIR_FLAG_DIRECTORY = 2
117} cdfs_dir_flag_t;
118
119/** Directory record */
120typedef struct {
121 uint8_t length;
122 uint8_t ea_length;
123
124 uint32_t_lb lba;
125 uint32_t_lb size;
126
127 cdfs_timestamp_t timestamp;
128 uint8_t flags;
129 uint8_t unit_size;
130 uint8_t gap_size;
131 uint16_t_lb sequence_nr;
132
133 uint8_t name_length;
134 uint8_t name[];
135} __attribute__((packed)) cdfs_dir_t;
136
137/** Directory record for the root directory */
138typedef struct {
139 uint8_t length;
140 uint8_t ea_length;
141
142 uint32_t_lb lba;
143 uint32_t_lb size;
144
145 cdfs_timestamp_t timestamp;
146 uint8_t flags;
147 uint8_t unit_size;
148 uint8_t gap_size;
149 uint16_t_lb sequence_nr;
150
151 uint8_t name_length;
152 uint8_t name[1];
153} __attribute__((packed)) cdfs_root_dir_t;
154
155typedef struct {
156 uint8_t flags; /* reserved in primary */
157
158 uint8_t system_ident[32];
159 uint8_t ident[32];
160
161 uint64_t res1;
162 uint32_t_lb lba_size;
163
164 uint8_t esc_seq[32]; /* reserved in primary */
165 uint16_t_lb set_size;
166 uint16_t_lb sequence_nr;
167
168 uint16_t_lb block_size;
169 uint32_t_lb path_table_size;
170
171 uint32_t path_table_lsb;
172 uint32_t opt_path_table_lsb;
173 uint32_t path_table_msb;
174 uint32_t opt_path_table_msb;
175
176 cdfs_root_dir_t root_dir;
177 uint8_t pad0;
178
179 uint8_t set_ident[128];
180 uint8_t publisher_ident[128];
181 uint8_t preparer_ident[128];
182 uint8_t app_ident[128];
183
184 uint8_t copyright_file_ident[37];
185 uint8_t abstract_file_ident[37];
186 uint8_t biblio_file_ident[37];
187
188 cdfs_datetime_t creation;
189 cdfs_datetime_t modification;
190 cdfs_datetime_t expiration;
191 cdfs_datetime_t effective;
192
193 uint8_t fs_version;
194} __attribute__((packed)) cdfs_vol_desc_prisec_t;
195
196typedef struct {
197 uint8_t type;
198 uint8_t standard_ident[5];
199 uint8_t version;
200 union {
201 cdfs_vol_desc_boot_t boot;
202 cdfs_vol_desc_prisec_t prisec;
203 } data;
204} __attribute__((packed)) cdfs_vol_desc_t;
205
206typedef enum {
207 /** ASCII character set / encoding (base ISO 9660) */
208 enc_ascii,
209 /** UCS-2 character set / encoding (Joliet) */
210 enc_ucs2
211} cdfs_enc_t;
212
213typedef enum {
214 CDFS_NONE,
215 CDFS_FILE,
216 CDFS_DIRECTORY
217} cdfs_dentry_type_t;
218
219typedef struct {
220 link_t link; /**< Siblings list link */
221 fs_index_t index; /**< Node index */
222 char *name; /**< Dentry name */
223} cdfs_dentry_t;
224
225typedef uint32_t cdfs_lba_t;
226
227typedef struct {
228 link_t link; /**< Link to list of all instances */
229 service_id_t service_id; /**< Service ID of block device */
230 cdfs_enc_t enc; /**< Filesystem string encoding */
231 char *vol_ident; /**< Volume identifier */
232} cdfs_t;
233
234typedef struct {
235 fs_node_t *fs_node; /**< FS node */
236 fs_index_t index; /**< Node index */
237 cdfs_t *fs; /**< File system */
238
239 ht_link_t nh_link; /**< Nodes hash table link */
240 cdfs_dentry_type_t type; /**< Dentry type */
241
242 unsigned int lnkcnt; /**< Link count */
243 uint32_t size; /**< File size if type is CDFS_FILE */
244
245 list_t cs_list; /**< Child's siblings list */
246 cdfs_lba_t lba; /**< LBA of data on disk */
247 bool processed; /**< If all children have been read */
248 unsigned int opened; /**< Opened count */
249} cdfs_node_t;
250
251/** String encoding */
252enum {
253 /** ASCII - standard ISO 9660 */
254 ucs2_esc_seq_no = 3,
255 /** USC-2 - Joliet */
256 ucs2_esc_seq_len = 3
257};
258
259/** Joliet SVD UCS-2 escape sequences */
260static uint8_t ucs2_esc_seq[ucs2_esc_seq_no][ucs2_esc_seq_len] = {
261 { 0x25, 0x2f, 0x40 },
262 { 0x25, 0x2f, 0x43 },
263 { 0x25, 0x2f, 0x45 }
264};
265
266/** List of all instances */
267static LIST_INITIALIZE(cdfs_instances);
268
269/** Shared index of nodes */
270static fs_index_t cdfs_index = 1;
271
272/** Number of currently cached nodes */
273static size_t nodes_cached = 0;
274
275/** Hash table of all cdfs nodes */
276static hash_table_t nodes;
277
278/*
279 * Hash table support functions.
280 */
281
282typedef struct {
283 service_id_t service_id;
284 fs_index_t index;
285} ht_key_t;
286
287static size_t nodes_key_hash(const void *k)
288{
289 const ht_key_t *key = k;
290 return hash_combine(key->service_id, key->index);
291}
292
293static size_t nodes_hash(const ht_link_t *item)
294{
295 cdfs_node_t *node = hash_table_get_inst(item, cdfs_node_t, nh_link);
296 return hash_combine(node->fs->service_id, node->index);
297}
298
299static bool nodes_key_equal(const void *k, const ht_link_t *item)
300{
301 cdfs_node_t *node = hash_table_get_inst(item, cdfs_node_t, nh_link);
302 const ht_key_t *key = k;
303
304 return key->service_id == node->fs->service_id && key->index == node->index;
305}
306
307static void nodes_remove_callback(ht_link_t *item)
308{
309 cdfs_node_t *node = hash_table_get_inst(item, cdfs_node_t, nh_link);
310
311 if (node->type == CDFS_DIRECTORY) {
312 link_t *link;
313 while ((link = list_first(&node->cs_list)) != NULL) {
314 cdfs_dentry_t *dentry = list_get_instance(link, cdfs_dentry_t, link);
315 list_remove(&dentry->link);
316 free(dentry);
317 }
318 }
319
320 free(node->fs_node);
321 free(node);
322}
323
324/** Nodes hash table operations */
325static hash_table_ops_t nodes_ops = {
326 .hash = nodes_hash,
327 .key_hash = nodes_key_hash,
328 .key_equal = nodes_key_equal,
329 .equal = NULL,
330 .remove_callback = nodes_remove_callback
331};
332
333static errno_t cdfs_node_get(fs_node_t **rfn, service_id_t service_id,
334 fs_index_t index)
335{
336 ht_key_t key = {
337 .index = index,
338 .service_id = service_id
339 };
340
341 ht_link_t *link = hash_table_find(&nodes, &key);
342 if (link) {
343 cdfs_node_t *node =
344 hash_table_get_inst(link, cdfs_node_t, nh_link);
345
346 *rfn = FS_NODE(node);
347 } else
348 *rfn = NULL;
349
350 return EOK;
351}
352
353static errno_t cdfs_root_get(fs_node_t **rfn, service_id_t service_id)
354{
355 return cdfs_node_get(rfn, service_id, CDFS_SOME_ROOT);
356}
357
358static void cdfs_node_initialize(cdfs_node_t *node)
359{
360 node->fs_node = NULL;
361 node->index = 0;
362 node->fs = NULL;
363 node->type = CDFS_NONE;
364 node->lnkcnt = 0;
365 node->size = 0;
366 node->lba = 0;
367 node->processed = false;
368 node->opened = 0;
369
370 list_initialize(&node->cs_list);
371}
372
373static errno_t create_node(fs_node_t **rfn, cdfs_t *fs, int lflag,
374 fs_index_t index)
375{
376 assert((lflag & L_FILE) ^ (lflag & L_DIRECTORY));
377
378 cdfs_node_t *node = malloc(sizeof(cdfs_node_t));
379 if (!node)
380 return ENOMEM;
381
382 cdfs_node_initialize(node);
383
384 node->fs_node = malloc(sizeof(fs_node_t));
385 if (!node->fs_node) {
386 free(node);
387 return ENOMEM;
388 }
389
390 fs_node_initialize(node->fs_node);
391 node->fs_node->data = node;
392
393 fs_node_t *rootfn;
394 errno_t rc = cdfs_root_get(&rootfn, fs->service_id);
395
396 assert(rc == EOK);
397
398 if (!rootfn)
399 node->index = CDFS_SOME_ROOT;
400 else
401 node->index = index;
402
403 node->fs = fs;
404
405 if (lflag & L_DIRECTORY)
406 node->type = CDFS_DIRECTORY;
407 else
408 node->type = CDFS_FILE;
409
410 /* Insert the new node into the nodes hash table. */
411 hash_table_insert(&nodes, &node->nh_link);
412
413 *rfn = FS_NODE(node);
414 nodes_cached++;
415
416 return EOK;
417}
418
419static errno_t link_node(fs_node_t *pfn, fs_node_t *fn, const char *name)
420{
421 cdfs_node_t *parent = CDFS_NODE(pfn);
422 cdfs_node_t *node = CDFS_NODE(fn);
423
424 assert(parent->type == CDFS_DIRECTORY);
425
426 /* Check for duplicate entries */
427 list_foreach(parent->cs_list, link, cdfs_dentry_t, dentry) {
428 if (str_cmp(dentry->name, name) == 0)
429 return EEXIST;
430 }
431
432 /* Allocate and initialize the dentry */
433 cdfs_dentry_t *dentry = malloc(sizeof(cdfs_dentry_t));
434 if (!dentry)
435 return ENOMEM;
436
437 /* Populate and link the new dentry */
438 dentry->name = str_dup(name);
439 if (dentry->name == NULL) {
440 free(dentry);
441 return ENOMEM;
442 }
443
444 link_initialize(&dentry->link);
445 dentry->index = node->index;
446
447 node->lnkcnt++;
448 list_append(&dentry->link, &parent->cs_list);
449
450 return EOK;
451}
452
453/** Decode CDFS string.
454 *
455 * @param data Pointer to string data
456 * @param dsize Size of data in bytes
457 * @param enc String encoding
458 * @return Decoded string
459 */
460static char *cdfs_decode_str(void *data, size_t dsize, cdfs_enc_t enc)
461{
462 errno_t rc;
463 char *str;
464 uint16_t *buf;
465
466 switch (enc) {
467 case enc_ascii:
468 str = malloc(dsize + 1);
469 if (str == NULL)
470 return NULL;
471 memcpy(str, data, dsize);
472 str[dsize] = '\0';
473 break;
474 case enc_ucs2:
475 buf = calloc(dsize + 2, 1);
476 if (buf == NULL)
477 return NULL;
478
479 size_t i;
480 for (i = 0; i < dsize / sizeof(uint16_t); i++) {
481 buf[i] = uint16_t_be2host(
482 ((unaligned_uint16_t *)data)[i]);
483 }
484
485 size_t dstr_size = dsize / sizeof(uint16_t) * 4 + 1;
486 str = malloc(dstr_size);
487 if (str == NULL)
488 return NULL;
489
490 rc = utf16_to_str(str, dstr_size, buf);
491 free(buf);
492
493 if (rc != EOK)
494 return NULL;
495 break;
496 default:
497 assert(false);
498 str = NULL;
499 }
500
501 return str;
502}
503
504/** Decode file name.
505 *
506 * @param data File name buffer
507 * @param dsize Fine name buffer size
508 * @param enc Encoding
509 * @param dtype Directory entry type
510 * @return Decoded file name (allocated string)
511 */
512static char *cdfs_decode_name(void *data, size_t dsize, cdfs_enc_t enc,
513 cdfs_dentry_type_t dtype)
514{
515 char *name;
516 char *dot;
517 char *scolon;
518
519 name = cdfs_decode_str(data, dsize, enc);
520 if (name == NULL)
521 return NULL;
522
523 if (dtype == CDFS_DIRECTORY)
524 return name;
525
526 dot = str_chr(name, '.');
527
528 if (dot != NULL) {
529 scolon = str_chr(dot, ';');
530 if (scolon != NULL) {
531 /* Trim version part */
532 *scolon = '\0';
533 }
534
535 /* If the extension is an empty string, trim the dot separator. */
536 if (dot[1] == '\0')
537 *dot = '\0';
538 }
539
540 return name;
541}
542
543/** Decode volume identifier.
544 *
545 * @param data Volume identifier buffer
546 * @param dsize Volume identifier buffer size
547 * @param enc Encoding
548 * @return Decoded volume identifier (allocated string)
549 */
550static char *cdfs_decode_vol_ident(void *data, size_t dsize, cdfs_enc_t enc)
551{
552 char *ident;
553 size_t i;
554
555 ident = cdfs_decode_str(data, dsize, enc);
556 if (ident == NULL)
557 return NULL;
558
559 /* Trim trailing spaces */
560 i = str_size(ident);
561 while (i > 0 && ident[i - 1] == ' ')
562 --i;
563 ident[i] = '\0';
564
565 return ident;
566}
567
568static errno_t cdfs_readdir(cdfs_t *fs, fs_node_t *fs_node)
569{
570 cdfs_node_t *node = CDFS_NODE(fs_node);
571 assert(node);
572
573 if (node->processed)
574 return EOK;
575
576 uint32_t blocks = node->size / BLOCK_SIZE;
577 if ((node->size % BLOCK_SIZE) != 0)
578 blocks++;
579
580 for (uint32_t i = 0; i < blocks; i++) {
581 block_t *block;
582 errno_t rc = block_get(&block, fs->service_id, node->lba + i, BLOCK_FLAGS_NONE);
583 if (rc != EOK)
584 return rc;
585
586 cdfs_dir_t *dir;
587
588 for (size_t offset = 0; offset < BLOCK_SIZE;
589 offset += dir->length) {
590 dir = (cdfs_dir_t *) (block->data + offset);
591 if (dir->length == 0)
592 break;
593 if (offset + dir->length > BLOCK_SIZE) {
594 /* XXX Incorrect FS structure */
595 break;
596 }
597
598 cdfs_dentry_type_t dentry_type;
599 if (dir->flags & DIR_FLAG_DIRECTORY)
600 dentry_type = CDFS_DIRECTORY;
601 else
602 dentry_type = CDFS_FILE;
603
604 /* Skip special entries */
605
606 if (dir->name_length == 1 &&
607 dir->name[0] == CDFS_NAME_CURDIR)
608 continue;
609 if (dir->name_length == 1 &&
610 dir->name[0] == CDFS_NAME_PARENTDIR)
611 continue;
612
613 // FIXME: hack - indexing by dentry byte offset on disc
614
615 fs_node_t *fn;
616 errno_t rc = create_node(&fn, fs, dentry_type,
617 (node->lba + i) * BLOCK_SIZE + offset);
618 if (rc != EOK)
619 return rc;
620
621 assert(fn != NULL);
622
623 cdfs_node_t *cur = CDFS_NODE(fn);
624 cur->lba = uint32_lb(dir->lba);
625 cur->size = uint32_lb(dir->size);
626
627 char *name = cdfs_decode_name(dir->name,
628 dir->name_length, node->fs->enc, dentry_type);
629 if (name == NULL)
630 return EIO;
631
632 // FIXME: check return value
633
634 link_node(fs_node, fn, name);
635 free(name);
636
637 if (dentry_type == CDFS_FILE)
638 cur->processed = true;
639 }
640
641 block_put(block);
642 }
643
644 node->processed = true;
645 return EOK;
646}
647
648static fs_node_t *get_uncached_node(cdfs_t *fs, fs_index_t index)
649{
650 cdfs_lba_t lba = index / BLOCK_SIZE;
651 size_t offset = index % BLOCK_SIZE;
652
653 block_t *block;
654 errno_t rc = block_get(&block, fs->service_id, lba, BLOCK_FLAGS_NONE);
655 if (rc != EOK)
656 return NULL;
657
658 cdfs_dir_t *dir = (cdfs_dir_t *) (block->data + offset);
659
660 cdfs_dentry_type_t dentry_type;
661 if (dir->flags & DIR_FLAG_DIRECTORY)
662 dentry_type = CDFS_DIRECTORY;
663 else
664 dentry_type = CDFS_FILE;
665
666 fs_node_t *fn;
667 rc = create_node(&fn, fs, dentry_type, index);
668 if ((rc != EOK) || (fn == NULL))
669 return NULL;
670
671 cdfs_node_t *node = CDFS_NODE(fn);
672 node->lba = uint32_lb(dir->lba);
673 node->size = uint32_lb(dir->size);
674 node->lnkcnt = 1;
675
676 if (dentry_type == CDFS_FILE)
677 node->processed = true;
678
679 block_put(block);
680 return fn;
681}
682
683static fs_node_t *get_cached_node(cdfs_t *fs, fs_index_t index)
684{
685 ht_key_t key = {
686 .index = index,
687 .service_id = fs->service_id
688 };
689
690 ht_link_t *link = hash_table_find(&nodes, &key);
691 if (link) {
692 cdfs_node_t *node =
693 hash_table_get_inst(link, cdfs_node_t, nh_link);
694 return FS_NODE(node);
695 }
696
697 return get_uncached_node(fs, index);
698}
699
700static errno_t cdfs_match(fs_node_t **fn, fs_node_t *pfn, const char *component)
701{
702 cdfs_node_t *parent = CDFS_NODE(pfn);
703
704 if (!parent->processed) {
705 errno_t rc = cdfs_readdir(parent->fs, pfn);
706 if (rc != EOK)
707 return rc;
708 }
709
710 list_foreach(parent->cs_list, link, cdfs_dentry_t, dentry) {
711 if (str_cmp(dentry->name, component) == 0) {
712 *fn = get_cached_node(parent->fs, dentry->index);
713 return EOK;
714 }
715 }
716
717 *fn = NULL;
718 return EOK;
719}
720
721static errno_t cdfs_node_open(fs_node_t *fn)
722{
723 cdfs_node_t *node = CDFS_NODE(fn);
724
725 if (!node->processed)
726 cdfs_readdir(node->fs, fn);
727
728 node->opened++;
729 return EOK;
730}
731
732static errno_t cdfs_node_put(fs_node_t *fn)
733{
734 /* Nothing to do */
735 return EOK;
736}
737
738static errno_t cdfs_create_node(fs_node_t **fn, service_id_t service_id, int lflag)
739{
740 /* Read-only */
741 return ENOTSUP;
742}
743
744static errno_t cdfs_destroy_node(fs_node_t *fn)
745{
746 /* Read-only */
747 return ENOTSUP;
748}
749
750static errno_t cdfs_link_node(fs_node_t *pfn, fs_node_t *cfn, const char *name)
751{
752 /* Read-only */
753 return ENOTSUP;
754}
755
756static errno_t cdfs_unlink_node(fs_node_t *pfn, fs_node_t *cfn, const char *name)
757{
758 /* Read-only */
759 return ENOTSUP;
760}
761
762static errno_t cdfs_has_children(bool *has_children, fs_node_t *fn)
763{
764 cdfs_node_t *node = CDFS_NODE(fn);
765
766 if ((node->type == CDFS_DIRECTORY) && (!node->processed))
767 cdfs_readdir(node->fs, fn);
768
769 *has_children = !list_empty(&node->cs_list);
770 return EOK;
771}
772
773static fs_index_t cdfs_index_get(fs_node_t *fn)
774{
775 cdfs_node_t *node = CDFS_NODE(fn);
776 return node->index;
777}
778
779static aoff64_t cdfs_size_get(fs_node_t *fn)
780{
781 cdfs_node_t *node = CDFS_NODE(fn);
782 return node->size;
783}
784
785static unsigned int cdfs_lnkcnt_get(fs_node_t *fn)
786{
787 cdfs_node_t *node = CDFS_NODE(fn);
788 return node->lnkcnt;
789}
790
791static bool cdfs_is_directory(fs_node_t *fn)
792{
793 cdfs_node_t *node = CDFS_NODE(fn);
794 return (node->type == CDFS_DIRECTORY);
795}
796
797static bool cdfs_is_file(fs_node_t *fn)
798{
799 cdfs_node_t *node = CDFS_NODE(fn);
800 return (node->type == CDFS_FILE);
801}
802
803static service_id_t cdfs_service_get(fs_node_t *fn)
804{
805 return 0;
806}
807
808static errno_t cdfs_size_block(service_id_t service_id, uint32_t *size)
809{
810 *size = BLOCK_SIZE;
811
812 return EOK;
813}
814
815static errno_t cdfs_total_block_count(service_id_t service_id, uint64_t *count)
816{
817 *count = 0;
818
819 return EOK;
820}
821
822static errno_t cdfs_free_block_count(service_id_t service_id, uint64_t *count)
823{
824 *count = 0;
825
826 return EOK;
827}
828
829libfs_ops_t cdfs_libfs_ops = {
830 .root_get = cdfs_root_get,
831 .match = cdfs_match,
832 .node_get = cdfs_node_get,
833 .node_open = cdfs_node_open,
834 .node_put = cdfs_node_put,
835 .create = cdfs_create_node,
836 .destroy = cdfs_destroy_node,
837 .link = cdfs_link_node,
838 .unlink = cdfs_unlink_node,
839 .has_children = cdfs_has_children,
840 .index_get = cdfs_index_get,
841 .size_get = cdfs_size_get,
842 .lnkcnt_get = cdfs_lnkcnt_get,
843 .is_directory = cdfs_is_directory,
844 .is_file = cdfs_is_file,
845 .service_get = cdfs_service_get,
846 .size_block = cdfs_size_block,
847 .total_block_count = cdfs_total_block_count,
848 .free_block_count = cdfs_free_block_count
849};
850
851/** Verify that escape sequence corresonds to one of the allowed encoding
852 * escape sequences allowed for Joliet.
853 */
854static errno_t cdfs_verify_joliet_esc_seq(uint8_t *seq)
855{
856 size_t i, j, k;
857 bool match;
858
859 i = 0;
860 while (i + ucs2_esc_seq_len <= 32) {
861 if (seq[i] == 0)
862 break;
863
864 for (j = 0; j < ucs2_esc_seq_no; j++) {
865 match = true;
866 for (k = 0; k < ucs2_esc_seq_len; k++)
867 if (seq[i + k] != ucs2_esc_seq[j][k])
868 match = false;
869 if (match) {
870 break;
871 }
872 }
873
874 if (!match)
875 return EINVAL;
876
877 i += ucs2_esc_seq_len;
878 }
879
880 while (i < 32) {
881 if (seq[i] != 0)
882 return EINVAL;
883 ++i;
884 }
885
886 return EOK;
887}
888
889/** Find Joliet supplementary volume descriptor.
890 *
891 * @param sid Block device service ID
892 * @param altroot First filesystem block
893 * @param rlba Place to store LBA of root dir
894 * @param rsize Place to store size of root dir
895 * @param vol_ident Place to store pointer to volume identifier
896 * @return EOK if found, ENOENT if not
897 */
898static errno_t cdfs_find_joliet_svd(service_id_t sid, cdfs_lba_t altroot,
899 uint32_t *rlba, uint32_t *rsize, char **vol_ident)
900{
901 cdfs_lba_t bi;
902
903 for (bi = altroot + 17; ; bi++) {
904 block_t *block;
905 errno_t rc = block_get(&block, sid, bi, BLOCK_FLAGS_NONE);
906 if (rc != EOK)
907 break;
908
909 cdfs_vol_desc_t *vol_desc = (cdfs_vol_desc_t *) block->data;
910
911 if (vol_desc->type == VOL_DESC_SET_TERMINATOR) {
912 block_put(block);
913 break;
914 }
915
916 if ((vol_desc->type != VOL_DESC_SUPPLEMENTARY) ||
917 (memcmp(vol_desc->standard_ident, CDFS_STANDARD_IDENT, 5) != 0) ||
918 (vol_desc->version != 1)) {
919 block_put(block);
920 continue;
921 }
922
923 uint16_t set_size = uint16_lb(vol_desc->data.prisec.set_size);
924 if (set_size > 1) {
925 /*
926 * Technically, we don't support multi-disc sets.
927 * But one can encounter erroneously mastered
928 * images in the wild and it might actually work
929 * for the first disc in the set.
930 */
931 }
932
933 uint16_t sequence_nr = uint16_lb(vol_desc->data.prisec.sequence_nr);
934 if (sequence_nr != 1) {
935 /*
936 * We only support the first disc
937 * in multi-disc sets.
938 */
939 block_put(block);
940 continue;
941 }
942
943 uint16_t block_size = uint16_lb(vol_desc->data.prisec.block_size);
944 if (block_size != BLOCK_SIZE) {
945 block_put(block);
946 continue;
947 }
948
949 rc = cdfs_verify_joliet_esc_seq(vol_desc->data.prisec.esc_seq);
950 if (rc != EOK)
951 continue;
952 *rlba = uint32_lb(vol_desc->data.prisec.root_dir.lba);
953 *rsize = uint32_lb(vol_desc->data.prisec.root_dir.size);
954
955 *vol_ident = cdfs_decode_vol_ident(vol_desc->data.prisec.ident,
956 32, enc_ucs2);
957
958 block_put(block);
959 return EOK;
960 }
961
962 return ENOENT;
963}
964
965/** Read the volume descriptors. */
966static errno_t iso_read_vol_desc(service_id_t sid, cdfs_lba_t altroot,
967 uint32_t *rlba, uint32_t *rsize, cdfs_enc_t *enc, char **vol_ident)
968{
969 /* First 16 blocks of isofs are empty */
970 block_t *block;
971 errno_t rc = block_get(&block, sid, altroot + 16, BLOCK_FLAGS_NONE);
972 if (rc != EOK)
973 return rc;
974
975 cdfs_vol_desc_t *vol_desc = (cdfs_vol_desc_t *) block->data;
976
977 /*
978 * Test for primary volume descriptor
979 * and standard compliance.
980 */
981 if ((vol_desc->type != VOL_DESC_PRIMARY) ||
982 (memcmp(vol_desc->standard_ident, CDFS_STANDARD_IDENT, 5) != 0) ||
983 (vol_desc->version != 1)) {
984 block_put(block);
985 return ENOTSUP;
986 }
987
988 uint16_t set_size = uint16_lb(vol_desc->data.prisec.set_size);
989 if (set_size > 1) {
990 /*
991 * Technically, we don't support multi-disc sets.
992 * But one can encounter erroneously mastered
993 * images in the wild and it might actually work
994 * for the first disc in the set.
995 */
996 }
997
998 uint16_t sequence_nr = uint16_lb(vol_desc->data.prisec.sequence_nr);
999 if (sequence_nr != 1) {
1000 /*
1001 * We only support the first disc
1002 * in multi-disc sets.
1003 */
1004 block_put(block);
1005 return ENOTSUP;
1006 }
1007
1008 uint16_t block_size = uint16_lb(vol_desc->data.prisec.block_size);
1009 if (block_size != BLOCK_SIZE) {
1010 block_put(block);
1011 return ENOTSUP;
1012 }
1013
1014 // TODO: implement path table support
1015
1016 /* Search for Joliet SVD */
1017
1018 uint32_t jrlba;
1019 uint32_t jrsize;
1020
1021 rc = cdfs_find_joliet_svd(sid, altroot, &jrlba, &jrsize, vol_ident);
1022 if (rc == EOK) {
1023 /* Found */
1024 *rlba = jrlba;
1025 *rsize = jrsize;
1026 *enc = enc_ucs2;
1027 } else {
1028 *rlba = uint32_lb(vol_desc->data.prisec.root_dir.lba);
1029 *rsize = uint32_lb(vol_desc->data.prisec.root_dir.size);
1030 *enc = enc_ascii;
1031 *vol_ident = cdfs_decode_vol_ident(vol_desc->data.prisec.ident,
1032 32, enc_ascii);
1033 }
1034
1035 block_put(block);
1036 return EOK;
1037}
1038
1039static errno_t iso_readfs(cdfs_t *fs, fs_node_t *rfn,
1040 cdfs_lba_t altroot)
1041{
1042 cdfs_node_t *node = CDFS_NODE(rfn);
1043
1044 errno_t rc = iso_read_vol_desc(fs->service_id, altroot, &node->lba,
1045 &node->size, &fs->enc, &fs->vol_ident);
1046 if (rc != EOK)
1047 return rc;
1048
1049 return cdfs_readdir(fs, rfn);
1050}
1051
1052/*
1053 * Mount a session with session start offset
1054 *
1055 */
1056static cdfs_t *cdfs_fs_create(service_id_t sid, cdfs_lba_t altroot)
1057{
1058 cdfs_t *fs = NULL;
1059 fs_node_t *rfn = NULL;
1060
1061 fs = calloc(1, sizeof(cdfs_t));
1062 if (fs == NULL)
1063 goto error;
1064
1065 fs->service_id = sid;
1066
1067 /* Create root node */
1068 errno_t rc = create_node(&rfn, fs, L_DIRECTORY, cdfs_index++);
1069
1070 if ((rc != EOK) || (!rfn))
1071 goto error;
1072
1073 /* FS root is not linked */
1074 CDFS_NODE(rfn)->lnkcnt = 0;
1075 CDFS_NODE(rfn)->lba = 0;
1076 CDFS_NODE(rfn)->processed = false;
1077
1078 /* Check if there is cdfs in given session */
1079 if (iso_readfs(fs, rfn, altroot) != EOK)
1080 goto error;
1081
1082 list_append(&fs->link, &cdfs_instances);
1083 return fs;
1084error:
1085 // XXX destroy node
1086 free(fs);
1087 return NULL;
1088}
1089
1090static errno_t cdfs_fsprobe(service_id_t service_id, vfs_fs_probe_info_t *info)
1091{
1092 char *vol_ident;
1093
1094 /* Initialize the block layer */
1095 errno_t rc = block_init(service_id, BLOCK_SIZE);
1096 if (rc != EOK)
1097 return rc;
1098
1099 cdfs_lba_t altroot = 0;
1100
1101 /*
1102 * Read TOC multisession information and get the start address
1103 * of the first track in the last session
1104 */
1105 scsi_toc_multisess_data_t toc;
1106
1107 rc = block_read_toc(service_id, 1, &toc, sizeof(toc));
1108 if (rc == EOK && (uint16_t_be2host(toc.toc_len) == 10))
1109 altroot = uint32_t_be2host(toc.ftrack_lsess.start_addr);
1110
1111 /* Initialize the block cache */
1112 rc = block_cache_init(service_id, BLOCK_SIZE, 0, CACHE_MODE_WT);
1113 if (rc != EOK) {
1114 block_fini(service_id);
1115 return rc;
1116 }
1117
1118 /* Check if this device is not already mounted */
1119 fs_node_t *rootfn;
1120 rc = cdfs_root_get(&rootfn, service_id);
1121 if ((rc == EOK) && (rootfn)) {
1122 cdfs_node_put(rootfn);
1123 block_cache_fini(service_id);
1124 block_fini(service_id);
1125 return EOK;
1126 }
1127
1128 /* Read volume descriptors */
1129 uint32_t rlba;
1130 uint32_t rsize;
1131 cdfs_enc_t enc;
1132 rc = iso_read_vol_desc(service_id, altroot, &rlba, &rsize, &enc,
1133 &vol_ident);
1134 if (rc != EOK) {
1135 block_cache_fini(service_id);
1136 block_fini(service_id);
1137 return rc;
1138 }
1139
1140 str_cpy(info->label, FS_LABEL_MAXLEN + 1, vol_ident);
1141 free(vol_ident);
1142
1143 block_cache_fini(service_id);
1144 block_fini(service_id);
1145 return EOK;
1146}
1147
1148static errno_t cdfs_mounted(service_id_t service_id, const char *opts,
1149 fs_index_t *index, aoff64_t *size)
1150{
1151 /* Initialize the block layer */
1152 errno_t rc = block_init(service_id, BLOCK_SIZE);
1153 if (rc != EOK)
1154 return rc;
1155
1156 cdfs_lba_t altroot = 0;
1157
1158 if (str_lcmp(opts, "altroot=", 8) == 0) {
1159 /* User-defined alternative root on a multi-session disk */
1160 if (str_uint32_t(opts + 8, NULL, 0, false, &altroot) != EOK)
1161 altroot = 0;
1162 } else {
1163 /*
1164 * Read TOC multisession information and get the start address
1165 * of the first track in the last session
1166 */
1167 scsi_toc_multisess_data_t toc;
1168
1169 rc = block_read_toc(service_id, 1, &toc, sizeof(toc));
1170 if (rc == EOK && (uint16_t_be2host(toc.toc_len) == 10))
1171 altroot = uint32_t_be2host(toc.ftrack_lsess.start_addr);
1172 }
1173
1174 /* Initialize the block cache */
1175 rc = block_cache_init(service_id, BLOCK_SIZE, 0, CACHE_MODE_WT);
1176 if (rc != EOK) {
1177 block_fini(service_id);
1178 return rc;
1179 }
1180
1181 /* Check if this device is not already mounted */
1182 fs_node_t *rootfn;
1183 rc = cdfs_root_get(&rootfn, service_id);
1184 if ((rc == EOK) && (rootfn)) {
1185 cdfs_node_put(rootfn);
1186 block_cache_fini(service_id);
1187 block_fini(service_id);
1188
1189 return EEXIST;
1190 }
1191
1192 /* Create cdfs instance */
1193 if (cdfs_fs_create(service_id, altroot) == NULL) {
1194 block_cache_fini(service_id);
1195 block_fini(service_id);
1196
1197 return ENOMEM;
1198 }
1199
1200 rc = cdfs_root_get(&rootfn, service_id);
1201 assert(rc == EOK);
1202
1203 cdfs_node_t *root = CDFS_NODE(rootfn);
1204 *index = root->index;
1205 *size = root->size;
1206
1207 return EOK;
1208}
1209
1210static bool rm_service_id_nodes(ht_link_t *item, void *arg)
1211{
1212 service_id_t service_id = *(service_id_t *)arg;
1213 cdfs_node_t *node = hash_table_get_inst(item, cdfs_node_t, nh_link);
1214
1215 if (node->fs->service_id == service_id) {
1216 hash_table_remove_item(&nodes, &node->nh_link);
1217 }
1218
1219 return true;
1220}
1221
1222static void cdfs_fs_destroy(cdfs_t *fs)
1223{
1224 list_remove(&fs->link);
1225 hash_table_apply(&nodes, rm_service_id_nodes, &fs->service_id);
1226 block_cache_fini(fs->service_id);
1227 block_fini(fs->service_id);
1228 free(fs->vol_ident);
1229 free(fs);
1230}
1231
1232static cdfs_t *cdfs_find_by_sid(service_id_t service_id)
1233{
1234 list_foreach(cdfs_instances, link, cdfs_t, fs) {
1235 if (fs->service_id == service_id)
1236 return fs;
1237 }
1238
1239 return NULL;
1240}
1241
1242static errno_t cdfs_unmounted(service_id_t service_id)
1243{
1244 cdfs_t *fs;
1245
1246 fs = cdfs_find_by_sid(service_id);
1247 if (fs == NULL)
1248 return ENOENT;
1249
1250 cdfs_fs_destroy(fs);
1251 return EOK;
1252}
1253
1254static errno_t cdfs_read(service_id_t service_id, fs_index_t index, aoff64_t pos,
1255 size_t *rbytes)
1256{
1257 ht_key_t key = {
1258 .index = index,
1259 .service_id = service_id
1260 };
1261
1262 ht_link_t *link = hash_table_find(&nodes, &key);
1263 if (link == NULL)
1264 return ENOENT;
1265
1266 cdfs_node_t *node =
1267 hash_table_get_inst(link, cdfs_node_t, nh_link);
1268
1269 if (!node->processed) {
1270 errno_t rc = cdfs_readdir(node->fs, FS_NODE(node));
1271 if (rc != EOK)
1272 return rc;
1273 }
1274
1275 ipc_call_t call;
1276 size_t len;
1277 if (!async_data_read_receive(&call, &len)) {
1278 async_answer_0(&call, EINVAL);
1279 return EINVAL;
1280 }
1281
1282 if (node->type == CDFS_FILE) {
1283 if (pos >= node->size) {
1284 *rbytes = 0;
1285 async_data_read_finalize(&call, NULL, 0);
1286 } else {
1287 cdfs_lba_t lba = pos / BLOCK_SIZE;
1288 size_t offset = pos % BLOCK_SIZE;
1289
1290 *rbytes = min(len, BLOCK_SIZE - offset);
1291 *rbytes = min(*rbytes, node->size - pos);
1292
1293 block_t *block;
1294 errno_t rc = block_get(&block, service_id, node->lba + lba,
1295 BLOCK_FLAGS_NONE);
1296 if (rc != EOK) {
1297 async_answer_0(&call, rc);
1298 return rc;
1299 }
1300
1301 async_data_read_finalize(&call, block->data + offset,
1302 *rbytes);
1303 rc = block_put(block);
1304 if (rc != EOK)
1305 return rc;
1306 }
1307 } else {
1308 link_t *link = list_nth(&node->cs_list, pos);
1309 if (link == NULL) {
1310 async_answer_0(&call, ENOENT);
1311 return ENOENT;
1312 }
1313
1314 cdfs_dentry_t *dentry =
1315 list_get_instance(link, cdfs_dentry_t, link);
1316
1317 *rbytes = 1;
1318 async_data_read_finalize(&call, dentry->name,
1319 str_size(dentry->name) + 1);
1320 }
1321
1322 return EOK;
1323}
1324
1325static errno_t cdfs_write(service_id_t service_id, fs_index_t index, aoff64_t pos,
1326 size_t *wbytes, aoff64_t *nsize)
1327{
1328 /*
1329 * As cdfs is a read-only filesystem,
1330 * the operation is not supported.
1331 */
1332
1333 return ENOTSUP;
1334}
1335
1336static errno_t cdfs_truncate(service_id_t service_id, fs_index_t index,
1337 aoff64_t size)
1338{
1339 /*
1340 * As cdfs is a read-only filesystem,
1341 * the operation is not supported.
1342 */
1343
1344 return ENOTSUP;
1345}
1346
1347static bool cache_remove_closed(ht_link_t *item, void *arg)
1348{
1349 size_t *premove_cnt = (size_t *)arg;
1350
1351 /* Some nodes were requested to be removed from the cache. */
1352 if (0 < *premove_cnt) {
1353 cdfs_node_t *node = hash_table_get_inst(item, cdfs_node_t, nh_link);
1354
1355 if (!node->opened) {
1356 hash_table_remove_item(&nodes, item);
1357
1358 --nodes_cached;
1359 --*premove_cnt;
1360 }
1361 }
1362
1363 /* Only continue if more nodes were requested to be removed. */
1364 return 0 < *premove_cnt;
1365}
1366
1367static void cleanup_cache(service_id_t service_id)
1368{
1369 if (nodes_cached > NODE_CACHE_SIZE) {
1370 size_t remove_cnt = nodes_cached - NODE_CACHE_SIZE;
1371
1372 if (0 < remove_cnt)
1373 hash_table_apply(&nodes, cache_remove_closed, &remove_cnt);
1374 }
1375}
1376
1377static errno_t cdfs_close(service_id_t service_id, fs_index_t index)
1378{
1379 /* Root node is always in memory */
1380 if (index == 0)
1381 return EOK;
1382
1383 ht_key_t key = {
1384 .index = index,
1385 .service_id = service_id
1386 };
1387
1388 ht_link_t *link = hash_table_find(&nodes, &key);
1389 if (link == 0)
1390 return ENOENT;
1391
1392 cdfs_node_t *node =
1393 hash_table_get_inst(link, cdfs_node_t, nh_link);
1394
1395 assert(node->opened > 0);
1396
1397 node->opened--;
1398 cleanup_cache(service_id);
1399
1400 return EOK;
1401}
1402
1403static errno_t cdfs_destroy(service_id_t service_id, fs_index_t index)
1404{
1405 /*
1406 * As cdfs is a read-only filesystem,
1407 * the operation is not supported.
1408 */
1409
1410 return ENOTSUP;
1411}
1412
1413static errno_t cdfs_sync(service_id_t service_id, fs_index_t index)
1414{
1415 /*
1416 * As cdfs is a read-only filesystem,
1417 * the sync operation is a no-op.
1418 */
1419
1420 return EOK;
1421}
1422
1423vfs_out_ops_t cdfs_ops = {
1424 .fsprobe = cdfs_fsprobe,
1425 .mounted = cdfs_mounted,
1426 .unmounted = cdfs_unmounted,
1427 .read = cdfs_read,
1428 .write = cdfs_write,
1429 .truncate = cdfs_truncate,
1430 .close = cdfs_close,
1431 .destroy = cdfs_destroy,
1432 .sync = cdfs_sync
1433};
1434
1435/** Initialize the cdfs server
1436 *
1437 */
1438bool cdfs_init(void)
1439{
1440 if (!hash_table_create(&nodes, 0, 0, &nodes_ops))
1441 return false;
1442
1443 return true;
1444}
1445
1446/**
1447 * @}
1448 */
Note: See TracBrowser for help on using the repository browser.