/* iso9660.c - iso9660 implementation with extensions: SUSP, Rock Ridge. */ /* * GRUB -- GRand Unified Bootloader * Copyright (C) 2004,2005,2006,2007,2008,2009,2010 Free Software Foundation, Inc. * * GRUB is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * GRUB is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GRUB. If not, see . */ #include #include #include #include #include #include #include #include #include GRUB_MOD_LICENSE ("GPLv3+"); #define GRUB_ISO9660_FSTYPE_DIR 0040000 #define GRUB_ISO9660_FSTYPE_REG 0100000 #define GRUB_ISO9660_FSTYPE_SYMLINK 0120000 #define GRUB_ISO9660_FSTYPE_MASK 0170000 #define GRUB_ISO9660_LOG2_BLKSZ 2 #define GRUB_ISO9660_BLKSZ 2048 #define GRUB_ISO9660_RR_DOT 2 #define GRUB_ISO9660_RR_DOTDOT 4 #define GRUB_ISO9660_VOLDESC_BOOT 0 #define GRUB_ISO9660_VOLDESC_PRIMARY 1 #define GRUB_ISO9660_VOLDESC_SUPP 2 #define GRUB_ISO9660_VOLDESC_PART 3 #define GRUB_ISO9660_VOLDESC_END 255 /* The head of a volume descriptor. */ struct grub_iso9660_voldesc { grub_uint8_t type; grub_uint8_t magic[5]; grub_uint8_t version; } __attribute__ ((packed)); /* A directory entry. */ struct grub_iso9660_dir { grub_uint8_t len; grub_uint8_t ext_sectors; grub_uint32_t first_sector; grub_uint32_t first_sector_be; grub_uint32_t size; grub_uint32_t size_be; grub_uint8_t unused1[7]; grub_uint8_t flags; grub_uint8_t unused2[6]; grub_uint8_t namelen; } __attribute__ ((packed)); struct grub_iso9660_date { grub_uint8_t year[4]; grub_uint8_t month[2]; grub_uint8_t day[2]; grub_uint8_t hour[2]; grub_uint8_t minute[2]; grub_uint8_t second[2]; grub_uint8_t hundredth[2]; grub_uint8_t offset; } __attribute__ ((packed)); /* The primary volume descriptor. Only little endian is used. */ struct grub_iso9660_primary_voldesc { struct grub_iso9660_voldesc voldesc; grub_uint8_t unused1[33]; grub_uint8_t volname[32]; grub_uint8_t unused2[16]; grub_uint8_t escape[32]; grub_uint8_t unused3[12]; grub_uint32_t path_table_size; grub_uint8_t unused4[4]; grub_uint32_t path_table; grub_uint8_t unused5[12]; struct grub_iso9660_dir rootdir; grub_uint8_t unused6[624]; struct grub_iso9660_date created; struct grub_iso9660_date modified; } __attribute__ ((packed)); /* A single entry in the path table. */ struct grub_iso9660_path { grub_uint8_t len; grub_uint8_t sectors; grub_uint32_t first_sector; grub_uint16_t parentdir; grub_uint8_t name[0]; } __attribute__ ((packed)); /* An entry in the System Usage area of the directory entry. */ struct grub_iso9660_susp_entry { grub_uint8_t sig[2]; grub_uint8_t len; grub_uint8_t version; grub_uint8_t data[0]; } __attribute__ ((packed)); /* The CE entry. This is used to describe the next block where data can be found. */ struct grub_iso9660_susp_ce { struct grub_iso9660_susp_entry entry; grub_uint32_t blk; grub_uint32_t blk_be; grub_uint32_t off; grub_uint32_t off_be; grub_uint32_t len; grub_uint32_t len_be; } __attribute__ ((packed)); struct grub_iso9660_data { struct grub_iso9660_primary_voldesc voldesc; grub_disk_t disk; unsigned int first_sector; int rockridge; int susp_skip; int joliet; }; struct grub_fshelp_node { struct grub_iso9660_data *data; unsigned int size; unsigned int blk; unsigned int dir_blk; unsigned int dir_off; }; static grub_dl_t my_mod; /* Iterate over the susp entries, starting with block SUA_BLOCK on the offset SUA_POS with a size of SUA_SIZE bytes. Hook is called for every entry. */ static grub_err_t grub_iso9660_susp_iterate (struct grub_iso9660_data *data, int sua_block, int sua_pos, int sua_size, grub_err_t (*hook) (struct grub_iso9660_susp_entry *entry)) { char *sua; struct grub_iso9660_susp_entry *entry; auto grub_err_t load_sua (void); /* Load a part of the System Usage Area. */ grub_err_t load_sua (void) { sua = grub_malloc (sua_size); if (!sua) return grub_errno; if (grub_disk_read (data->disk, sua_block, sua_pos, sua_size, sua)) return grub_errno; entry = (struct grub_iso9660_susp_entry *) sua; return 0; } if (load_sua ()) return grub_errno; for (; (char *) entry < (char *) sua + sua_size - 1; entry = (struct grub_iso9660_susp_entry *) ((char *) entry + entry->len)) { /* The last entry. */ if (grub_strncmp ((char *) entry->sig, "ST", 2) == 0) break; /* Additional entries are stored elsewhere. */ if (grub_strncmp ((char *) entry->sig, "CE", 2) == 0) { struct grub_iso9660_susp_ce *ce; ce = (struct grub_iso9660_susp_ce *) entry; sua_size = grub_le_to_cpu32 (ce->len); sua_pos = grub_le_to_cpu32 (ce->off); sua_block = grub_le_to_cpu32 (ce->blk) << GRUB_ISO9660_LOG2_BLKSZ; grub_free (sua); if (load_sua ()) return grub_errno; } if (hook (entry)) { grub_free (sua); return 0; } } grub_free (sua); return 0; } static char * grub_iso9660_convert_string (grub_uint16_t *us, int len) { char *p; int i; p = grub_malloc (len * 4 + 1); if (! p) return p; for (i=0; isig, "ER", 2) == 0) { data->rockridge = 1; return 1; } return 0; } data = grub_zalloc (sizeof (struct grub_iso9660_data)); if (! data) return 0; data->disk = disk; block = 16; do { int copy_voldesc = 0; /* Read the superblock. */ if (grub_disk_read (disk, block << GRUB_ISO9660_LOG2_BLKSZ, 0, sizeof (struct grub_iso9660_primary_voldesc), (char *) &voldesc)) { grub_error (GRUB_ERR_BAD_FS, "not a ISO9660 filesystem"); goto fail; } if (grub_strncmp ((char *) voldesc.voldesc.magic, "CD001", 5) != 0) { grub_error (GRUB_ERR_BAD_FS, "not a ISO9660 filesystem"); goto fail; } if (voldesc.voldesc.type == GRUB_ISO9660_VOLDESC_PRIMARY) copy_voldesc = 1; else if ((voldesc.voldesc.type == GRUB_ISO9660_VOLDESC_SUPP) && (voldesc.escape[0] == 0x25) && (voldesc.escape[1] == 0x2f) && ((voldesc.escape[2] == 0x40) || /* UCS-2 Level 1. */ (voldesc.escape[2] == 0x43) || /* UCS-2 Level 2. */ (voldesc.escape[2] == 0x45))) /* UCS-2 Level 3. */ { copy_voldesc = 1; data->joliet = 1; } if (copy_voldesc) grub_memcpy((char *) &data->voldesc, (char *) &voldesc, sizeof (struct grub_iso9660_primary_voldesc)); block++; } while (voldesc.voldesc.type != GRUB_ISO9660_VOLDESC_END); /* Read the system use area and test it to see if SUSP is supported. */ if (grub_disk_read (disk, (grub_le_to_cpu32 (data->voldesc.rootdir.first_sector) << GRUB_ISO9660_LOG2_BLKSZ), 0, sizeof (rootdir), (char *) &rootdir)) { grub_error (GRUB_ERR_BAD_FS, "not a ISO9660 filesystem"); goto fail; } sua_pos = (sizeof (rootdir) + rootdir.namelen + (rootdir.namelen % 2) - 1); sua_size = rootdir.len - sua_pos; sua = grub_malloc (sua_size); if (! sua) goto fail; if (grub_disk_read (disk, (grub_le_to_cpu32 (data->voldesc.rootdir.first_sector) << GRUB_ISO9660_LOG2_BLKSZ), sua_pos, sua_size, sua)) { grub_error (GRUB_ERR_BAD_FS, "not a ISO9660 filesystem"); goto fail; } entry = (struct grub_iso9660_susp_entry *) sua; /* Test if the SUSP protocol is used on this filesystem. */ if (grub_strncmp ((char *) entry->sig, "SP", 2) == 0) { /* The 2nd data byte stored how many bytes are skipped every time to get to the SUA (System Usage Area). */ data->susp_skip = entry->data[2]; entry = (struct grub_iso9660_susp_entry *) ((char *) entry + entry->len); /* Iterate over the entries in the SUA area to detect extensions. */ if (grub_iso9660_susp_iterate (data, (grub_le_to_cpu32 (data->voldesc.rootdir.first_sector) << GRUB_ISO9660_LOG2_BLKSZ), sua_pos, sua_size, susp_iterate)) goto fail; } return data; fail: grub_free (data); return 0; } static char * grub_iso9660_read_symlink (grub_fshelp_node_t node) { struct grub_iso9660_dir dirent; int sua_off; int sua_size; char *symlink = 0; int addslash = 0; auto void add_part (const char *part, int len); auto grub_err_t susp_iterate_sl (struct grub_iso9660_susp_entry *); /* Extend the symlink. */ void add_part (const char *part, int len) { int size = grub_strlen (symlink); symlink = grub_realloc (symlink, size + len + 1); if (! symlink) return; grub_strncat (symlink, part, len); } /* Read in a symlink. */ grub_err_t susp_iterate_sl (struct grub_iso9660_susp_entry *entry) { if (grub_strncmp ("SL", (char *) entry->sig, 2) == 0) { unsigned int pos = 1; /* The symlink is not stored as a POSIX symlink, translate it. */ while (pos < grub_le_to_cpu32 (entry->len)) { if (addslash) { add_part ("/", 1); addslash = 0; } /* The current position is the `Component Flag'. */ switch (entry->data[pos] & 30) { case 0: { /* The data on pos + 2 is the actual data, pos + 1 is the length. Both are part of the `Component Record'. */ add_part ((char *) &entry->data[pos + 2], entry->data[pos + 1]); if ((entry->data[pos] & 1)) addslash = 1; break; } case 2: add_part ("./", 2); break; case 4: add_part ("../", 3); break; case 8: add_part ("/", 1); break; } /* In pos + 1 the length of the `Component Record' is stored. */ pos += entry->data[pos + 1] + 2; } /* Check if `grub_realloc' failed. */ if (grub_errno) return grub_errno; } return 0; } if (grub_disk_read (node->data->disk, node->dir_blk, node->dir_off, sizeof (dirent), (char *) &dirent)) return 0; sua_off = (sizeof (dirent) + dirent.namelen + 1 - (dirent.namelen % 2) + node->data->susp_skip); sua_size = dirent.len - sua_off; symlink = grub_malloc (1); if (!symlink) return 0; *symlink = '\0'; if (grub_iso9660_susp_iterate (node->data, node->dir_blk, node->dir_off + sua_off, sua_size, susp_iterate_sl)) { grub_free (symlink); return 0; } return symlink; } static int grub_iso9660_iterate_dir (grub_fshelp_node_t dir, int NESTED_FUNC_ATTR (*hook) (const char *filename, enum grub_fshelp_filetype filetype, grub_fshelp_node_t node)) { struct grub_iso9660_dir dirent; unsigned int offset = 0; char *filename; int filename_alloc = 0; enum grub_fshelp_filetype type; auto grub_err_t susp_iterate_dir (struct grub_iso9660_susp_entry *); grub_err_t susp_iterate_dir (struct grub_iso9660_susp_entry *entry) { /* The filename in the rock ridge entry. */ if (grub_strncmp ("NM", (char *) entry->sig, 2) == 0) { /* The flags are stored at the data position 0, here the filename type is stored. */ if (entry->data[0] & GRUB_ISO9660_RR_DOT) filename = "."; else if (entry->data[0] & GRUB_ISO9660_RR_DOTDOT) filename = ".."; else { int size = 1; if (filename) { size += grub_strlen (filename); grub_realloc (filename, grub_strlen (filename) + entry->len); } else { size = entry->len - 5; filename = grub_zalloc (size + 1); } filename_alloc = 1; grub_strncpy (filename, (char *) &entry->data[1], size); filename[size] = '\0'; } } /* The mode information (st_mode). */ else if (grub_strncmp ((char *) entry->sig, "PX", 2) == 0) { /* At position 0 of the PX record the st_mode information is stored (little-endian). */ grub_uint32_t mode = ((entry->data[0] + (entry->data[1] << 8)) & GRUB_ISO9660_FSTYPE_MASK); switch (mode) { case GRUB_ISO9660_FSTYPE_DIR: type = GRUB_FSHELP_DIR; break; case GRUB_ISO9660_FSTYPE_REG: type = GRUB_FSHELP_REG; break; case GRUB_ISO9660_FSTYPE_SYMLINK: type = GRUB_FSHELP_SYMLINK; break; default: type = GRUB_FSHELP_UNKNOWN; } } return 0; } while (offset < dir->size) { if (grub_disk_read (dir->data->disk, (dir->blk << GRUB_ISO9660_LOG2_BLKSZ) + offset / GRUB_DISK_SECTOR_SIZE, offset % GRUB_DISK_SECTOR_SIZE, sizeof (dirent), (char *) &dirent)) return 0; /* The end of the block, skip to the next one. */ if (!dirent.len) { offset = (offset / GRUB_ISO9660_BLKSZ + 1) * GRUB_ISO9660_BLKSZ; continue; } { char name[dirent.namelen + 1]; int nameoffset = offset + sizeof (dirent); struct grub_fshelp_node *node; int sua_off = (sizeof (dirent) + dirent.namelen + 1 - (dirent.namelen % 2)); int sua_size = dirent.len - sua_off; sua_off += offset + dir->data->susp_skip; filename = 0; filename_alloc = 0; type = GRUB_FSHELP_UNKNOWN; if (dir->data->rockridge && grub_iso9660_susp_iterate (dir->data, (dir->blk << GRUB_ISO9660_LOG2_BLKSZ) + (sua_off / GRUB_DISK_SECTOR_SIZE), sua_off % GRUB_DISK_SECTOR_SIZE, sua_size, susp_iterate_dir)) return 0; /* Read the name. */ if (grub_disk_read (dir->data->disk, (dir->blk << GRUB_ISO9660_LOG2_BLKSZ) + nameoffset / GRUB_DISK_SECTOR_SIZE, nameoffset % GRUB_DISK_SECTOR_SIZE, dirent.namelen, (char *) name)) return 0; node = grub_malloc (sizeof (struct grub_fshelp_node)); if (!node) return 0; /* Setup a new node. */ node->data = dir->data; node->size = grub_le_to_cpu32 (dirent.size); node->blk = grub_le_to_cpu32 (dirent.first_sector); node->dir_blk = ((dir->blk << GRUB_ISO9660_LOG2_BLKSZ) + offset / GRUB_DISK_SECTOR_SIZE); node->dir_off = offset % GRUB_DISK_SECTOR_SIZE; /* If the filetype was not stored using rockridge, use whatever is stored in the iso9660 filesystem. */ if (type == GRUB_FSHELP_UNKNOWN) { if ((dirent.flags & 3) == 2) type = GRUB_FSHELP_DIR; else type = GRUB_FSHELP_REG; } /* The filename was not stored in a rock ridge entry. Read it from the iso9660 filesystem. */ if (!filename) { name[dirent.namelen] = '\0'; filename = grub_strrchr (name, ';'); if (filename) *filename = '\0'; if (dirent.namelen == 1 && name[0] == 0) filename = "."; else if (dirent.namelen == 1 && name[0] == 1) filename = ".."; else filename = name; } if (dir->data->joliet) { char *oldname, *semicolon; oldname = filename; filename = grub_iso9660_convert_string ((grub_uint16_t *) oldname, dirent.namelen >> 1); semicolon = grub_strrchr (filename, ';'); if (semicolon) *semicolon = '\0'; if (filename_alloc) grub_free (oldname); filename_alloc = 1; } if (hook (filename, type, node)) { if (filename_alloc) grub_free (filename); return 1; } if (filename_alloc) grub_free (filename); } offset += dirent.len; } return 0; } static grub_err_t grub_iso9660_dir (grub_device_t device, const char *path, int (*hook) (const char *filename, const struct grub_dirhook_info *info)) { struct grub_iso9660_data *data = 0; struct grub_fshelp_node rootnode; struct grub_fshelp_node *foundnode; auto int NESTED_FUNC_ATTR iterate (const char *filename, enum grub_fshelp_filetype filetype, grub_fshelp_node_t node); int NESTED_FUNC_ATTR iterate (const char *filename, enum grub_fshelp_filetype filetype, grub_fshelp_node_t node) { struct grub_dirhook_info info; grub_memset (&info, 0, sizeof (info)); info.dir = ((filetype & GRUB_FSHELP_TYPE_MASK) == GRUB_FSHELP_DIR); grub_free (node); return hook (filename, &info); } grub_dl_ref (my_mod); data = grub_iso9660_mount (device->disk); if (! data) goto fail; rootnode.data = data; rootnode.blk = grub_le_to_cpu32 (data->voldesc.rootdir.first_sector); rootnode.size = grub_le_to_cpu32 (data->voldesc.rootdir.size); /* Use the fshelp function to traverse the path. */ if (grub_fshelp_find_file (path, &rootnode, &foundnode, grub_iso9660_iterate_dir, grub_iso9660_read_symlink, GRUB_FSHELP_DIR)) goto fail; /* List the files in the directory. */ grub_iso9660_iterate_dir (foundnode, iterate); if (foundnode != &rootnode) grub_free (foundnode); fail: grub_free (data); grub_dl_unref (my_mod); return grub_errno; } /* Open a file named NAME and initialize FILE. */ static grub_err_t grub_iso9660_open (struct grub_file *file, const char *name) { struct grub_iso9660_data *data; struct grub_fshelp_node rootnode; struct grub_fshelp_node *foundnode; grub_dl_ref (my_mod); data = grub_iso9660_mount (file->device->disk); if (!data) goto fail; rootnode.data = data; rootnode.blk = grub_le_to_cpu32 (data->voldesc.rootdir.first_sector); rootnode.size = grub_le_to_cpu32 (data->voldesc.rootdir.size); /* Use the fshelp function to traverse the path. */ if (grub_fshelp_find_file (name, &rootnode, &foundnode, grub_iso9660_iterate_dir, grub_iso9660_read_symlink, GRUB_FSHELP_REG)) goto fail; data->first_sector = foundnode->blk; file->data = data; file->size = foundnode->size; file->offset = 0; return 0; fail: grub_dl_unref (my_mod); grub_free (data); return grub_errno; } static grub_ssize_t grub_iso9660_read (grub_file_t file, char *buf, grub_size_t len) { struct grub_iso9660_data *data = (struct grub_iso9660_data *) file->data; /* XXX: The file is stored in as a single extent. */ data->disk->read_hook = file->read_hook; grub_disk_read (data->disk, data->first_sector << GRUB_ISO9660_LOG2_BLKSZ, file->offset, len, buf); data->disk->read_hook = NULL; if (grub_errno) return -1; return len; } static grub_err_t grub_iso9660_close (grub_file_t file) { grub_free (file->data); grub_dl_unref (my_mod); return GRUB_ERR_NONE; } static grub_err_t grub_iso9660_label (grub_device_t device, char **label) { struct grub_iso9660_data *data; data = grub_iso9660_mount (device->disk); if (data) { if (data->joliet) *label = grub_iso9660_convert_string ((grub_uint16_t *) &data->voldesc.volname, 16); else *label = grub_strndup ((char *) data->voldesc.volname, 32); if (*label) { char *ptr; for (ptr = *label; *ptr;ptr++); ptr--; while (ptr >= *label && *ptr == ' ') *ptr-- = 0; } grub_free (data); } else *label = 0; return grub_errno; } static grub_err_t grub_iso9660_uuid (grub_device_t device, char **uuid) { struct grub_iso9660_data *data; grub_disk_t disk = device->disk; grub_dl_ref (my_mod); data = grub_iso9660_mount (disk); if (data) { if (! data->voldesc.modified.year[0] && ! data->voldesc.modified.year[1] && ! data->voldesc.modified.year[2] && ! data->voldesc.modified.year[3] && ! data->voldesc.modified.month[0] && ! data->voldesc.modified.month[1] && ! data->voldesc.modified.day[0] && ! data->voldesc.modified.day[1] && ! data->voldesc.modified.hour[0] && ! data->voldesc.modified.hour[1] && ! data->voldesc.modified.minute[0] && ! data->voldesc.modified.minute[1] && ! data->voldesc.modified.second[0] && ! data->voldesc.modified.second[1] && ! data->voldesc.modified.hundredth[0] && ! data->voldesc.modified.hundredth[1]) { grub_error (GRUB_ERR_BAD_NUMBER, "no creation date in filesystem to generate UUID"); *uuid = NULL; } else { *uuid = grub_xasprintf ("%c%c%c%c-%c%c-%c%c-%c%c-%c%c-%c%c-%c%c", data->voldesc.modified.year[0], data->voldesc.modified.year[1], data->voldesc.modified.year[2], data->voldesc.modified.year[3], data->voldesc.modified.month[0], data->voldesc.modified.month[1], data->voldesc.modified.day[0], data->voldesc.modified.day[1], data->voldesc.modified.hour[0], data->voldesc.modified.hour[1], data->voldesc.modified.minute[0], data->voldesc.modified.minute[1], data->voldesc.modified.second[0], data->voldesc.modified.second[1], data->voldesc.modified.hundredth[0], data->voldesc.modified.hundredth[1]); } } else *uuid = NULL; grub_dl_unref (my_mod); grub_free (data); return grub_errno; } static struct grub_fs grub_iso9660_fs = { .name = "iso9660", .dir = grub_iso9660_dir, .open = grub_iso9660_open, .read = grub_iso9660_read, .close = grub_iso9660_close, .label = grub_iso9660_label, .uuid = grub_iso9660_uuid, .next = 0 }; GRUB_MOD_INIT(iso9660) { grub_fs_register (&grub_iso9660_fs); my_mod = mod; } GRUB_MOD_FINI(iso9660) { grub_fs_unregister (&grub_iso9660_fs); }