source: mainline/uspace/srv/fs/cdfs/cdfs_ops.c@ 0cebbac

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 0cebbac was 0cebbac, checked in by Jiri Svoboda <jiri@…>, 8 years ago

Restore CDFS definition of primary volume descriptor, with properly sized root directory entry.

  • Property mode set to 100644
File size: 30.9 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 fs
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 <malloc.h>
45#include <mem.h>
46#include <loc.h>
47#include <libfs.h>
48#include <errno.h>
49#include <block.h>
50#include <scsi/mmc.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(void *k)
288{
289 ht_key_t *key = (ht_key_t*)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(void *k, const ht_link_t *item)
300{
301 cdfs_node_t *node = hash_table_get_inst(item, cdfs_node_t, nh_link);
302 ht_key_t *key = (ht_key_t*)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 int 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 int 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 int 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 int 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 int 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 int 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 bool 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 true;
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 int rc = block_get(&block, fs->service_id, node->lba + i, BLOCK_FLAGS_NONE);
583 if (rc != EOK)
584 return false;
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 int rc = create_node(&fn, fs, dentry_type,
617 (node->lba + i) * BLOCK_SIZE + offset);
618 if ((rc != EOK) || (fn == NULL))
619 return false;
620
621 cdfs_node_t *cur = CDFS_NODE(fn);
622 cur->lba = uint32_lb(dir->lba);
623 cur->size = uint32_lb(dir->size);
624
625 char *name = cdfs_decode_name(dir->name,
626 dir->name_length, node->fs->enc, dentry_type);
627 if (name == NULL)
628 return false;
629
630 // FIXME: check return value
631
632 link_node(fs_node, fn, name);
633 free(name);
634
635 if (dentry_type == CDFS_FILE)
636 cur->processed = true;
637 }
638
639 block_put(block);
640 }
641
642 node->processed = true;
643 return true;
644}
645
646static fs_node_t *get_uncached_node(cdfs_t *fs, fs_index_t index)
647{
648 cdfs_lba_t lba = index / BLOCK_SIZE;
649 size_t offset = index % BLOCK_SIZE;
650
651 block_t *block;
652 int rc = block_get(&block, fs->service_id, lba, BLOCK_FLAGS_NONE);
653 if (rc != EOK)
654 return NULL;
655
656 cdfs_dir_t *dir = (cdfs_dir_t *) (block->data + offset);
657
658 cdfs_dentry_type_t dentry_type;
659 if (dir->flags & DIR_FLAG_DIRECTORY)
660 dentry_type = CDFS_DIRECTORY;
661 else
662 dentry_type = CDFS_FILE;
663
664 fs_node_t *fn;
665 rc = create_node(&fn, fs, dentry_type, index);
666 if ((rc != EOK) || (fn == NULL))
667 return NULL;
668
669 cdfs_node_t *node = CDFS_NODE(fn);
670 node->lba = uint32_lb(dir->lba);
671 node->size = uint32_lb(dir->size);
672 node->lnkcnt = 1;
673
674 if (dentry_type == CDFS_FILE)
675 node->processed = true;
676
677 block_put(block);
678 return fn;
679}
680
681static fs_node_t *get_cached_node(cdfs_t *fs, fs_index_t index)
682{
683 ht_key_t key = {
684 .index = index,
685 .service_id = fs->service_id
686 };
687
688 ht_link_t *link = hash_table_find(&nodes, &key);
689 if (link) {
690 cdfs_node_t *node =
691 hash_table_get_inst(link, cdfs_node_t, nh_link);
692 return FS_NODE(node);
693 }
694
695 return get_uncached_node(fs, index);
696}
697
698static int cdfs_match(fs_node_t **fn, fs_node_t *pfn, const char *component)
699{
700 cdfs_node_t *parent = CDFS_NODE(pfn);
701
702 if (!parent->processed) {
703 int rc = cdfs_readdir(parent->fs, pfn);
704 if (rc != EOK)
705 return rc;
706 }
707
708 list_foreach(parent->cs_list, link, cdfs_dentry_t, dentry) {
709 if (str_cmp(dentry->name, component) == 0) {
710 *fn = get_cached_node(parent->fs, dentry->index);
711 return EOK;
712 }
713 }
714
715 *fn = NULL;
716 return EOK;
717}
718
719static int cdfs_node_open(fs_node_t *fn)
720{
721 cdfs_node_t *node = CDFS_NODE(fn);
722
723 if (!node->processed)
724 cdfs_readdir(node->fs, fn);
725
726 node->opened++;
727 return EOK;
728}
729
730static int cdfs_node_put(fs_node_t *fn)
731{
732 /* Nothing to do */
733 return EOK;
734}
735
736static int cdfs_create_node(fs_node_t **fn, service_id_t service_id, int lflag)
737{
738 /* Read-only */
739 return ENOTSUP;
740}
741
742static int cdfs_destroy_node(fs_node_t *fn)
743{
744 /* Read-only */
745 return ENOTSUP;
746}
747
748static int cdfs_link_node(fs_node_t *pfn, fs_node_t *cfn, const char *name)
749{
750 /* Read-only */
751 return ENOTSUP;
752}
753
754static int cdfs_unlink_node(fs_node_t *pfn, fs_node_t *cfn, const char *name)
755{
756 /* Read-only */
757 return ENOTSUP;
758}
759
760static int cdfs_has_children(bool *has_children, fs_node_t *fn)
761{
762 cdfs_node_t *node = CDFS_NODE(fn);
763
764 if ((node->type == CDFS_DIRECTORY) && (!node->processed))
765 cdfs_readdir(node->fs, fn);
766
767 *has_children = !list_empty(&node->cs_list);
768 return EOK;
769}
770
771static fs_index_t cdfs_index_get(fs_node_t *fn)
772{
773 cdfs_node_t *node = CDFS_NODE(fn);
774 return node->index;
775}
776
777static aoff64_t cdfs_size_get(fs_node_t *fn)
778{
779 cdfs_node_t *node = CDFS_NODE(fn);
780 return node->size;
781}
782
783static unsigned int cdfs_lnkcnt_get(fs_node_t *fn)
784{
785 cdfs_node_t *node = CDFS_NODE(fn);
786 return node->lnkcnt;
787}
788
789static bool cdfs_is_directory(fs_node_t *fn)
790{
791 cdfs_node_t *node = CDFS_NODE(fn);
792 return (node->type == CDFS_DIRECTORY);
793}
794
795static bool cdfs_is_file(fs_node_t *fn)
796{
797 cdfs_node_t *node = CDFS_NODE(fn);
798 return (node->type == CDFS_FILE);
799}
800
801static service_id_t cdfs_service_get(fs_node_t *fn)
802{
803 return 0;
804}
805
806static int cdfs_size_block(service_id_t service_id, uint32_t *size)
807{
808 *size = BLOCK_SIZE;
809
810 return EOK;
811}
812
813static int cdfs_total_block_count(service_id_t service_id, uint64_t *count)
814{
815 *count = 0;
816
817 return EOK;
818}
819
820static int cdfs_free_block_count(service_id_t service_id, uint64_t *count)
821{
822 *count = 0;
823
824 return EOK;
825}
826
827libfs_ops_t cdfs_libfs_ops = {
828 .root_get = cdfs_root_get,
829 .match = cdfs_match,
830 .node_get = cdfs_node_get,
831 .node_open = cdfs_node_open,
832 .node_put = cdfs_node_put,
833 .create = cdfs_create_node,
834 .destroy = cdfs_destroy_node,
835 .link = cdfs_link_node,
836 .unlink = cdfs_unlink_node,
837 .has_children = cdfs_has_children,
838 .index_get = cdfs_index_get,
839 .size_get = cdfs_size_get,
840 .lnkcnt_get = cdfs_lnkcnt_get,
841 .is_directory = cdfs_is_directory,
842 .is_file = cdfs_is_file,
843 .service_get = cdfs_service_get,
844 .size_block = cdfs_size_block,
845 .total_block_count = cdfs_total_block_count,
846 .free_block_count = cdfs_free_block_count
847};
848
849/** Verify that escape sequence corresonds to one of the allowed encoding
850 * escape sequences allowed for Joliet. */
851static int cdfs_verify_joliet_esc_seq(uint8_t *seq)
852{
853 size_t i, j, k;
854 bool match;
855
856 i = 0;
857 while (i + ucs2_esc_seq_len <= 32) {
858 if (seq[i] == 0)
859 break;
860
861 for (j = 0; j < ucs2_esc_seq_no; j++) {
862 match = true;
863 for (k = 0; k < ucs2_esc_seq_len; k++)
864 if (seq[i + k] != ucs2_esc_seq[j][k])
865 match = false;
866 if (match) {
867 break;
868 }
869 }
870
871 if (!match)
872 return EINVAL;
873
874 i += ucs2_esc_seq_len;
875 }
876
877 while (i < 32) {
878 if (seq[i] != 0)
879 return EINVAL;
880 ++i;
881 }
882
883 return EOK;
884}
885
886/** Find Joliet supplementary volume descriptor.
887 *
888 * @param sid Block device service ID
889 * @param altroot First filesystem block
890 * @param rlba Place to store LBA of root dir
891 * @param rsize Place to store size of root dir
892 * @param vol_ident Place to store pointer to volume identifier
893 * @return EOK if found, ENOENT if not
894 */
895static int cdfs_find_joliet_svd(service_id_t sid, cdfs_lba_t altroot,
896 uint32_t *rlba, uint32_t *rsize, char **vol_ident)
897{
898 cdfs_lba_t bi;
899
900 for (bi = altroot + 17; ; bi++) {
901 block_t *block;
902 int rc = block_get(&block, sid, bi, BLOCK_FLAGS_NONE);
903 if (rc != EOK)
904 break;
905
906 cdfs_vol_desc_t *vol_desc = (cdfs_vol_desc_t *) block->data;
907
908 if (vol_desc->type == VOL_DESC_SET_TERMINATOR) {
909 block_put(block);
910 break;
911 }
912
913 if ((vol_desc->type != VOL_DESC_SUPPLEMENTARY) ||
914 (memcmp(vol_desc->standard_ident, CDFS_STANDARD_IDENT, 5) != 0) ||
915 (vol_desc->version != 1)) {
916 block_put(block);
917 continue;
918 }
919
920 uint16_t set_size = uint16_lb(vol_desc->data.prisec.set_size);
921 if (set_size > 1) {
922 /*
923 * Technically, we don't support multi-disc sets.
924 * But one can encounter erroneously mastered
925 * images in the wild and it might actually work
926 * for the first disc in the set.
927 */
928 }
929
930 uint16_t sequence_nr = uint16_lb(vol_desc->data.prisec.sequence_nr);
931 if (sequence_nr != 1) {
932 /*
933 * We only support the first disc
934 * in multi-disc sets.
935 */
936 block_put(block);
937 continue;
938 }
939
940 uint16_t block_size = uint16_lb(vol_desc->data.prisec.block_size);
941 if (block_size != BLOCK_SIZE) {
942 block_put(block);
943 continue;
944 }
945
946 rc = cdfs_verify_joliet_esc_seq(vol_desc->data.prisec.esc_seq);
947 if (rc != EOK)
948 continue;
949 *rlba = uint32_lb(vol_desc->data.prisec.root_dir.lba);
950 *rsize = uint32_lb(vol_desc->data.prisec.root_dir.size);
951
952 *vol_ident = cdfs_decode_vol_ident(vol_desc->data.prisec.ident,
953 32, enc_ucs2);
954
955 block_put(block);
956 return EOK;
957 }
958
959 return ENOENT;
960}
961
962/** Read the volume descriptors. */
963static bool iso_read_vol_desc(service_id_t sid, cdfs_lba_t altroot,
964 uint32_t *rlba, uint32_t *rsize, cdfs_enc_t *enc, char **vol_ident)
965{
966 /* First 16 blocks of isofs are empty */
967 block_t *block;
968 int rc = block_get(&block, sid, altroot + 16, BLOCK_FLAGS_NONE);
969 if (rc != EOK)
970 return false;
971
972 cdfs_vol_desc_t *vol_desc = (cdfs_vol_desc_t *) block->data;
973
974 /*
975 * Test for primary volume descriptor
976 * and standard compliance.
977 */
978 if ((vol_desc->type != VOL_DESC_PRIMARY) ||
979 (memcmp(vol_desc->standard_ident, CDFS_STANDARD_IDENT, 5) != 0) ||
980 (vol_desc->version != 1)) {
981 block_put(block);
982 return false;
983 }
984
985 uint16_t set_size = uint16_lb(vol_desc->data.prisec.set_size);
986 if (set_size > 1) {
987 /*
988 * Technically, we don't support multi-disc sets.
989 * But one can encounter erroneously mastered
990 * images in the wild and it might actually work
991 * for the first disc in the set.
992 */
993 }
994
995 uint16_t sequence_nr = uint16_lb(vol_desc->data.prisec.sequence_nr);
996 if (sequence_nr != 1) {
997 /*
998 * We only support the first disc
999 * in multi-disc sets.
1000 */
1001 block_put(block);
1002 return false;
1003 }
1004
1005 uint16_t block_size = uint16_lb(vol_desc->data.prisec.block_size);
1006 if (block_size != BLOCK_SIZE) {
1007 block_put(block);
1008 return false;
1009 }
1010
1011 // TODO: implement path table support
1012
1013 /* Search for Joliet SVD */
1014
1015 uint32_t jrlba;
1016 uint32_t jrsize;
1017
1018 rc = cdfs_find_joliet_svd(sid, altroot, &jrlba, &jrsize, vol_ident);
1019 if (rc == EOK) {
1020 /* Found */
1021 *rlba = jrlba;
1022 *rsize = jrsize;
1023 *enc = enc_ucs2;
1024 } else {
1025 *rlba = uint32_lb(vol_desc->data.prisec.root_dir.lba);
1026 *rsize = uint32_lb(vol_desc->data.prisec.root_dir.size);
1027 *enc = enc_ascii;
1028 *vol_ident = cdfs_decode_vol_ident(vol_desc->data.prisec.ident,
1029 32, enc_ascii);
1030 }
1031
1032 block_put(block);
1033 return true;
1034}
1035
1036static bool iso_readfs(cdfs_t *fs, fs_node_t *rfn,
1037 cdfs_lba_t altroot)
1038{
1039 cdfs_node_t *node = CDFS_NODE(rfn);
1040
1041 if (!iso_read_vol_desc(fs->service_id, altroot, &node->lba,
1042 &node->size, &fs->enc, &fs->vol_ident))
1043 return false;
1044
1045 return cdfs_readdir(fs, rfn);
1046}
1047
1048/* Mount a session with session start offset
1049 *
1050 */
1051static cdfs_t *cdfs_fs_create(service_id_t sid, cdfs_lba_t altroot)
1052{
1053 cdfs_t *fs = NULL;
1054 fs_node_t *rfn = NULL;
1055
1056 fs = calloc(1, sizeof(cdfs_t));
1057 if (fs == NULL)
1058 goto error;
1059
1060 fs->service_id = sid;
1061
1062 /* Create root node */
1063 int rc = create_node(&rfn, fs, L_DIRECTORY, cdfs_index++);
1064
1065 if ((rc != EOK) || (!rfn))
1066 goto error;
1067
1068 /* FS root is not linked */
1069 CDFS_NODE(rfn)->lnkcnt = 0;
1070 CDFS_NODE(rfn)->lba = 0;
1071 CDFS_NODE(rfn)->processed = false;
1072
1073 /* Check if there is cdfs in given session */
1074 if (!iso_readfs(fs, rfn, altroot))
1075 goto error;
1076
1077 list_append(&fs->link, &cdfs_instances);
1078 return fs;
1079error:
1080 // XXX destroy node
1081 free(fs);
1082 return NULL;
1083}
1084
1085static int cdfs_fsprobe(service_id_t service_id, vfs_fs_probe_info_t *info)
1086{
1087 char *vol_ident;
1088
1089 /* Initialize the block layer */
1090 int rc = block_init(service_id, BLOCK_SIZE);
1091 if (rc != EOK)
1092 return rc;
1093
1094 cdfs_lba_t altroot = 0;
1095
1096 /*
1097 * Read TOC multisession information and get the start address
1098 * of the first track in the last session
1099 */
1100 scsi_toc_multisess_data_t toc;
1101
1102 rc = block_read_toc(service_id, 1, &toc, sizeof(toc));
1103 if (rc == EOK && (uint16_t_be2host(toc.toc_len) == 10))
1104 altroot = uint32_t_be2host(toc.ftrack_lsess.start_addr);
1105
1106 /* Initialize the block cache */
1107 rc = block_cache_init(service_id, BLOCK_SIZE, 0, CACHE_MODE_WT);
1108 if (rc != EOK) {
1109 block_fini(service_id);
1110 return rc;
1111 }
1112
1113 /* Check if this device is not already mounted */
1114 fs_node_t *rootfn;
1115 rc = cdfs_root_get(&rootfn, service_id);
1116 if ((rc == EOK) && (rootfn)) {
1117 cdfs_node_put(rootfn);
1118 block_cache_fini(service_id);
1119 block_fini(service_id);
1120 return EOK;
1121 }
1122
1123 /* Read volume descriptors */
1124 uint32_t rlba;
1125 uint32_t rsize;
1126 cdfs_enc_t enc;
1127 if (!iso_read_vol_desc(service_id, altroot, &rlba, &rsize, &enc,
1128 &vol_ident)) {
1129 block_cache_fini(service_id);
1130 block_fini(service_id);
1131 return EIO;
1132 }
1133
1134 str_cpy(info->label, FS_LABEL_MAXLEN + 1, vol_ident);
1135 free(vol_ident);
1136
1137 block_cache_fini(service_id);
1138 block_fini(service_id);
1139 return EOK;
1140}
1141
1142static int cdfs_mounted(service_id_t service_id, const char *opts,
1143 fs_index_t *index, aoff64_t *size)
1144{
1145 /* Initialize the block layer */
1146 int rc = block_init(service_id, BLOCK_SIZE);
1147 if (rc != EOK)
1148 return rc;
1149
1150 cdfs_lba_t altroot = 0;
1151
1152 if (str_lcmp(opts, "altroot=", 8) == 0) {
1153 /* User-defined alternative root on a multi-session disk */
1154 if (str_uint32_t(opts + 8, NULL, 0, false, &altroot) != EOK)
1155 altroot = 0;
1156 } else {
1157 /*
1158 * Read TOC multisession information and get the start address
1159 * of the first track in the last session
1160 */
1161 scsi_toc_multisess_data_t toc;
1162
1163 rc = block_read_toc(service_id, 1, &toc, sizeof(toc));
1164 if (rc == EOK && (uint16_t_be2host(toc.toc_len) == 10))
1165 altroot = uint32_t_be2host(toc.ftrack_lsess.start_addr);
1166 }
1167
1168 /* Initialize the block cache */
1169 rc = block_cache_init(service_id, BLOCK_SIZE, 0, CACHE_MODE_WT);
1170 if (rc != EOK) {
1171 block_fini(service_id);
1172 return rc;
1173 }
1174
1175 /* Check if this device is not already mounted */
1176 fs_node_t *rootfn;
1177 rc = cdfs_root_get(&rootfn, service_id);
1178 if ((rc == EOK) && (rootfn)) {
1179 cdfs_node_put(rootfn);
1180 block_cache_fini(service_id);
1181 block_fini(service_id);
1182
1183 return EEXIST;
1184 }
1185
1186 /* Create cdfs instance */
1187 if (cdfs_fs_create(service_id, altroot) == NULL) {
1188 block_cache_fini(service_id);
1189 block_fini(service_id);
1190
1191 return ENOMEM;
1192 }
1193
1194 rc = cdfs_root_get(&rootfn, service_id);
1195 assert(rc == EOK);
1196
1197 cdfs_node_t *root = CDFS_NODE(rootfn);
1198 *index = root->index;
1199 *size = root->size;
1200
1201 return EOK;
1202}
1203
1204static bool rm_service_id_nodes(ht_link_t *item, void *arg)
1205{
1206 service_id_t service_id = *(service_id_t*)arg;
1207 cdfs_node_t *node = hash_table_get_inst(item, cdfs_node_t, nh_link);
1208
1209 if (node->fs->service_id == service_id) {
1210 hash_table_remove_item(&nodes, &node->nh_link);
1211 }
1212
1213 return true;
1214}
1215
1216static void cdfs_fs_destroy(cdfs_t *fs)
1217{
1218 list_remove(&fs->link);
1219 hash_table_apply(&nodes, rm_service_id_nodes, &fs->service_id);
1220 block_cache_fini(fs->service_id);
1221 block_fini(fs->service_id);
1222 free(fs->vol_ident);
1223 free(fs);
1224}
1225
1226static cdfs_t *cdfs_find_by_sid(service_id_t service_id)
1227{
1228 list_foreach(cdfs_instances, link, cdfs_t, fs) {
1229 if (fs->service_id == service_id)
1230 return fs;
1231 }
1232
1233 return NULL;
1234}
1235
1236static int cdfs_unmounted(service_id_t service_id)
1237{
1238 cdfs_t *fs;
1239
1240 fs = cdfs_find_by_sid(service_id);
1241 if (fs == NULL)
1242 return ENOENT;
1243
1244 cdfs_fs_destroy(fs);
1245 return EOK;
1246}
1247
1248static int cdfs_read(service_id_t service_id, fs_index_t index, aoff64_t pos,
1249 size_t *rbytes)
1250{
1251 ht_key_t key = {
1252 .index = index,
1253 .service_id = service_id
1254 };
1255
1256 ht_link_t *link = hash_table_find(&nodes, &key);
1257 if (link == NULL)
1258 return ENOENT;
1259
1260 cdfs_node_t *node =
1261 hash_table_get_inst(link, cdfs_node_t, nh_link);
1262
1263 if (!node->processed) {
1264 int rc = cdfs_readdir(node->fs, FS_NODE(node));
1265 if (rc != EOK)
1266 return rc;
1267 }
1268
1269 ipc_callid_t callid;
1270 size_t len;
1271 if (!async_data_read_receive(&callid, &len)) {
1272 async_answer_0(callid, EINVAL);
1273 return EINVAL;
1274 }
1275
1276 if (node->type == CDFS_FILE) {
1277 if (pos >= node->size) {
1278 *rbytes = 0;
1279 async_data_read_finalize(callid, NULL, 0);
1280 } else {
1281 cdfs_lba_t lba = pos / BLOCK_SIZE;
1282 size_t offset = pos % BLOCK_SIZE;
1283
1284 *rbytes = min(len, BLOCK_SIZE - offset);
1285 *rbytes = min(*rbytes, node->size - pos);
1286
1287 block_t *block;
1288 int rc = block_get(&block, service_id, node->lba + lba,
1289 BLOCK_FLAGS_NONE);
1290 if (rc != EOK) {
1291 async_answer_0(callid, rc);
1292 return rc;
1293 }
1294
1295 async_data_read_finalize(callid, block->data + offset,
1296 *rbytes);
1297 rc = block_put(block);
1298 if (rc != EOK)
1299 return rc;
1300 }
1301 } else {
1302 link_t *link = list_nth(&node->cs_list, pos);
1303 if (link == NULL) {
1304 async_answer_0(callid, ENOENT);
1305 return ENOENT;
1306 }
1307
1308 cdfs_dentry_t *dentry =
1309 list_get_instance(link, cdfs_dentry_t, link);
1310
1311 *rbytes = 1;
1312 async_data_read_finalize(callid, dentry->name,
1313 str_size(dentry->name) + 1);
1314 }
1315
1316 return EOK;
1317}
1318
1319static int cdfs_write(service_id_t service_id, fs_index_t index, aoff64_t pos,
1320 size_t *wbytes, aoff64_t *nsize)
1321{
1322 /*
1323 * As cdfs is a read-only filesystem,
1324 * the operation is not supported.
1325 */
1326
1327 return ENOTSUP;
1328}
1329
1330static int cdfs_truncate(service_id_t service_id, fs_index_t index,
1331 aoff64_t size)
1332{
1333 /*
1334 * As cdfs is a read-only filesystem,
1335 * the operation is not supported.
1336 */
1337
1338 return ENOTSUP;
1339}
1340
1341static bool cache_remove_closed(ht_link_t *item, void *arg)
1342{
1343 size_t *premove_cnt = (size_t*)arg;
1344
1345 /* Some nodes were requested to be removed from the cache. */
1346 if (0 < *premove_cnt) {
1347 cdfs_node_t *node = hash_table_get_inst(item, cdfs_node_t, nh_link);
1348
1349 if (!node->opened) {
1350 hash_table_remove_item(&nodes, item);
1351
1352 --nodes_cached;
1353 --*premove_cnt;
1354 }
1355 }
1356
1357 /* Only continue if more nodes were requested to be removed. */
1358 return 0 < *premove_cnt;
1359}
1360
1361static void cleanup_cache(service_id_t service_id)
1362{
1363 if (nodes_cached > NODE_CACHE_SIZE) {
1364 size_t remove_cnt = nodes_cached - NODE_CACHE_SIZE;
1365
1366 if (0 < remove_cnt)
1367 hash_table_apply(&nodes, cache_remove_closed, &remove_cnt);
1368 }
1369}
1370
1371static int cdfs_close(service_id_t service_id, fs_index_t index)
1372{
1373 /* Root node is always in memory */
1374 if (index == 0)
1375 return EOK;
1376
1377 ht_key_t key = {
1378 .index = index,
1379 .service_id = service_id
1380 };
1381
1382 ht_link_t *link = hash_table_find(&nodes, &key);
1383 if (link == 0)
1384 return ENOENT;
1385
1386 cdfs_node_t *node =
1387 hash_table_get_inst(link, cdfs_node_t, nh_link);
1388
1389 assert(node->opened > 0);
1390
1391 node->opened--;
1392 cleanup_cache(service_id);
1393
1394 return EOK;
1395}
1396
1397static int cdfs_destroy(service_id_t service_id, fs_index_t index)
1398{
1399 /*
1400 * As cdfs is a read-only filesystem,
1401 * the operation is not supported.
1402 */
1403
1404 return ENOTSUP;
1405}
1406
1407static int cdfs_sync(service_id_t service_id, fs_index_t index)
1408{
1409 /*
1410 * As cdfs is a read-only filesystem,
1411 * the sync operation is a no-op.
1412 */
1413
1414 return EOK;
1415}
1416
1417vfs_out_ops_t cdfs_ops = {
1418 .fsprobe = cdfs_fsprobe,
1419 .mounted = cdfs_mounted,
1420 .unmounted = cdfs_unmounted,
1421 .read = cdfs_read,
1422 .write = cdfs_write,
1423 .truncate = cdfs_truncate,
1424 .close = cdfs_close,
1425 .destroy = cdfs_destroy,
1426 .sync = cdfs_sync
1427};
1428
1429/** Initialize the cdfs server
1430 *
1431 */
1432bool cdfs_init(void)
1433{
1434 if (!hash_table_create(&nodes, 0, 0, &nodes_ops))
1435 return false;
1436
1437 return true;
1438}
1439
1440/**
1441 * @}
1442 */
Note: See TracBrowser for help on using the repository browser.