#include <dprintf.h> #include <stdio.h> #include <string.h> #include <sys/dirent.h> #include <core.h> #include <cache.h> #include <disk.h> #include <fs.h> #include <stdlib.h> #include "iso9660_fs.h" #include "susp_rr.h" /* Convert to lower case string */ static inline char iso_tolower(char c) { if (c >= 'A' && c <= 'Z') c += 0x20; return c; } static struct inode *new_iso_inode(struct fs_info *fs) { return alloc_inode(fs, 0, sizeof(struct iso9660_pvt_inode)); } static inline struct iso_sb_info *ISO_SB(struct fs_info *fs) { return fs->fs_info; } static size_t iso_convert_name(char *dst, const char *src, int len) { char *p = dst; char c; if (len == 1) { switch (*src) { case 1: *p++ = '.'; /* fall through */ case 0: *p++ = '.'; goto done; default: /* nothing special */ break; } } while (len-- && (c = *src++)) { if (c == ';') /* Remove any filename version suffix */ break; *p++ = iso_tolower(c); } /* Then remove any terminal dots */ while (p > dst+1 && p[-1] == '.') p--; done: *p = '\0'; return p - dst; } /* * Unlike strcmp, it does return 1 on match, or reutrn 0 if not match. */ static bool iso_compare_name(const char *de_name, size_t len, const char *file_name) { char iso_file_name[256]; char *p = iso_file_name; char c1, c2; int i; i = iso_convert_name(iso_file_name, de_name, len); (void)i; dprintf("Compare: \"%s\" to \"%s\" (len %zu)\n", file_name, iso_file_name, i); do { c1 = *p++; c2 = iso_tolower(*file_name++); /* compare equal except for case? */ if (c1 != c2) return false; } while (c1); return true; } /* * Find a entry in the specified dir with name _dname_. */ static const struct iso_dir_entry * iso_find_entry(const char *dname, struct inode *inode) { struct fs_info *fs = inode->fs; block_t dir_block = PVT(inode)->lba; int i = 0, offset = 0; const char *de_name; int de_name_len, de_len, rr_name_len, ret; const struct iso_dir_entry *de; const char *data = NULL; char *rr_name = NULL; dprintf("iso_find_entry: \"%s\"\n", dname); while (1) { if (!data) { dprintf("Getting block %d from block %llu\n", i, dir_block); if (++i > inode->blocks) return NULL; /* End of directory */ data = get_cache(fs->fs_dev, dir_block++); offset = 0; } de = (const struct iso_dir_entry *)(data + offset); de_len = de->length; offset += de_len; /* Make sure we have a full directory entry */ if (de_len < 33 || offset > BLOCK_SIZE(fs)) { /* * Zero = end of sector, or corrupt directory entry * * ECMA-119:1987 6.8.1.1: "Each Directory Record shall end * in the Logical Sector in which it begins. */ data = NULL; continue; } /* Try to get Rock Ridge name */ ret = susp_rr_get_nm(fs, (char *) de, &rr_name, &rr_name_len); if (ret > 0) { if (strcmp(rr_name, dname) == 0) { dprintf("Found (by RR name).\n"); free(rr_name); return de; } free(rr_name); rr_name = NULL; continue; /* Rock Ridge was valid and did not match */ } /* Fall back to ISO name */ de_name_len = de->name_len; de_name = de->name; if (iso_compare_name(de_name, de_name_len, dname)) { dprintf("Found (by ISO name).\n"); return de; } } } static inline enum dirent_type get_inode_mode(uint8_t flags) { return (flags & 0x02) ? DT_DIR : DT_REG; } static struct inode *iso_get_inode(struct fs_info *fs, const struct iso_dir_entry *de) { struct inode *inode = new_iso_inode(fs); int blktosec = BLOCK_SHIFT(fs) - SECTOR_SHIFT(fs); if (!inode) return NULL; dprintf("Getting inode for: %.*s\n", de->name_len, de->name); inode->mode = get_inode_mode(de->flags); inode->size = de->size_le; PVT(inode)->lba = de->extent_le; inode->blocks = (inode->size + BLOCK_SIZE(fs) - 1) >> BLOCK_SHIFT(fs); /* We have a single extent for all data */ inode->next_extent.pstart = (sector_t)de->extent_le << blktosec; inode->next_extent.len = (sector_t)inode->blocks << blktosec; return inode; } static struct inode *iso_iget_root(struct fs_info *fs) { const struct iso_dir_entry *root = &ISO_SB(fs)->root; return iso_get_inode(fs, root); } static struct inode *iso_iget(const char *dname, struct inode *parent) { const struct iso_dir_entry *de; dprintf("iso_iget %p %s\n", parent, dname); de = iso_find_entry(dname, parent); if (!de) return NULL; return iso_get_inode(parent->fs, de); } static int iso_readdir(struct file *file, struct dirent *dirent) { struct fs_info *fs = file->fs; struct inode *inode = file->inode; const struct iso_dir_entry *de; const char *data = NULL; char *rr_name = NULL; int name_len, ret; while (1) { size_t offset = file->offset & (BLOCK_SIZE(fs) - 1); if (!data) { uint32_t i = file->offset >> BLOCK_SHIFT(fs); if (i >= inode->blocks) return -1; data = get_cache(fs->fs_dev, PVT(inode)->lba + i); } de = (const struct iso_dir_entry *)(data + offset); if (de->length < 33 || offset + de->length > BLOCK_SIZE(fs)) { file->offset = (file->offset + BLOCK_SIZE(fs)) & ~(BLOCK_SIZE(fs) - 1); /* Start of the next block */ data = NULL; continue; } break; } dirent->d_ino = 0; /* Inode number is invalid to ISO fs */ dirent->d_off = file->offset; dirent->d_type = get_inode_mode(de->flags); /* Try to get Rock Ridge name */ ret = susp_rr_get_nm(fs, (char *) de, &rr_name, &name_len); if (ret > 0) { memcpy(dirent->d_name, rr_name, name_len + 1); free(rr_name); rr_name = NULL; } else { name_len = iso_convert_name(dirent->d_name, de->name, de->name_len); } dirent->d_reclen = offsetof(struct dirent, d_name) + 1 + name_len; file->offset += de->length; /* Update for next reading */ return 0; } /* Load the config file, return 1 if failed, or 0 */ static int iso_open_config(struct com32_filedata *filedata) { static const char *search_directories[] = { "/boot/isolinux", "/isolinux", "/boot/syslinux", "/syslinux", "/", NULL }; static const char *filenames[] = { "isolinux.cfg", "syslinux.cfg", NULL }; return search_dirs(filedata, search_directories, filenames, ConfigName); } static int iso_fs_init(struct fs_info *fs) { struct iso_sb_info *sbi; char pvd[2048]; /* Primary Volume Descriptor */ uint32_t pvd_lba; struct disk *disk = fs->fs_dev->disk; int blktosec; sbi = malloc(sizeof(*sbi)); if (!sbi) { malloc_error("iso_sb_info structure"); return 1; } fs->fs_info = sbi; /* * XXX: handling iso9660 in hybrid mode on top of a 4K-logical disk * will really, really hurt... */ fs->sector_shift = fs->fs_dev->disk->sector_shift; fs->block_shift = 11; /* A CD-ROM block is always 2K */ fs->sector_size = 1 << fs->sector_shift; fs->block_size = 1 << fs->block_shift; blktosec = fs->block_shift - fs->sector_shift; pvd_lba = iso_boot_info.pvd; if (!pvd_lba) pvd_lba = 16; /* Default if not otherwise defined */ disk->rdwr_sectors(disk, pvd, (sector_t)pvd_lba << blktosec, 1 << blktosec, false); memcpy(&sbi->root, pvd + ROOT_DIR_OFFSET, sizeof(sbi->root)); /* Initialize the cache */ cache_init(fs->fs_dev, fs->block_shift); /* Check for SP and ER in the first directory record of the root directory. Set sbi->susp_skip and enable sbi->do_rr as appropriate. */ susp_rr_check_signatures(fs, 1); return fs->block_shift; } const struct fs_ops iso_fs_ops = { .fs_name = "iso", .fs_flags = FS_USEMEM | FS_THISIND, .fs_init = iso_fs_init, .searchdir = NULL, .getfssec = generic_getfssec, .close_file = generic_close_file, .mangle_name = generic_mangle_name, .open_config = iso_open_config, .iget_root = iso_iget_root, .iget = iso_iget, .readdir = iso_readdir, .next_extent = no_next_extent, .fs_uuid = NULL, };