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 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
;
154 static grub_dl_t my_mod
;
158 /* Iterate over the susp entries, starting with block SUA_BLOCK on the
159 offset SUA_POS with a size of SUA_SIZE bytes. Hook is called for
162 grub_iso9660_susp_iterate (struct grub_iso9660_data
*data
,
163 int sua_block
, int sua_pos
, int sua_size
,
165 (struct grub_iso9660_susp_entry
*entry
))
168 struct grub_iso9660_susp_entry
*entry
;
170 auto grub_err_t
load_sua (void);
172 /* Load a part of the System Usage Area. */
173 grub_err_t
load_sua (void)
175 sua
= grub_malloc (sua_size
);
179 if (grub_disk_read (data
->disk
, sua_block
, sua_pos
,
183 entry
= (struct grub_iso9660_susp_entry
*) sua
;
190 for (; (char *) entry
< (char *) sua
+ sua_size
- 1;
191 entry
= (struct grub_iso9660_susp_entry
*)
192 ((char *) entry
+ entry
->len
))
194 /* The last entry. */
195 if (grub_strncmp ((char *) entry
->sig
, "ST", 2) == 0)
198 /* Additional entries are stored elsewhere. */
199 if (grub_strncmp ((char *) entry
->sig
, "CE", 2) == 0)
201 struct grub_iso9660_susp_ce
*ce
;
203 ce
= (struct grub_iso9660_susp_ce
*) entry
;
204 sua_size
= grub_le_to_cpu32 (ce
->len
);
205 sua_pos
= grub_le_to_cpu32 (ce
->off
);
206 sua_block
= grub_le_to_cpu32 (ce
->blk
) << GRUB_ISO9660_LOG2_BLKSZ
;
225 grub_iso9660_convert_string (grub_uint16_t
*us
, int len
)
230 p
= grub_malloc (len
* 4 + 1);
234 for (i
=0; i
<len
; i
++)
235 us
[i
] = grub_be_to_cpu16 (us
[i
]);
237 *grub_utf16_to_utf8 ((grub_uint8_t
*) p
, us
, len
) = '\0';
242 static struct grub_iso9660_data
*
243 grub_iso9660_mount (grub_disk_t disk
)
245 struct grub_iso9660_data
*data
= 0;
246 struct grub_iso9660_dir rootdir
;
250 struct grub_iso9660_susp_entry
*entry
;
251 struct grub_iso9660_primary_voldesc voldesc
;
254 auto grub_err_t
susp_iterate (struct grub_iso9660_susp_entry
*);
256 grub_err_t
susp_iterate (struct grub_iso9660_susp_entry
*susp_entry
)
258 /* The "ER" entry is used to detect extensions. The
259 `IEEE_P1285' extension means Rock ridge. */
260 if (grub_strncmp ((char *) susp_entry
->sig
, "ER", 2) == 0)
268 data
= grub_malloc (sizeof (struct grub_iso9660_data
));
279 int copy_voldesc
= 0;
281 /* Read the superblock. */
282 if (grub_disk_read (disk
, block
<< GRUB_ISO9660_LOG2_BLKSZ
, 0,
283 sizeof (struct grub_iso9660_primary_voldesc
),
286 grub_error (GRUB_ERR_BAD_FS
, "not a iso9660 filesystem");
290 if (grub_strncmp ((char *) voldesc
.voldesc
.magic
, "CD001", 5) != 0)
292 grub_error (GRUB_ERR_BAD_FS
, "not a iso9660 filesystem");
296 if (voldesc
.voldesc
.type
== GRUB_ISO9660_VOLDESC_PRIMARY
)
298 else if ((voldesc
.voldesc
.type
== GRUB_ISO9660_VOLDESC_SUPP
) &&
299 (voldesc
.escape
[0] == 0x25) && (voldesc
.escape
[1] == 0x2f) &&
300 ((voldesc
.escape
[2] == 0x40) || /* UCS-2 Level 1. */
301 (voldesc
.escape
[2] == 0x43) || /* UCS-2 Level 2. */
302 (voldesc
.escape
[2] == 0x45))) /* UCS-2 Level 3. */
309 grub_memcpy((char *) &data
->voldesc
, (char *) &voldesc
,
310 sizeof (struct grub_iso9660_primary_voldesc
));
313 } while (voldesc
.voldesc
.type
!= GRUB_ISO9660_VOLDESC_END
);
315 /* Read the system use area and test it to see if SUSP is
317 if (grub_disk_read (disk
, (grub_le_to_cpu32 (data
->voldesc
.rootdir
.first_sector
)
318 << GRUB_ISO9660_LOG2_BLKSZ
), 0,
319 sizeof (rootdir
), (char *) &rootdir
))
321 grub_error (GRUB_ERR_BAD_FS
, "not a iso9660 filesystem");
325 sua_pos
= (sizeof (rootdir
) + rootdir
.namelen
326 + (rootdir
.namelen
% 2) - 1);
327 sua_size
= rootdir
.len
- sua_pos
;
329 sua
= grub_malloc (sua_size
);
333 if (grub_disk_read (disk
, (grub_le_to_cpu32 (data
->voldesc
.rootdir
.first_sector
)
334 << GRUB_ISO9660_LOG2_BLKSZ
), sua_pos
,
337 grub_error (GRUB_ERR_BAD_FS
, "not a iso9660 filesystem");
341 entry
= (struct grub_iso9660_susp_entry
*) sua
;
343 /* Test if the SUSP protocol is used on this filesystem. */
344 if (grub_strncmp ((char *) entry
->sig
, "SP", 2) == 0)
346 /* The 2nd data byte stored how many bytes are skipped every time
347 to get to the SUA (System Usage Area). */
348 data
->susp_skip
= entry
->data
[2];
349 entry
= (struct grub_iso9660_susp_entry
*) ((char *) entry
+ entry
->len
);
351 /* Iterate over the entries in the SUA area to detect
353 if (grub_iso9660_susp_iterate (data
,
354 (grub_le_to_cpu32 (data
->voldesc
.rootdir
.first_sector
)
355 << GRUB_ISO9660_LOG2_BLKSZ
),
356 sua_pos
, sua_size
, susp_iterate
))
369 grub_iso9660_read_symlink (grub_fshelp_node_t node
)
371 struct grub_iso9660_dir dirent
;
377 auto void add_part (const char *part
, int len
);
378 auto grub_err_t
susp_iterate_sl (struct grub_iso9660_susp_entry
*);
380 /* Extend the symlink. */
381 void add_part (const char *part
, int len
)
383 int size
= grub_strlen (symlink
);
385 symlink
= grub_realloc (symlink
, size
+ len
+ 1);
389 grub_strncat (symlink
, part
, len
);
392 /* Read in a symlink. */
393 grub_err_t
susp_iterate_sl (struct grub_iso9660_susp_entry
*entry
)
395 if (grub_strncmp ("SL", (char *) entry
->sig
, 2) == 0)
397 unsigned int pos
= 1;
399 /* The symlink is not stored as a POSIX symlink, translate it. */
400 while (pos
< grub_le_to_cpu32 (entry
->len
))
408 /* The current position is the `Component Flag'. */
409 switch (entry
->data
[pos
] & 30)
413 /* The data on pos + 2 is the actual data, pos + 1
414 is the length. Both are part of the `Component
416 add_part ((char *) &entry
->data
[pos
+ 2],
417 entry
->data
[pos
+ 1]);
418 if ((entry
->data
[pos
] & 1))
436 /* In pos + 1 the length of the `Component Record' is
438 pos
+= entry
->data
[pos
+ 1] + 2;
441 /* Check if `grub_realloc' failed. */
449 if (grub_disk_read (node
->data
->disk
, node
->dir_blk
, node
->dir_off
,
450 sizeof (dirent
), (char *) &dirent
))
453 sua_off
= (sizeof (dirent
) + dirent
.namelen
+ 1 - (dirent
.namelen
% 2)
454 + node
->data
->susp_skip
);
455 sua_size
= dirent
.len
- sua_off
;
457 symlink
= grub_malloc (1);
463 if (grub_iso9660_susp_iterate (node
->data
, node
->dir_blk
,
464 node
->dir_off
+ sua_off
,
465 sua_size
, susp_iterate_sl
))
476 grub_iso9660_iterate_dir (grub_fshelp_node_t dir
,
478 (*hook
) (const char *filename
,
479 enum grub_fshelp_filetype filetype
,
480 grub_fshelp_node_t node
))
482 struct grub_iso9660_dir dirent
;
483 unsigned int offset
= 0;
485 int filename_alloc
= 0;
486 enum grub_fshelp_filetype type
;
488 auto grub_err_t
susp_iterate_dir (struct grub_iso9660_susp_entry
*);
490 grub_err_t
susp_iterate_dir (struct grub_iso9660_susp_entry
*entry
)
492 /* The filename in the rock ridge entry. */
493 if (grub_strncmp ("NM", (char *) entry
->sig
, 2) == 0)
495 /* The flags are stored at the data position 0, here the
496 filename type is stored. */
497 if (entry
->data
[0] & GRUB_ISO9660_RR_DOT
)
499 else if (entry
->data
[0] & GRUB_ISO9660_RR_DOTDOT
)
506 size
+= grub_strlen (filename
);
507 grub_realloc (filename
,
508 grub_strlen (filename
)
513 size
= entry
->len
- 5;
514 filename
= grub_malloc (size
+ 1);
518 grub_strncpy (filename
, (char *) &entry
->data
[1], size
);
519 filename
[size
] = '\0';
522 /* The mode information (st_mode). */
523 else if (grub_strncmp ((char *) entry
->sig
, "PX", 2) == 0)
525 /* At position 0 of the PX record the st_mode information is
527 grub_uint32_t mode
= ((*(grub_uint32_t
*) &entry
->data
[0])
528 & GRUB_ISO9660_FSTYPE_MASK
);
532 case GRUB_ISO9660_FSTYPE_DIR
:
533 type
= GRUB_FSHELP_DIR
;
535 case GRUB_ISO9660_FSTYPE_REG
:
536 type
= GRUB_FSHELP_REG
;
538 case GRUB_ISO9660_FSTYPE_SYMLINK
:
539 type
= GRUB_FSHELP_SYMLINK
;
542 type
= GRUB_FSHELP_UNKNOWN
;
549 while (offset
< dir
->size
)
551 if (grub_disk_read (dir
->data
->disk
,
552 (dir
->blk
<< GRUB_ISO9660_LOG2_BLKSZ
)
553 + offset
/ GRUB_DISK_SECTOR_SIZE
,
554 offset
% GRUB_DISK_SECTOR_SIZE
,
555 sizeof (dirent
), (char *) &dirent
))
558 /* The end of the block, skip to the next one. */
561 offset
= (offset
/ GRUB_ISO9660_BLKSZ
+ 1) * GRUB_ISO9660_BLKSZ
;
566 char name
[dirent
.namelen
+ 1];
567 int nameoffset
= offset
+ sizeof (dirent
);
568 struct grub_fshelp_node
*node
;
569 int sua_off
= (sizeof (dirent
) + dirent
.namelen
+ 1
570 - (dirent
.namelen
% 2));;
571 int sua_size
= dirent
.len
- sua_off
;
573 sua_off
+= offset
+ dir
->data
->susp_skip
;
577 type
= GRUB_FSHELP_UNKNOWN
;
579 if (dir
->data
->rockridge
580 && grub_iso9660_susp_iterate (dir
->data
,
581 (dir
->blk
<< GRUB_ISO9660_LOG2_BLKSZ
)
583 / GRUB_DISK_SECTOR_SIZE
),
584 sua_off
% GRUB_DISK_SECTOR_SIZE
,
585 sua_size
, susp_iterate_dir
))
589 if (grub_disk_read (dir
->data
->disk
,
590 (dir
->blk
<< GRUB_ISO9660_LOG2_BLKSZ
)
591 + nameoffset
/ GRUB_DISK_SECTOR_SIZE
,
592 nameoffset
% GRUB_DISK_SECTOR_SIZE
,
593 dirent
.namelen
, (char *) name
))
596 node
= grub_malloc (sizeof (struct grub_fshelp_node
));
600 /* Setup a new node. */
601 node
->data
= dir
->data
;
602 node
->size
= grub_le_to_cpu32 (dirent
.size
);
603 node
->blk
= grub_le_to_cpu32 (dirent
.first_sector
);
604 node
->dir_blk
= ((dir
->blk
<< GRUB_ISO9660_LOG2_BLKSZ
)
605 + offset
/ GRUB_DISK_SECTOR_SIZE
);
606 node
->dir_off
= offset
% GRUB_DISK_SECTOR_SIZE
;
608 /* If the filetype was not stored using rockridge, use
609 whatever is stored in the iso9660 filesystem. */
610 if (type
== GRUB_FSHELP_UNKNOWN
)
612 if ((dirent
.flags
& 3) == 2)
613 type
= GRUB_FSHELP_DIR
;
615 type
= GRUB_FSHELP_REG
;
618 /* The filename was not stored in a rock ridge entry. Read it
619 from the iso9660 filesystem. */
622 name
[dirent
.namelen
] = '\0';
623 filename
= grub_strrchr (name
, ';');
627 if (dirent
.namelen
== 1 && name
[0] == 0)
629 else if (dirent
.namelen
== 1 && name
[0] == 1)
635 if (dir
->data
->joliet
)
640 filename
= grub_iso9660_convert_string
641 ((grub_uint16_t
*) oldname
, dirent
.namelen
>> 1);
649 if (hook (filename
, type
, node
))
652 grub_free (filename
);
656 grub_free (filename
);
659 offset
+= dirent
.len
;
668 grub_iso9660_dir (grub_device_t device
, const char *path
,
669 int (*hook
) (const char *filename
, int dir
))
671 struct grub_iso9660_data
*data
= 0;
672 struct grub_fshelp_node rootnode
;
673 struct grub_fshelp_node
*foundnode
;
675 auto int NESTED_FUNC_ATTR
iterate (const char *filename
,
676 enum grub_fshelp_filetype filetype
,
677 grub_fshelp_node_t node
);
679 int NESTED_FUNC_ATTR
iterate (const char *filename
,
680 enum grub_fshelp_filetype filetype
,
681 grub_fshelp_node_t node
)
685 if (filetype
== GRUB_FSHELP_DIR
)
686 return hook (filename
, 1);
688 return hook (filename
, 0);
694 grub_dl_ref (my_mod
);
697 data
= grub_iso9660_mount (device
->disk
);
701 rootnode
.data
= data
;
702 rootnode
.blk
= grub_le_to_cpu32 (data
->voldesc
.rootdir
.first_sector
);
703 rootnode
.size
= grub_le_to_cpu32 (data
->voldesc
.rootdir
.size
);
705 /* Use the fshelp function to traverse the path. */
706 if (grub_fshelp_find_file (path
, &rootnode
,
708 grub_iso9660_iterate_dir
,
709 grub_iso9660_read_symlink
,
713 /* List the files in the directory. */
714 grub_iso9660_iterate_dir (foundnode
, iterate
);
716 if (foundnode
!= &rootnode
)
717 grub_free (foundnode
);
723 grub_dl_unref (my_mod
);
730 /* Open a file named NAME and initialize FILE. */
732 grub_iso9660_open (struct grub_file
*file
, const char *name
)
734 struct grub_iso9660_data
*data
;
735 struct grub_fshelp_node rootnode
;
736 struct grub_fshelp_node
*foundnode
;
739 grub_dl_ref (my_mod
);
742 data
= grub_iso9660_mount (file
->device
->disk
);
746 rootnode
.data
= data
;
747 rootnode
.blk
= grub_le_to_cpu32 (data
->voldesc
.rootdir
.first_sector
);
748 rootnode
.size
= grub_le_to_cpu32 (data
->voldesc
.rootdir
.size
);
750 /* Use the fshelp function to traverse the path. */
751 if (grub_fshelp_find_file (name
, &rootnode
,
753 grub_iso9660_iterate_dir
,
754 grub_iso9660_read_symlink
,
758 data
->first_sector
= foundnode
->blk
;
759 data
->length
= foundnode
->size
;
762 file
->size
= foundnode
->size
;
769 grub_dl_unref (my_mod
);
779 grub_iso9660_read (grub_file_t file
, char *buf
, grub_size_t len
)
781 struct grub_iso9660_data
*data
=
782 (struct grub_iso9660_data
*) file
->data
;
784 /* XXX: The file is stored in as a single extent. */
785 data
->disk
->read_hook
= file
->read_hook
;
786 grub_disk_read (data
->disk
,
787 data
->first_sector
<< GRUB_ISO9660_LOG2_BLKSZ
,
790 data
->disk
->read_hook
= 0;
797 grub_iso9660_close (grub_file_t file
)
799 grub_free (file
->data
);
802 grub_dl_unref (my_mod
);
805 return GRUB_ERR_NONE
;
810 grub_iso9660_label (grub_device_t device
, char **label
)
812 struct grub_iso9660_data
*data
;
813 data
= grub_iso9660_mount (device
->disk
);
818 *label
= grub_iso9660_convert_string
819 ((grub_uint16_t
*) &data
->voldesc
.volname
, 16);
821 *label
= grub_strndup ((char *) data
->voldesc
.volname
, 32);
832 grub_iso9660_uuid (grub_device_t device
, char **uuid
)
834 struct grub_iso9660_data
*data
;
835 grub_disk_t disk
= device
->disk
;
838 grub_dl_ref (my_mod
);
841 data
= grub_iso9660_mount (disk
);
844 if (! 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])
853 grub_error (GRUB_ERR_BAD_NUMBER
, "No creation date in filesystem to generate UUID.");
858 *uuid
= grub_malloc (sizeof ("YYYY-MM-DD-HH-mm-ss-hh"));
859 grub_sprintf (*uuid
, "%c%c%c%c-%c%c-%c%c-%c%c-%c%c-%c%c-%c%c",
860 data
->voldesc
.modified
.year
[0], data
->voldesc
.modified
.year
[1],
861 data
->voldesc
.modified
.year
[2], data
->voldesc
.modified
.year
[3],
862 data
->voldesc
.modified
.month
[0], data
->voldesc
.modified
.month
[1],
863 data
->voldesc
.modified
.day
[0], data
->voldesc
.modified
.day
[1],
864 data
->voldesc
.modified
.hour
[0], data
->voldesc
.modified
.hour
[1],
865 data
->voldesc
.modified
.minute
[0], data
->voldesc
.modified
.minute
[1],
866 data
->voldesc
.modified
.second
[0], data
->voldesc
.modified
.second
[1],
867 data
->voldesc
.modified
.hundredth
[0], data
->voldesc
.modified
.hundredth
[1]);
874 grub_dl_unref (my_mod
);
884 static struct grub_fs grub_iso9660_fs
=
887 .dir
= grub_iso9660_dir
,
888 .open
= grub_iso9660_open
,
889 .read
= grub_iso9660_read
,
890 .close
= grub_iso9660_close
,
891 .label
= grub_iso9660_label
,
892 .uuid
= grub_iso9660_uuid
,
896 GRUB_MOD_INIT(iso9660
)
898 grub_fs_register (&grub_iso9660_fs
);
904 GRUB_MOD_FINI(iso9660
)
906 grub_fs_unregister (&grub_iso9660_fs
);