1 /* iso9660.c - iso9660 implementation with extensions:
4 * GRUB -- GRand Unified Bootloader
5 * Copyright (C) 2004,2005,2006,2007 Free Software Foundation, Inc.
7 * GRUB is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
12 * GRUB is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
22 #include <grub/file.h>
24 #include <grub/misc.h>
25 #include <grub/disk.h>
27 #include <grub/types.h>
28 #include <grub/fshelp.h>
30 #define GRUB_ISO9660_FSTYPE_DIR 0040000
31 #define GRUB_ISO9660_FSTYPE_REG 0100000
32 #define GRUB_ISO9660_FSTYPE_SYMLINK 0120000
33 #define GRUB_ISO9660_FSTYPE_MASK 0170000
35 #define GRUB_ISO9660_LOG2_BLKSZ 2
36 #define GRUB_ISO9660_BLKSZ 2048
38 #define GRUB_ISO9660_RR_DOT 2
39 #define GRUB_ISO9660_RR_DOTDOT 4
41 #define GRUB_ISO9660_VOLDESC_BOOT 0
42 #define GRUB_ISO9660_VOLDESC_PRIMARY 1
43 #define GRUB_ISO9660_VOLDESC_SUPP 2
44 #define GRUB_ISO9660_VOLDESC_PART 3
45 #define GRUB_ISO9660_VOLDESC_END 255
47 /* The head of a volume descriptor. */
48 struct grub_iso9660_voldesc
51 grub_uint8_t magic
[5];
53 } __attribute__ ((packed
));
55 /* A directory entry. */
56 struct grub_iso9660_dir
59 grub_uint8_t ext_sectors
;
60 grub_uint32_t first_sector
;
61 grub_uint32_t first_sector_be
;
63 grub_uint32_t size_be
;
64 grub_uint8_t unused1
[7];
66 grub_uint8_t unused2
[6];
68 } __attribute__ ((packed
));
70 /* The primary volume descriptor. Only little endian is used. */
71 struct grub_iso9660_primary_voldesc
73 struct grub_iso9660_voldesc voldesc
;
74 grub_uint8_t unused1
[33];
75 grub_uint8_t volname
[32];
76 grub_uint8_t unused2
[16];
77 grub_uint8_t escape
[32];
78 grub_uint8_t unused3
[12];
79 grub_uint32_t path_table_size
;
80 grub_uint8_t unused4
[4];
81 grub_uint32_t path_table
;
82 grub_uint8_t unused5
[12];
83 struct grub_iso9660_dir rootdir
;
84 } __attribute__ ((packed
));
86 /* A single entry in the path table. */
87 struct grub_iso9660_path
91 grub_uint32_t first_sector
;
92 grub_uint16_t parentdir
;
94 } __attribute__ ((packed
));
96 /* An entry in the System Usage area of the directory entry. */
97 struct grub_iso9660_susp_entry
101 grub_uint8_t version
;
102 grub_uint8_t data
[0];
103 } __attribute__ ((packed
));
105 /* The CE entry. This is used to describe the next block where data
107 struct grub_iso9660_susp_ce
109 struct grub_iso9660_susp_entry entry
;
111 grub_uint32_t blk_be
;
113 grub_uint32_t off_be
;
115 grub_uint32_t len_be
;
116 } __attribute__ ((packed
));
118 struct grub_iso9660_data
120 struct grub_iso9660_primary_voldesc voldesc
;
122 unsigned int first_sector
;
129 struct grub_fshelp_node
131 struct grub_iso9660_data
*data
;
134 unsigned int dir_blk
;
135 unsigned int dir_off
;
139 static grub_dl_t my_mod
;
143 /* Iterate over the susp entries, starting with block SUA_BLOCK on the
144 offset SUA_POS with a size of SUA_SIZE bytes. Hook is called for
147 grub_iso9660_susp_iterate (struct grub_iso9660_data
*data
,
148 int sua_block
, int sua_pos
, int sua_size
,
150 (struct grub_iso9660_susp_entry
*entry
))
153 struct grub_iso9660_susp_entry
*entry
;
155 auto grub_err_t
load_sua (void);
157 /* Load a part of the System Usage Area. */
158 grub_err_t
load_sua (void)
160 sua
= grub_malloc (sua_size
);
164 if (grub_disk_read (data
->disk
, sua_block
, sua_pos
,
168 entry
= (struct grub_iso9660_susp_entry
*) sua
;
175 for (; (char *) entry
< (char *) sua
+ sua_size
- 1;
176 entry
= (struct grub_iso9660_susp_entry
*)
177 ((char *) entry
+ entry
->len
))
179 /* The last entry. */
180 if (grub_strncmp ((char *) entry
->sig
, "ST", 2) == 0)
183 /* Additional entries are stored elsewhere. */
184 if (grub_strncmp ((char *) entry
->sig
, "CE", 2) == 0)
186 struct grub_iso9660_susp_ce
*ce
;
188 ce
= (struct grub_iso9660_susp_ce
*) entry
;
189 sua_size
= grub_le_to_cpu32 (ce
->len
);
190 sua_pos
= grub_le_to_cpu32 (ce
->off
);
191 sua_block
= grub_le_to_cpu32 (ce
->blk
) << GRUB_ISO9660_LOG2_BLKSZ
;
210 grub_iso9660_convert_string (grub_uint16_t
*us
, int len
)
215 p
= grub_malloc (len
* 4 + 1);
219 for (i
=0; i
<len
; i
++)
220 us
[i
] = grub_be_to_cpu16 (us
[i
]);
222 *grub_utf16_to_utf8 ((grub_uint8_t
*) p
, us
, len
) = '\0';
227 static struct grub_iso9660_data
*
228 grub_iso9660_mount (grub_disk_t disk
)
230 struct grub_iso9660_data
*data
= 0;
231 struct grub_iso9660_dir rootdir
;
235 struct grub_iso9660_susp_entry
*entry
;
236 struct grub_iso9660_primary_voldesc voldesc
;
239 auto grub_err_t
susp_iterate (struct grub_iso9660_susp_entry
*);
241 grub_err_t
susp_iterate (struct grub_iso9660_susp_entry
*susp_entry
)
243 /* The "ER" entry is used to detect extensions. The
244 `IEEE_P1285' extension means Rock ridge. */
245 if (grub_strncmp ((char *) susp_entry
->sig
, "ER", 2) == 0)
253 data
= grub_malloc (sizeof (struct grub_iso9660_data
));
264 int copy_voldesc
= 0;
266 /* Read the superblock. */
267 if (grub_disk_read (disk
, block
<< GRUB_ISO9660_LOG2_BLKSZ
, 0,
268 sizeof (struct grub_iso9660_primary_voldesc
),
271 grub_error (GRUB_ERR_BAD_FS
, "not a iso9660 filesystem");
275 if (grub_strncmp ((char *) voldesc
.voldesc
.magic
, "CD001", 5) != 0)
277 grub_error (GRUB_ERR_BAD_FS
, "not a iso9660 filesystem");
281 if (voldesc
.voldesc
.type
== GRUB_ISO9660_VOLDESC_PRIMARY
)
283 else if ((voldesc
.voldesc
.type
== GRUB_ISO9660_VOLDESC_SUPP
) &&
284 (voldesc
.escape
[0] == 0x25) && (voldesc
.escape
[1] == 0x2f) &&
285 ((voldesc
.escape
[2] == 0x40) || /* UCS-2 Level 1. */
286 (voldesc
.escape
[2] == 0x43) || /* UCS-2 Level 2. */
287 (voldesc
.escape
[2] == 0x45))) /* UCS-2 Level 3. */
294 grub_memcpy((char *) &data
->voldesc
, (char *) &voldesc
,
295 sizeof (struct grub_iso9660_primary_voldesc
));
298 } while (voldesc
.voldesc
.type
!= GRUB_ISO9660_VOLDESC_END
);
300 /* Read the system use area and test it to see if SUSP is
302 if (grub_disk_read (disk
, (grub_le_to_cpu32 (data
->voldesc
.rootdir
.first_sector
)
303 << GRUB_ISO9660_LOG2_BLKSZ
), 0,
304 sizeof (rootdir
), (char *) &rootdir
))
306 grub_error (GRUB_ERR_BAD_FS
, "not a iso9660 filesystem");
310 sua_pos
= (sizeof (rootdir
) + rootdir
.namelen
311 + (rootdir
.namelen
% 2) - 1);
312 sua_size
= rootdir
.len
- sua_pos
;
314 sua
= grub_malloc (sua_size
);
318 if (grub_disk_read (disk
, (grub_le_to_cpu32 (data
->voldesc
.rootdir
.first_sector
)
319 << GRUB_ISO9660_LOG2_BLKSZ
), sua_pos
,
322 grub_error (GRUB_ERR_BAD_FS
, "not a iso9660 filesystem");
326 entry
= (struct grub_iso9660_susp_entry
*) sua
;
328 /* Test if the SUSP protocol is used on this filesystem. */
329 if (grub_strncmp ((char *) entry
->sig
, "SP", 2) == 0)
331 /* The 2nd data byte stored how many bytes are skipped every time
332 to get to the SUA (System Usage Area). */
333 data
->susp_skip
= entry
->data
[2];
334 entry
= (struct grub_iso9660_susp_entry
*) ((char *) entry
+ entry
->len
);
336 /* Iterate over the entries in the SUA area to detect
338 if (grub_iso9660_susp_iterate (data
,
339 (grub_le_to_cpu32 (data
->voldesc
.rootdir
.first_sector
)
340 << GRUB_ISO9660_LOG2_BLKSZ
),
341 sua_pos
, sua_size
, susp_iterate
))
354 grub_iso9660_read_symlink (grub_fshelp_node_t node
)
356 struct grub_iso9660_dir dirent
;
362 auto void add_part (const char *part
, int len
);
363 auto grub_err_t
susp_iterate_sl (struct grub_iso9660_susp_entry
*);
365 /* Extend the symlink. */
366 void add_part (const char *part
, int len
)
368 int size
= grub_strlen (symlink
);
370 symlink
= grub_realloc (symlink
, size
+ len
+ 1);
374 grub_strncat (symlink
, part
, len
);
377 /* Read in a symlink. */
378 grub_err_t
susp_iterate_sl (struct grub_iso9660_susp_entry
*entry
)
380 if (grub_strncmp ("SL", (char *) entry
->sig
, 2) == 0)
382 unsigned int pos
= 1;
384 /* The symlink is not stored as a POSIX symlink, translate it. */
385 while (pos
< grub_le_to_cpu32 (entry
->len
))
393 /* The current position is the `Component Flag'. */
394 switch (entry
->data
[pos
] & 30)
398 /* The data on pos + 2 is the actual data, pos + 1
399 is the length. Both are part of the `Component
401 add_part ((char *) &entry
->data
[pos
+ 2],
402 entry
->data
[pos
+ 1]);
403 if ((entry
->data
[pos
] & 1))
421 /* In pos + 1 the length of the `Component Record' is
423 pos
+= entry
->data
[pos
+ 1] + 2;
426 /* Check if `grub_realloc' failed. */
434 if (grub_disk_read (node
->data
->disk
, node
->dir_blk
, node
->dir_off
,
435 sizeof (dirent
), (char *) &dirent
))
438 sua_off
= (sizeof (dirent
) + dirent
.namelen
+ 1 - (dirent
.namelen
% 2)
439 + node
->data
->susp_skip
);
440 sua_size
= dirent
.len
- sua_off
;
442 symlink
= grub_malloc (1);
448 if (grub_iso9660_susp_iterate (node
->data
, node
->dir_blk
,
449 node
->dir_off
+ sua_off
,
450 sua_size
, susp_iterate_sl
))
461 grub_iso9660_iterate_dir (grub_fshelp_node_t dir
,
463 (*hook
) (const char *filename
,
464 enum grub_fshelp_filetype filetype
,
465 grub_fshelp_node_t node
))
467 struct grub_iso9660_dir dirent
;
468 unsigned int offset
= 0;
470 int filename_alloc
= 0;
471 enum grub_fshelp_filetype type
;
473 auto grub_err_t
susp_iterate_dir (struct grub_iso9660_susp_entry
*);
475 grub_err_t
susp_iterate_dir (struct grub_iso9660_susp_entry
*entry
)
477 /* The filename in the rock ridge entry. */
478 if (grub_strncmp ("NM", (char *) entry
->sig
, 2) == 0)
480 /* The flags are stored at the data position 0, here the
481 filename type is stored. */
482 if (entry
->data
[0] & GRUB_ISO9660_RR_DOT
)
484 else if (entry
->data
[0] & GRUB_ISO9660_RR_DOTDOT
)
491 size
+= grub_strlen (filename
);
492 grub_realloc (filename
,
493 grub_strlen (filename
)
498 size
= entry
->len
- 5;
499 filename
= grub_malloc (size
+ 1);
503 grub_strncpy (filename
, (char *) &entry
->data
[1], size
);
504 filename
[size
] = '\0';
507 /* The mode information (st_mode). */
508 else if (grub_strncmp ((char *) entry
->sig
, "PX", 2) == 0)
510 /* At position 0 of the PX record the st_mode information is
512 grub_uint32_t mode
= ((*(grub_uint32_t
*) &entry
->data
[0])
513 & GRUB_ISO9660_FSTYPE_MASK
);
517 case GRUB_ISO9660_FSTYPE_DIR
:
518 type
= GRUB_FSHELP_DIR
;
520 case GRUB_ISO9660_FSTYPE_REG
:
521 type
= GRUB_FSHELP_REG
;
523 case GRUB_ISO9660_FSTYPE_SYMLINK
:
524 type
= GRUB_FSHELP_SYMLINK
;
527 type
= GRUB_FSHELP_UNKNOWN
;
534 while (offset
< dir
->size
)
536 if (grub_disk_read (dir
->data
->disk
,
537 (dir
->blk
<< GRUB_ISO9660_LOG2_BLKSZ
)
538 + offset
/ GRUB_DISK_SECTOR_SIZE
,
539 offset
% GRUB_DISK_SECTOR_SIZE
,
540 sizeof (dirent
), (char *) &dirent
))
543 /* The end of the block, skip to the next one. */
546 offset
= (offset
/ GRUB_ISO9660_BLKSZ
+ 1) * GRUB_ISO9660_BLKSZ
;
551 char name
[dirent
.namelen
+ 1];
552 int nameoffset
= offset
+ sizeof (dirent
);
553 struct grub_fshelp_node
*node
;
554 int sua_off
= (sizeof (dirent
) + dirent
.namelen
+ 1
555 - (dirent
.namelen
% 2));;
556 int sua_size
= dirent
.len
- sua_off
;
558 sua_off
+= offset
+ dir
->data
->susp_skip
;
562 type
= GRUB_FSHELP_UNKNOWN
;
564 if (dir
->data
->rockridge
565 && grub_iso9660_susp_iterate (dir
->data
,
566 (dir
->blk
<< GRUB_ISO9660_LOG2_BLKSZ
)
568 / GRUB_DISK_SECTOR_SIZE
),
569 sua_off
% GRUB_DISK_SECTOR_SIZE
,
570 sua_size
, susp_iterate_dir
))
574 if (grub_disk_read (dir
->data
->disk
,
575 (dir
->blk
<< GRUB_ISO9660_LOG2_BLKSZ
)
576 + nameoffset
/ GRUB_DISK_SECTOR_SIZE
,
577 nameoffset
% GRUB_DISK_SECTOR_SIZE
,
578 dirent
.namelen
, (char *) name
))
581 node
= grub_malloc (sizeof (struct grub_fshelp_node
));
585 /* Setup a new node. */
586 node
->data
= dir
->data
;
587 node
->size
= grub_le_to_cpu32 (dirent
.size
);
588 node
->blk
= grub_le_to_cpu32 (dirent
.first_sector
);
589 node
->dir_blk
= ((dir
->blk
<< GRUB_ISO9660_LOG2_BLKSZ
)
590 + offset
/ GRUB_DISK_SECTOR_SIZE
);
591 node
->dir_off
= offset
% GRUB_DISK_SECTOR_SIZE
;
593 /* If the filetype was not stored using rockridge, use
594 whatever is stored in the iso9660 filesystem. */
595 if (type
== GRUB_FSHELP_UNKNOWN
)
597 if ((dirent
.flags
& 3) == 2)
598 type
= GRUB_FSHELP_DIR
;
600 type
= GRUB_FSHELP_REG
;
603 /* The filename was not stored in a rock ridge entry. Read it
604 from the iso9660 filesystem. */
607 name
[dirent
.namelen
] = '\0';
608 filename
= grub_strrchr (name
, ';');
612 if (dirent
.namelen
== 1 && name
[0] == 0)
614 else if (dirent
.namelen
== 1 && name
[0] == 1)
620 if (dir
->data
->joliet
)
625 filename
= grub_iso9660_convert_string
626 ((grub_uint16_t
*) oldname
, dirent
.namelen
>> 1);
634 if (hook (filename
, type
, node
))
637 grub_free (filename
);
641 grub_free (filename
);
644 offset
+= dirent
.len
;
653 grub_iso9660_dir (grub_device_t device
, const char *path
,
654 int (*hook
) (const char *filename
, int dir
))
656 struct grub_iso9660_data
*data
= 0;
657 struct grub_fshelp_node rootnode
;
658 struct grub_fshelp_node
*foundnode
;
660 auto int NESTED_FUNC_ATTR
iterate (const char *filename
,
661 enum grub_fshelp_filetype filetype
,
662 grub_fshelp_node_t node
);
664 int NESTED_FUNC_ATTR
iterate (const char *filename
,
665 enum grub_fshelp_filetype filetype
,
666 grub_fshelp_node_t node
)
670 if (filetype
== GRUB_FSHELP_DIR
)
671 return hook (filename
, 1);
673 return hook (filename
, 0);
679 grub_dl_ref (my_mod
);
682 data
= grub_iso9660_mount (device
->disk
);
686 rootnode
.data
= data
;
687 rootnode
.blk
= grub_le_to_cpu32 (data
->voldesc
.rootdir
.first_sector
);
688 rootnode
.size
= grub_le_to_cpu32 (data
->voldesc
.rootdir
.size
);
690 /* Use the fshelp function to traverse the path. */
691 if (grub_fshelp_find_file (path
, &rootnode
,
693 grub_iso9660_iterate_dir
,
694 grub_iso9660_read_symlink
,
698 /* List the files in the directory. */
699 grub_iso9660_iterate_dir (foundnode
, iterate
);
701 if (foundnode
!= &rootnode
)
702 grub_free (foundnode
);
708 grub_dl_unref (my_mod
);
715 /* Open a file named NAME and initialize FILE. */
717 grub_iso9660_open (struct grub_file
*file
, const char *name
)
719 struct grub_iso9660_data
*data
;
720 struct grub_fshelp_node rootnode
;
721 struct grub_fshelp_node
*foundnode
;
724 grub_dl_ref (my_mod
);
727 data
= grub_iso9660_mount (file
->device
->disk
);
731 rootnode
.data
= data
;
732 rootnode
.blk
= grub_le_to_cpu32 (data
->voldesc
.rootdir
.first_sector
);
733 rootnode
.size
= grub_le_to_cpu32 (data
->voldesc
.rootdir
.size
);
735 /* Use the fshelp function to traverse the path. */
736 if (grub_fshelp_find_file (name
, &rootnode
,
738 grub_iso9660_iterate_dir
,
739 grub_iso9660_read_symlink
,
743 data
->first_sector
= foundnode
->blk
;
744 data
->length
= foundnode
->size
;
747 file
->size
= foundnode
->size
;
754 grub_dl_unref (my_mod
);
764 grub_iso9660_read (grub_file_t file
, char *buf
, grub_size_t len
)
766 struct grub_iso9660_data
*data
=
767 (struct grub_iso9660_data
*) file
->data
;
769 /* XXX: The file is stored in as a single extent. */
770 data
->disk
->read_hook
= file
->read_hook
;
771 grub_disk_read (data
->disk
,
772 data
->first_sector
<< GRUB_ISO9660_LOG2_BLKSZ
,
775 data
->disk
->read_hook
= 0;
782 grub_iso9660_close (grub_file_t file
)
784 grub_free (file
->data
);
787 grub_dl_unref (my_mod
);
790 return GRUB_ERR_NONE
;
795 grub_iso9660_label (grub_device_t device
, char **label
)
797 struct grub_iso9660_data
*data
;
798 data
= grub_iso9660_mount (device
->disk
);
803 *label
= grub_iso9660_convert_string
804 ((grub_uint16_t
*) &data
->voldesc
.volname
, 16);
806 *label
= grub_strndup ((char *) data
->voldesc
.volname
, 32);
817 static struct grub_fs grub_iso9660_fs
=
820 .dir
= grub_iso9660_dir
,
821 .open
= grub_iso9660_open
,
822 .read
= grub_iso9660_read
,
823 .close
= grub_iso9660_close
,
824 .label
= grub_iso9660_label
,
828 GRUB_MOD_INIT(iso9660
)
830 grub_fs_register (&grub_iso9660_fs
);
836 GRUB_MOD_FINI(iso9660
)
838 grub_fs_unregister (&grub_iso9660_fs
);