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_malloc (sizeof (struct grub_iso9660_data
));
277 int copy_voldesc
= 0;
279 /* Read the superblock. */
280 if (grub_disk_read (disk
, block
<< GRUB_ISO9660_LOG2_BLKSZ
, 0,
281 sizeof (struct grub_iso9660_primary_voldesc
),
284 grub_error (GRUB_ERR_BAD_FS
, "not a iso9660 filesystem");
288 if (grub_strncmp ((char *) voldesc
.voldesc
.magic
, "CD001", 5) != 0)
290 grub_error (GRUB_ERR_BAD_FS
, "not a iso9660 filesystem");
294 if (voldesc
.voldesc
.type
== GRUB_ISO9660_VOLDESC_PRIMARY
)
296 else if ((voldesc
.voldesc
.type
== GRUB_ISO9660_VOLDESC_SUPP
) &&
297 (voldesc
.escape
[0] == 0x25) && (voldesc
.escape
[1] == 0x2f) &&
298 ((voldesc
.escape
[2] == 0x40) || /* UCS-2 Level 1. */
299 (voldesc
.escape
[2] == 0x43) || /* UCS-2 Level 2. */
300 (voldesc
.escape
[2] == 0x45))) /* UCS-2 Level 3. */
307 grub_memcpy((char *) &data
->voldesc
, (char *) &voldesc
,
308 sizeof (struct grub_iso9660_primary_voldesc
));
311 } while (voldesc
.voldesc
.type
!= GRUB_ISO9660_VOLDESC_END
);
313 /* Read the system use area and test it to see if SUSP is
315 if (grub_disk_read (disk
, (grub_le_to_cpu32 (data
->voldesc
.rootdir
.first_sector
)
316 << GRUB_ISO9660_LOG2_BLKSZ
), 0,
317 sizeof (rootdir
), (char *) &rootdir
))
319 grub_error (GRUB_ERR_BAD_FS
, "not a iso9660 filesystem");
323 sua_pos
= (sizeof (rootdir
) + rootdir
.namelen
324 + (rootdir
.namelen
% 2) - 1);
325 sua_size
= rootdir
.len
- sua_pos
;
327 sua
= grub_malloc (sua_size
);
331 if (grub_disk_read (disk
, (grub_le_to_cpu32 (data
->voldesc
.rootdir
.first_sector
)
332 << GRUB_ISO9660_LOG2_BLKSZ
), sua_pos
,
335 grub_error (GRUB_ERR_BAD_FS
, "not a iso9660 filesystem");
339 entry
= (struct grub_iso9660_susp_entry
*) sua
;
341 /* Test if the SUSP protocol is used on this filesystem. */
342 if (grub_strncmp ((char *) entry
->sig
, "SP", 2) == 0)
344 /* The 2nd data byte stored how many bytes are skipped every time
345 to get to the SUA (System Usage Area). */
346 data
->susp_skip
= entry
->data
[2];
347 entry
= (struct grub_iso9660_susp_entry
*) ((char *) entry
+ entry
->len
);
349 /* Iterate over the entries in the SUA area to detect
351 if (grub_iso9660_susp_iterate (data
,
352 (grub_le_to_cpu32 (data
->voldesc
.rootdir
.first_sector
)
353 << GRUB_ISO9660_LOG2_BLKSZ
),
354 sua_pos
, sua_size
, susp_iterate
))
367 grub_iso9660_read_symlink (grub_fshelp_node_t node
)
369 struct grub_iso9660_dir dirent
;
375 auto void add_part (const char *part
, int len
);
376 auto grub_err_t
susp_iterate_sl (struct grub_iso9660_susp_entry
*);
378 /* Extend the symlink. */
379 void add_part (const char *part
, int len
)
381 int size
= grub_strlen (symlink
);
383 symlink
= grub_realloc (symlink
, size
+ len
+ 1);
387 grub_strncat (symlink
, part
, len
);
390 /* Read in a symlink. */
391 grub_err_t
susp_iterate_sl (struct grub_iso9660_susp_entry
*entry
)
393 if (grub_strncmp ("SL", (char *) entry
->sig
, 2) == 0)
395 unsigned int pos
= 1;
397 /* The symlink is not stored as a POSIX symlink, translate it. */
398 while (pos
< grub_le_to_cpu32 (entry
->len
))
406 /* The current position is the `Component Flag'. */
407 switch (entry
->data
[pos
] & 30)
411 /* The data on pos + 2 is the actual data, pos + 1
412 is the length. Both are part of the `Component
414 add_part ((char *) &entry
->data
[pos
+ 2],
415 entry
->data
[pos
+ 1]);
416 if ((entry
->data
[pos
] & 1))
434 /* In pos + 1 the length of the `Component Record' is
436 pos
+= entry
->data
[pos
+ 1] + 2;
439 /* Check if `grub_realloc' failed. */
447 if (grub_disk_read (node
->data
->disk
, node
->dir_blk
, node
->dir_off
,
448 sizeof (dirent
), (char *) &dirent
))
451 sua_off
= (sizeof (dirent
) + dirent
.namelen
+ 1 - (dirent
.namelen
% 2)
452 + node
->data
->susp_skip
);
453 sua_size
= dirent
.len
- sua_off
;
455 symlink
= grub_malloc (1);
461 if (grub_iso9660_susp_iterate (node
->data
, node
->dir_blk
,
462 node
->dir_off
+ sua_off
,
463 sua_size
, susp_iterate_sl
))
474 grub_iso9660_iterate_dir (grub_fshelp_node_t dir
,
476 (*hook
) (const char *filename
,
477 enum grub_fshelp_filetype filetype
,
478 grub_fshelp_node_t node
))
480 struct grub_iso9660_dir dirent
;
481 unsigned int offset
= 0;
483 int filename_alloc
= 0;
484 enum grub_fshelp_filetype type
;
486 auto grub_err_t
susp_iterate_dir (struct grub_iso9660_susp_entry
*);
488 grub_err_t
susp_iterate_dir (struct grub_iso9660_susp_entry
*entry
)
490 /* The filename in the rock ridge entry. */
491 if (grub_strncmp ("NM", (char *) entry
->sig
, 2) == 0)
493 /* The flags are stored at the data position 0, here the
494 filename type is stored. */
495 if (entry
->data
[0] & GRUB_ISO9660_RR_DOT
)
497 else if (entry
->data
[0] & GRUB_ISO9660_RR_DOTDOT
)
504 size
+= grub_strlen (filename
);
505 grub_realloc (filename
,
506 grub_strlen (filename
)
511 size
= entry
->len
- 5;
512 filename
= grub_malloc (size
+ 1);
516 grub_strncpy (filename
, (char *) &entry
->data
[1], size
);
517 filename
[size
] = '\0';
520 /* The mode information (st_mode). */
521 else if (grub_strncmp ((char *) entry
->sig
, "PX", 2) == 0)
523 /* At position 0 of the PX record the st_mode information is
524 stored (little-endian). */
525 grub_uint32_t mode
= ((entry
->data
[0] + (entry
->data
[1] << 8))
526 & GRUB_ISO9660_FSTYPE_MASK
);
530 case GRUB_ISO9660_FSTYPE_DIR
:
531 type
= GRUB_FSHELP_DIR
;
533 case GRUB_ISO9660_FSTYPE_REG
:
534 type
= GRUB_FSHELP_REG
;
536 case GRUB_ISO9660_FSTYPE_SYMLINK
:
537 type
= GRUB_FSHELP_SYMLINK
;
540 type
= GRUB_FSHELP_UNKNOWN
;
547 while (offset
< dir
->size
)
549 if (grub_disk_read (dir
->data
->disk
,
550 (dir
->blk
<< GRUB_ISO9660_LOG2_BLKSZ
)
551 + offset
/ GRUB_DISK_SECTOR_SIZE
,
552 offset
% GRUB_DISK_SECTOR_SIZE
,
553 sizeof (dirent
), (char *) &dirent
))
556 /* The end of the block, skip to the next one. */
559 offset
= (offset
/ GRUB_ISO9660_BLKSZ
+ 1) * GRUB_ISO9660_BLKSZ
;
564 char name
[dirent
.namelen
+ 1];
565 int nameoffset
= offset
+ sizeof (dirent
);
566 struct grub_fshelp_node
*node
;
567 int sua_off
= (sizeof (dirent
) + dirent
.namelen
+ 1
568 - (dirent
.namelen
% 2));;
569 int sua_size
= dirent
.len
- sua_off
;
571 sua_off
+= offset
+ dir
->data
->susp_skip
;
575 type
= GRUB_FSHELP_UNKNOWN
;
577 if (dir
->data
->rockridge
578 && grub_iso9660_susp_iterate (dir
->data
,
579 (dir
->blk
<< GRUB_ISO9660_LOG2_BLKSZ
)
581 / GRUB_DISK_SECTOR_SIZE
),
582 sua_off
% GRUB_DISK_SECTOR_SIZE
,
583 sua_size
, susp_iterate_dir
))
587 if (grub_disk_read (dir
->data
->disk
,
588 (dir
->blk
<< GRUB_ISO9660_LOG2_BLKSZ
)
589 + nameoffset
/ GRUB_DISK_SECTOR_SIZE
,
590 nameoffset
% GRUB_DISK_SECTOR_SIZE
,
591 dirent
.namelen
, (char *) name
))
594 node
= grub_malloc (sizeof (struct grub_fshelp_node
));
598 /* Setup a new node. */
599 node
->data
= dir
->data
;
600 node
->size
= grub_le_to_cpu32 (dirent
.size
);
601 node
->blk
= grub_le_to_cpu32 (dirent
.first_sector
);
602 node
->dir_blk
= ((dir
->blk
<< GRUB_ISO9660_LOG2_BLKSZ
)
603 + offset
/ GRUB_DISK_SECTOR_SIZE
);
604 node
->dir_off
= offset
% GRUB_DISK_SECTOR_SIZE
;
606 /* If the filetype was not stored using rockridge, use
607 whatever is stored in the iso9660 filesystem. */
608 if (type
== GRUB_FSHELP_UNKNOWN
)
610 if ((dirent
.flags
& 3) == 2)
611 type
= GRUB_FSHELP_DIR
;
613 type
= GRUB_FSHELP_REG
;
616 /* The filename was not stored in a rock ridge entry. Read it
617 from the iso9660 filesystem. */
620 name
[dirent
.namelen
] = '\0';
621 filename
= grub_strrchr (name
, ';');
625 if (dirent
.namelen
== 1 && name
[0] == 0)
627 else if (dirent
.namelen
== 1 && name
[0] == 1)
633 if (dir
->data
->joliet
)
638 filename
= grub_iso9660_convert_string
639 ((grub_uint16_t
*) oldname
, dirent
.namelen
>> 1);
647 if (hook (filename
, type
, node
))
650 grub_free (filename
);
654 grub_free (filename
);
657 offset
+= dirent
.len
;
666 grub_iso9660_dir (grub_device_t device
, const char *path
,
667 int (*hook
) (const char *filename
,
668 const struct grub_dirhook_info
*info
))
670 struct grub_iso9660_data
*data
= 0;
671 struct grub_fshelp_node rootnode
;
672 struct grub_fshelp_node
*foundnode
;
674 auto int NESTED_FUNC_ATTR
iterate (const char *filename
,
675 enum grub_fshelp_filetype filetype
,
676 grub_fshelp_node_t node
);
678 int NESTED_FUNC_ATTR
iterate (const char *filename
,
679 enum grub_fshelp_filetype filetype
,
680 grub_fshelp_node_t node
)
682 struct grub_dirhook_info info
;
683 grub_memset (&info
, 0, sizeof (info
));
684 info
.dir
= ((filetype
& GRUB_FSHELP_TYPE_MASK
) == GRUB_FSHELP_DIR
);
686 return hook (filename
, &info
);
689 grub_dl_ref (my_mod
);
691 data
= grub_iso9660_mount (device
->disk
);
695 rootnode
.data
= data
;
696 rootnode
.blk
= grub_le_to_cpu32 (data
->voldesc
.rootdir
.first_sector
);
697 rootnode
.size
= grub_le_to_cpu32 (data
->voldesc
.rootdir
.size
);
699 /* Use the fshelp function to traverse the path. */
700 if (grub_fshelp_find_file (path
, &rootnode
,
702 grub_iso9660_iterate_dir
,
703 grub_iso9660_read_symlink
,
707 /* List the files in the directory. */
708 grub_iso9660_iterate_dir (foundnode
, iterate
);
710 if (foundnode
!= &rootnode
)
711 grub_free (foundnode
);
716 grub_dl_unref (my_mod
);
722 /* Open a file named NAME and initialize FILE. */
724 grub_iso9660_open (struct grub_file
*file
, const char *name
)
726 struct grub_iso9660_data
*data
;
727 struct grub_fshelp_node rootnode
;
728 struct grub_fshelp_node
*foundnode
;
730 grub_dl_ref (my_mod
);
732 data
= grub_iso9660_mount (file
->device
->disk
);
736 rootnode
.data
= data
;
737 rootnode
.blk
= grub_le_to_cpu32 (data
->voldesc
.rootdir
.first_sector
);
738 rootnode
.size
= grub_le_to_cpu32 (data
->voldesc
.rootdir
.size
);
740 /* Use the fshelp function to traverse the path. */
741 if (grub_fshelp_find_file (name
, &rootnode
,
743 grub_iso9660_iterate_dir
,
744 grub_iso9660_read_symlink
,
748 data
->first_sector
= foundnode
->blk
;
749 data
->length
= foundnode
->size
;
752 file
->size
= foundnode
->size
;
758 grub_dl_unref (my_mod
);
767 grub_iso9660_read (grub_file_t file
, char *buf
, grub_size_t len
)
769 struct grub_iso9660_data
*data
=
770 (struct grub_iso9660_data
*) file
->data
;
772 /* XXX: The file is stored in as a single extent. */
773 data
->disk
->read_hook
= file
->read_hook
;
774 grub_disk_read (data
->disk
,
775 data
->first_sector
<< GRUB_ISO9660_LOG2_BLKSZ
,
778 data
->disk
->read_hook
= 0;
785 grub_iso9660_close (grub_file_t file
)
787 grub_free (file
->data
);
789 grub_dl_unref (my_mod
);
791 return GRUB_ERR_NONE
;
796 grub_iso9660_label (grub_device_t device
, char **label
)
798 struct grub_iso9660_data
*data
;
799 data
= grub_iso9660_mount (device
->disk
);
804 *label
= grub_iso9660_convert_string
805 ((grub_uint16_t
*) &data
->voldesc
.volname
, 16);
807 *label
= grub_strndup ((char *) data
->voldesc
.volname
, 32);
818 grub_iso9660_uuid (grub_device_t device
, char **uuid
)
820 struct grub_iso9660_data
*data
;
821 grub_disk_t disk
= device
->disk
;
823 grub_dl_ref (my_mod
);
825 data
= grub_iso9660_mount (disk
);
828 if (! data
->voldesc
.modified
.year
[0] && ! data
->voldesc
.modified
.year
[1]
829 && ! data
->voldesc
.modified
.year
[2] && ! data
->voldesc
.modified
.year
[3]
830 && ! data
->voldesc
.modified
.month
[0] && ! data
->voldesc
.modified
.month
[1]
831 && ! data
->voldesc
.modified
.day
[0] && ! data
->voldesc
.modified
.day
[1]
832 && ! data
->voldesc
.modified
.hour
[0] && ! data
->voldesc
.modified
.hour
[1]
833 && ! data
->voldesc
.modified
.minute
[0] && ! data
->voldesc
.modified
.minute
[1]
834 && ! data
->voldesc
.modified
.second
[0] && ! data
->voldesc
.modified
.second
[1]
835 && ! data
->voldesc
.modified
.hundredth
[0] && ! data
->voldesc
.modified
.hundredth
[1])
837 grub_error (GRUB_ERR_BAD_NUMBER
, "No creation date in filesystem to generate UUID.");
842 *uuid
= grub_malloc (sizeof ("YYYY-MM-DD-HH-mm-ss-hh"));
843 grub_sprintf (*uuid
, "%c%c%c%c-%c%c-%c%c-%c%c-%c%c-%c%c-%c%c",
844 data
->voldesc
.modified
.year
[0], data
->voldesc
.modified
.year
[1],
845 data
->voldesc
.modified
.year
[2], data
->voldesc
.modified
.year
[3],
846 data
->voldesc
.modified
.month
[0], data
->voldesc
.modified
.month
[1],
847 data
->voldesc
.modified
.day
[0], data
->voldesc
.modified
.day
[1],
848 data
->voldesc
.modified
.hour
[0], data
->voldesc
.modified
.hour
[1],
849 data
->voldesc
.modified
.minute
[0], data
->voldesc
.modified
.minute
[1],
850 data
->voldesc
.modified
.second
[0], data
->voldesc
.modified
.second
[1],
851 data
->voldesc
.modified
.hundredth
[0], data
->voldesc
.modified
.hundredth
[1]);
857 grub_dl_unref (my_mod
);
866 static struct grub_fs grub_iso9660_fs
=
869 .dir
= grub_iso9660_dir
,
870 .open
= grub_iso9660_open
,
871 .read
= grub_iso9660_read
,
872 .close
= grub_iso9660_close
,
873 .label
= grub_iso9660_label
,
874 .uuid
= grub_iso9660_uuid
,
878 GRUB_MOD_INIT(iso9660
)
880 grub_fs_register (&grub_iso9660_fs
);
884 GRUB_MOD_FINI(iso9660
)
886 grub_fs_unregister (&grub_iso9660_fs
);