337 lines
8.0 KiB
C
337 lines
8.0 KiB
C
#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,
|
|
};
|