1 /* iso9660.c - iso9660 implementation with extensions:
4 * GRUB -- GRand Unified Bootloader
5 * Copyright (C) 2004,2005,2006,2007,2008 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 struct grub_iso9660_date
73 grub_uint8_t month
[2];
76 grub_uint8_t minute
[2];
77 grub_uint8_t second
[2];
78 grub_uint8_t hundredth
[2];
80 } __attribute__ ((packed
));
82 /* The primary volume descriptor. Only little endian is used. */
83 struct grub_iso9660_primary_voldesc
85 struct grub_iso9660_voldesc voldesc
;
86 grub_uint8_t unused1
[33];
87 grub_uint8_t volname
[32];
88 grub_uint8_t unused2
[16];
89 grub_uint8_t escape
[32];
90 grub_uint8_t unused3
[12];
91 grub_uint32_t path_table_size
;
92 grub_uint8_t unused4
[4];
93 grub_uint32_t path_table
;
94 grub_uint8_t unused5
[12];
95 struct grub_iso9660_dir rootdir
;
96 grub_uint8_t unused6
[624];
97 struct grub_iso9660_date created
;
98 struct grub_iso9660_date modified
;
99 } __attribute__ ((packed
));
101 /* A single entry in the path table. */
102 struct grub_iso9660_path
105 grub_uint8_t sectors
;
106 grub_uint32_t first_sector
;
107 grub_uint16_t parentdir
;
108 grub_uint8_t name
[0];
109 } __attribute__ ((packed
));
111 /* An entry in the System Usage area of the directory entry. */
112 struct grub_iso9660_susp_entry
116 grub_uint8_t version
;
117 grub_uint8_t data
[0];
118 } __attribute__ ((packed
));
120 /* The CE entry. This is used to describe the next block where data
122 struct grub_iso9660_susp_ce
124 struct grub_iso9660_susp_entry entry
;
126 grub_uint32_t blk_be
;
128 grub_uint32_t off_be
;
130 grub_uint32_t len_be
;
131 } __attribute__ ((packed
));
133 struct grub_iso9660_data
135 struct grub_iso9660_primary_voldesc voldesc
;
137 unsigned int first_sector
;
144 struct grub_fshelp_node
146 struct grub_iso9660_data
*data
;
149 unsigned int dir_blk
;
150 unsigned int dir_off
;
153 static grub_dl_t my_mod
;
156 /* Iterate over the susp entries, starting with block SUA_BLOCK on the
157 offset SUA_POS with a size of SUA_SIZE bytes. Hook is called for
160 grub_iso9660_susp_iterate (struct grub_iso9660_data
*data
,
161 int sua_block
, int sua_pos
, int sua_size
,
163 (struct grub_iso9660_susp_entry
*entry
))
166 struct grub_iso9660_susp_entry
*entry
;
168 auto grub_err_t
load_sua (void);
170 /* Load a part of the System Usage Area. */
171 grub_err_t
load_sua (void)
173 sua
= grub_malloc (sua_size
);
177 if (grub_disk_read (data
->disk
, sua_block
, sua_pos
,
181 entry
= (struct grub_iso9660_susp_entry
*) sua
;
188 for (; (char *) entry
< (char *) sua
+ sua_size
- 1;
189 entry
= (struct grub_iso9660_susp_entry
*)
190 ((char *) entry
+ entry
->len
))
192 /* The last entry. */
193 if (grub_strncmp ((char *) entry
->sig
, "ST", 2) == 0)
196 /* Additional entries are stored elsewhere. */
197 if (grub_strncmp ((char *) entry
->sig
, "CE", 2) == 0)
199 struct grub_iso9660_susp_ce
*ce
;
201 ce
= (struct grub_iso9660_susp_ce
*) entry
;
202 sua_size
= grub_le_to_cpu32 (ce
->len
);
203 sua_pos
= grub_le_to_cpu32 (ce
->off
);
204 sua_block
= grub_le_to_cpu32 (ce
->blk
) << GRUB_ISO9660_LOG2_BLKSZ
;
223 grub_iso9660_convert_string (grub_uint16_t
*us
, int len
)
228 p
= grub_malloc (len
* 4 + 1);
232 for (i
=0; i
<len
; i
++)
233 us
[i
] = grub_be_to_cpu16 (us
[i
]);
235 *grub_utf16_to_utf8 ((grub_uint8_t
*) p
, us
, len
) = '\0';
240 static struct grub_iso9660_data
*
241 grub_iso9660_mount (grub_disk_t disk
)
243 struct grub_iso9660_data
*data
= 0;
244 struct grub_iso9660_dir rootdir
;
248 struct grub_iso9660_susp_entry
*entry
;
249 struct grub_iso9660_primary_voldesc voldesc
;
252 auto grub_err_t
susp_iterate (struct grub_iso9660_susp_entry
*);
254 grub_err_t
susp_iterate (struct grub_iso9660_susp_entry
*susp_entry
)
256 /* The "ER" entry is used to detect extensions. The
257 `IEEE_P1285' extension means Rock ridge. */
258 if (grub_strncmp ((char *) susp_entry
->sig
, "ER", 2) == 0)
266 data
= grub_zalloc (sizeof (struct grub_iso9660_data
));
275 int copy_voldesc
= 0;
277 /* Read the superblock. */
278 if (grub_disk_read (disk
, block
<< GRUB_ISO9660_LOG2_BLKSZ
, 0,
279 sizeof (struct grub_iso9660_primary_voldesc
),
282 grub_error (GRUB_ERR_BAD_FS
, "not a iso9660 filesystem");
286 if (grub_strncmp ((char *) voldesc
.voldesc
.magic
, "CD001", 5) != 0)
288 grub_error (GRUB_ERR_BAD_FS
, "not a iso9660 filesystem");
292 if (voldesc
.voldesc
.type
== GRUB_ISO9660_VOLDESC_PRIMARY
)
294 else if ((voldesc
.voldesc
.type
== GRUB_ISO9660_VOLDESC_SUPP
) &&
295 (voldesc
.escape
[0] == 0x25) && (voldesc
.escape
[1] == 0x2f) &&
296 ((voldesc
.escape
[2] == 0x40) || /* UCS-2 Level 1. */
297 (voldesc
.escape
[2] == 0x43) || /* UCS-2 Level 2. */
298 (voldesc
.escape
[2] == 0x45))) /* UCS-2 Level 3. */
305 grub_memcpy((char *) &data
->voldesc
, (char *) &voldesc
,
306 sizeof (struct grub_iso9660_primary_voldesc
));
309 } while (voldesc
.voldesc
.type
!= GRUB_ISO9660_VOLDESC_END
);
311 /* Read the system use area and test it to see if SUSP is
313 if (grub_disk_read (disk
, (grub_le_to_cpu32 (data
->voldesc
.rootdir
.first_sector
)
314 << GRUB_ISO9660_LOG2_BLKSZ
), 0,
315 sizeof (rootdir
), (char *) &rootdir
))
317 grub_error (GRUB_ERR_BAD_FS
, "not a iso9660 filesystem");
321 sua_pos
= (sizeof (rootdir
) + rootdir
.namelen
322 + (rootdir
.namelen
% 2) - 1);
323 sua_size
= rootdir
.len
- sua_pos
;
325 sua
= grub_malloc (sua_size
);
329 if (grub_disk_read (disk
, (grub_le_to_cpu32 (data
->voldesc
.rootdir
.first_sector
)
330 << GRUB_ISO9660_LOG2_BLKSZ
), sua_pos
,
333 grub_error (GRUB_ERR_BAD_FS
, "not a iso9660 filesystem");
337 entry
= (struct grub_iso9660_susp_entry
*) sua
;
339 /* Test if the SUSP protocol is used on this filesystem. */
340 if (grub_strncmp ((char *) entry
->sig
, "SP", 2) == 0)
342 /* The 2nd data byte stored how many bytes are skipped every time
343 to get to the SUA (System Usage Area). */
344 data
->susp_skip
= entry
->data
[2];
345 entry
= (struct grub_iso9660_susp_entry
*) ((char *) entry
+ entry
->len
);
347 /* Iterate over the entries in the SUA area to detect
349 if (grub_iso9660_susp_iterate (data
,
350 (grub_le_to_cpu32 (data
->voldesc
.rootdir
.first_sector
)
351 << GRUB_ISO9660_LOG2_BLKSZ
),
352 sua_pos
, sua_size
, susp_iterate
))
365 grub_iso9660_read_symlink (grub_fshelp_node_t node
)
367 struct grub_iso9660_dir dirent
;
373 auto void add_part (const char *part
, int len
);
374 auto grub_err_t
susp_iterate_sl (struct grub_iso9660_susp_entry
*);
376 /* Extend the symlink. */
377 void add_part (const char *part
, int len
)
379 int size
= grub_strlen (symlink
);
381 symlink
= grub_realloc (symlink
, size
+ len
+ 1);
385 grub_strncat (symlink
, part
, len
);
388 /* Read in a symlink. */
389 grub_err_t
susp_iterate_sl (struct grub_iso9660_susp_entry
*entry
)
391 if (grub_strncmp ("SL", (char *) entry
->sig
, 2) == 0)
393 unsigned int pos
= 1;
395 /* The symlink is not stored as a POSIX symlink, translate it. */
396 while (pos
< grub_le_to_cpu32 (entry
->len
))
404 /* The current position is the `Component Flag'. */
405 switch (entry
->data
[pos
] & 30)
409 /* The data on pos + 2 is the actual data, pos + 1
410 is the length. Both are part of the `Component
412 add_part ((char *) &entry
->data
[pos
+ 2],
413 entry
->data
[pos
+ 1]);
414 if ((entry
->data
[pos
] & 1))
432 /* In pos + 1 the length of the `Component Record' is
434 pos
+= entry
->data
[pos
+ 1] + 2;
437 /* Check if `grub_realloc' failed. */
445 if (grub_disk_read (node
->data
->disk
, node
->dir_blk
, node
->dir_off
,
446 sizeof (dirent
), (char *) &dirent
))
449 sua_off
= (sizeof (dirent
) + dirent
.namelen
+ 1 - (dirent
.namelen
% 2)
450 + node
->data
->susp_skip
);
451 sua_size
= dirent
.len
- sua_off
;
453 symlink
= grub_malloc (1);
459 if (grub_iso9660_susp_iterate (node
->data
, node
->dir_blk
,
460 node
->dir_off
+ sua_off
,
461 sua_size
, susp_iterate_sl
))
472 grub_iso9660_iterate_dir (grub_fshelp_node_t dir
,
474 (*hook
) (const char *filename
,
475 enum grub_fshelp_filetype filetype
,
476 grub_fshelp_node_t node
))
478 struct grub_iso9660_dir dirent
;
479 unsigned int offset
= 0;
481 int filename_alloc
= 0;
482 enum grub_fshelp_filetype type
;
484 auto grub_err_t
susp_iterate_dir (struct grub_iso9660_susp_entry
*);
486 grub_err_t
susp_iterate_dir (struct grub_iso9660_susp_entry
*entry
)
488 /* The filename in the rock ridge entry. */
489 if (grub_strncmp ("NM", (char *) entry
->sig
, 2) == 0)
491 /* The flags are stored at the data position 0, here the
492 filename type is stored. */
493 if (entry
->data
[0] & GRUB_ISO9660_RR_DOT
)
495 else if (entry
->data
[0] & GRUB_ISO9660_RR_DOTDOT
)
502 size
+= grub_strlen (filename
);
503 grub_realloc (filename
,
504 grub_strlen (filename
)
509 size
= entry
->len
- 5;
510 filename
= grub_zalloc (size
+ 1);
513 grub_strncpy (filename
, (char *) &entry
->data
[1], size
);
514 filename
[size
] = '\0';
517 /* The mode information (st_mode). */
518 else if (grub_strncmp ((char *) entry
->sig
, "PX", 2) == 0)
520 /* At position 0 of the PX record the st_mode information is
521 stored (little-endian). */
522 grub_uint32_t mode
= ((entry
->data
[0] + (entry
->data
[1] << 8))
523 & GRUB_ISO9660_FSTYPE_MASK
);
527 case GRUB_ISO9660_FSTYPE_DIR
:
528 type
= GRUB_FSHELP_DIR
;
530 case GRUB_ISO9660_FSTYPE_REG
:
531 type
= GRUB_FSHELP_REG
;
533 case GRUB_ISO9660_FSTYPE_SYMLINK
:
534 type
= GRUB_FSHELP_SYMLINK
;
537 type
= GRUB_FSHELP_UNKNOWN
;
544 while (offset
< dir
->size
)
546 if (grub_disk_read (dir
->data
->disk
,
547 (dir
->blk
<< GRUB_ISO9660_LOG2_BLKSZ
)
548 + offset
/ GRUB_DISK_SECTOR_SIZE
,
549 offset
% GRUB_DISK_SECTOR_SIZE
,
550 sizeof (dirent
), (char *) &dirent
))
553 /* The end of the block, skip to the next one. */
556 offset
= (offset
/ GRUB_ISO9660_BLKSZ
+ 1) * GRUB_ISO9660_BLKSZ
;
561 char name
[dirent
.namelen
+ 1];
562 int nameoffset
= offset
+ sizeof (dirent
);
563 struct grub_fshelp_node
*node
;
564 int sua_off
= (sizeof (dirent
) + dirent
.namelen
+ 1
565 - (dirent
.namelen
% 2));
566 int sua_size
= dirent
.len
- sua_off
;
568 sua_off
+= offset
+ dir
->data
->susp_skip
;
572 type
= GRUB_FSHELP_UNKNOWN
;
574 if (dir
->data
->rockridge
575 && grub_iso9660_susp_iterate (dir
->data
,
576 (dir
->blk
<< GRUB_ISO9660_LOG2_BLKSZ
)
578 / GRUB_DISK_SECTOR_SIZE
),
579 sua_off
% GRUB_DISK_SECTOR_SIZE
,
580 sua_size
, susp_iterate_dir
))
584 if (grub_disk_read (dir
->data
->disk
,
585 (dir
->blk
<< GRUB_ISO9660_LOG2_BLKSZ
)
586 + nameoffset
/ GRUB_DISK_SECTOR_SIZE
,
587 nameoffset
% GRUB_DISK_SECTOR_SIZE
,
588 dirent
.namelen
, (char *) name
))
591 node
= grub_malloc (sizeof (struct grub_fshelp_node
));
595 /* Setup a new node. */
596 node
->data
= dir
->data
;
597 node
->size
= grub_le_to_cpu32 (dirent
.size
);
598 node
->blk
= grub_le_to_cpu32 (dirent
.first_sector
);
599 node
->dir_blk
= ((dir
->blk
<< GRUB_ISO9660_LOG2_BLKSZ
)
600 + offset
/ GRUB_DISK_SECTOR_SIZE
);
601 node
->dir_off
= offset
% GRUB_DISK_SECTOR_SIZE
;
603 /* If the filetype was not stored using rockridge, use
604 whatever is stored in the iso9660 filesystem. */
605 if (type
== GRUB_FSHELP_UNKNOWN
)
607 if ((dirent
.flags
& 3) == 2)
608 type
= GRUB_FSHELP_DIR
;
610 type
= GRUB_FSHELP_REG
;
613 /* The filename was not stored in a rock ridge entry. Read it
614 from the iso9660 filesystem. */
617 name
[dirent
.namelen
] = '\0';
618 filename
= grub_strrchr (name
, ';');
622 if (dirent
.namelen
== 1 && name
[0] == 0)
624 else if (dirent
.namelen
== 1 && name
[0] == 1)
630 if (dir
->data
->joliet
)
635 filename
= grub_iso9660_convert_string
636 ((grub_uint16_t
*) oldname
, dirent
.namelen
>> 1);
644 if (hook (filename
, type
, node
))
647 grub_free (filename
);
651 grub_free (filename
);
654 offset
+= dirent
.len
;
663 grub_iso9660_dir (grub_device_t device
, const char *path
,
664 int (*hook
) (const char *filename
,
665 const struct grub_dirhook_info
*info
))
667 struct grub_iso9660_data
*data
= 0;
668 struct grub_fshelp_node rootnode
;
669 struct grub_fshelp_node
*foundnode
;
671 auto int NESTED_FUNC_ATTR
iterate (const char *filename
,
672 enum grub_fshelp_filetype filetype
,
673 grub_fshelp_node_t node
);
675 int NESTED_FUNC_ATTR
iterate (const char *filename
,
676 enum grub_fshelp_filetype filetype
,
677 grub_fshelp_node_t node
)
679 struct grub_dirhook_info info
;
680 grub_memset (&info
, 0, sizeof (info
));
681 info
.dir
= ((filetype
& GRUB_FSHELP_TYPE_MASK
) == GRUB_FSHELP_DIR
);
683 return hook (filename
, &info
);
686 grub_dl_ref (my_mod
);
688 data
= grub_iso9660_mount (device
->disk
);
692 rootnode
.data
= data
;
693 rootnode
.blk
= grub_le_to_cpu32 (data
->voldesc
.rootdir
.first_sector
);
694 rootnode
.size
= grub_le_to_cpu32 (data
->voldesc
.rootdir
.size
);
696 /* Use the fshelp function to traverse the path. */
697 if (grub_fshelp_find_file (path
, &rootnode
,
699 grub_iso9660_iterate_dir
,
700 grub_iso9660_read_symlink
,
704 /* List the files in the directory. */
705 grub_iso9660_iterate_dir (foundnode
, iterate
);
707 if (foundnode
!= &rootnode
)
708 grub_free (foundnode
);
713 grub_dl_unref (my_mod
);
719 /* Open a file named NAME and initialize FILE. */
721 grub_iso9660_open (struct grub_file
*file
, const char *name
)
723 struct grub_iso9660_data
*data
;
724 struct grub_fshelp_node rootnode
;
725 struct grub_fshelp_node
*foundnode
;
727 grub_dl_ref (my_mod
);
729 data
= grub_iso9660_mount (file
->device
->disk
);
733 rootnode
.data
= data
;
734 rootnode
.blk
= grub_le_to_cpu32 (data
->voldesc
.rootdir
.first_sector
);
735 rootnode
.size
= grub_le_to_cpu32 (data
->voldesc
.rootdir
.size
);
737 /* Use the fshelp function to traverse the path. */
738 if (grub_fshelp_find_file (name
, &rootnode
,
740 grub_iso9660_iterate_dir
,
741 grub_iso9660_read_symlink
,
745 data
->first_sector
= foundnode
->blk
;
746 data
->length
= foundnode
->size
;
749 file
->size
= foundnode
->size
;
755 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
);
786 grub_dl_unref (my_mod
);
788 return GRUB_ERR_NONE
;
793 grub_iso9660_label (grub_device_t device
, char **label
)
795 struct grub_iso9660_data
*data
;
796 data
= grub_iso9660_mount (device
->disk
);
801 *label
= grub_iso9660_convert_string
802 ((grub_uint16_t
*) &data
->voldesc
.volname
, 16);
804 *label
= grub_strndup ((char *) data
->voldesc
.volname
, 32);
815 grub_iso9660_uuid (grub_device_t device
, char **uuid
)
817 struct grub_iso9660_data
*data
;
818 grub_disk_t disk
= device
->disk
;
820 grub_dl_ref (my_mod
);
822 data
= grub_iso9660_mount (disk
);
825 if (! data
->voldesc
.modified
.year
[0] && ! data
->voldesc
.modified
.year
[1]
826 && ! data
->voldesc
.modified
.year
[2] && ! data
->voldesc
.modified
.year
[3]
827 && ! data
->voldesc
.modified
.month
[0] && ! data
->voldesc
.modified
.month
[1]
828 && ! data
->voldesc
.modified
.day
[0] && ! data
->voldesc
.modified
.day
[1]
829 && ! data
->voldesc
.modified
.hour
[0] && ! data
->voldesc
.modified
.hour
[1]
830 && ! data
->voldesc
.modified
.minute
[0] && ! data
->voldesc
.modified
.minute
[1]
831 && ! data
->voldesc
.modified
.second
[0] && ! data
->voldesc
.modified
.second
[1]
832 && ! data
->voldesc
.modified
.hundredth
[0] && ! data
->voldesc
.modified
.hundredth
[1])
834 grub_error (GRUB_ERR_BAD_NUMBER
, "No creation date in filesystem to generate UUID.");
839 *uuid
= grub_malloc (sizeof ("YYYY-MM-DD-HH-mm-ss-hh"));
840 grub_sprintf (*uuid
, "%c%c%c%c-%c%c-%c%c-%c%c-%c%c-%c%c-%c%c",
841 data
->voldesc
.modified
.year
[0], data
->voldesc
.modified
.year
[1],
842 data
->voldesc
.modified
.year
[2], data
->voldesc
.modified
.year
[3],
843 data
->voldesc
.modified
.month
[0], data
->voldesc
.modified
.month
[1],
844 data
->voldesc
.modified
.day
[0], data
->voldesc
.modified
.day
[1],
845 data
->voldesc
.modified
.hour
[0], data
->voldesc
.modified
.hour
[1],
846 data
->voldesc
.modified
.minute
[0], data
->voldesc
.modified
.minute
[1],
847 data
->voldesc
.modified
.second
[0], data
->voldesc
.modified
.second
[1],
848 data
->voldesc
.modified
.hundredth
[0], data
->voldesc
.modified
.hundredth
[1]);
854 grub_dl_unref (my_mod
);
863 static struct grub_fs grub_iso9660_fs
=
866 .dir
= grub_iso9660_dir
,
867 .open
= grub_iso9660_open
,
868 .read
= grub_iso9660_read
,
869 .close
= grub_iso9660_close
,
870 .label
= grub_iso9660_label
,
871 .uuid
= grub_iso9660_uuid
,
875 GRUB_MOD_INIT(iso9660
)
877 grub_fs_register (&grub_iso9660_fs
);
881 GRUB_MOD_FINI(iso9660
)
883 grub_fs_unregister (&grub_iso9660_fs
);