toaruos/modules/iso9660.c
2016-12-14 21:21:32 +09:00

433 lines
11 KiB
C

/* vim: tabstop=4 shiftwidth=4 noexpandtab
* This file is part of ToaruOS and is released under the terms
* of the NCSA / University of Illinois License - see LICENSE.md
* Copyright (C) 2016 Kevin Lange
*
* ISO 9660 filesystem driver (for CDs)
*/
#include <system.h>
#include <types.h>
#include <fs.h>
#include <logging.h>
#include <module.h>
#include <args.h>
#include <printf.h>
#define ISO_SECTOR_SIZE 2048
#define FLAG_HIDDEN 0x01
#define FLAG_DIRECTORY 0x02
#define FLAG_ASSOCIATED 0x04
#define FLAG_EXTENDED 0x08
#define FLAG_PERMISSIONS 0x10
#define FLAG_CONTINUES 0x80
typedef struct {
fs_node_t * block_device;
uint32_t block_size;
} iso_9660_fs_t;
typedef struct {
char year[4];
char month[2];
char day[2];
char hour[2];
char minute[2];
char second[2];
char hundredths[2];
int8_t timezone;
} __attribute__((packed)) iso_9660_datetime_t;
typedef struct {
uint8_t year;
uint8_t month;
uint8_t day;
uint8_t hour;
uint8_t minute;
uint8_t second;
int8_t timezone;
} __attribute__((packed)) iso_9660_rec_date_t;
typedef struct {
uint8_t length;
uint8_t ext_length;
uint32_t extent_start_LSB;
uint32_t extent_start_MSB;
uint32_t extent_length_LSB;
uint32_t extent_length_MSB;
iso_9660_rec_date_t record_date;
uint8_t flags;
uint8_t interleave_units;
uint8_t interleave_gap;
uint16_t volume_seq_LSB;
uint16_t volume_seq_MSB;
uint8_t name_len;
char name[];
} __attribute__((packed)) iso_9660_directory_entry_t;
typedef struct {
uint8_t type; /* 0x01 */
char id[5]; /* CD001 */
uint8_t version;
uint8_t _unused0;
char system_id[32];
char volume_id[32];
uint8_t _unused1[8];
uint32_t volume_space_LSB;
uint32_t volume_space_MSB;
uint8_t _unused2[32];
uint16_t volume_set_LSB;
uint16_t volume_set_MSB;
uint16_t volume_seq_LSB;
uint16_t volume_seq_MSB;
uint16_t logical_block_size_LSB;
uint16_t logical_block_size_MSB;
uint32_t path_table_size_LSB;
uint32_t path_table_size_MSB;
uint32_t path_table_LSB;
uint32_t optional_path_table_LSB;
uint32_t path_table_MSB;
uint32_t optional_path_table_MSB;
/* iso_9660_directory_entry_t */
char root[34];
char volume_set_id[128];
char volume_publisher[128];
char data_preparer[128];
char application_id[128];
char copyright_file[38];
char abstract_file[36];
char bibliographic_file[37];
iso_9660_datetime_t creation;
iso_9660_datetime_t modification;
iso_9660_datetime_t expiration;
iso_9660_datetime_t effective;
uint8_t file_structure_version;
uint8_t _unused_3;
char application_use[];
} __attribute__((packed)) iso_9660_volume_descriptor_t;
static void file_from_dir_entry(iso_9660_fs_t * this, size_t sector, iso_9660_directory_entry_t * dir, size_t offset, fs_node_t * fs);
static void read_sector(iso_9660_fs_t * this, uint32_t sector_id, char * buffer) {
read_fs(this->block_device, sector_id * this->block_size, this->block_size, (uint8_t *)buffer);
}
static void inplace_lower(char * string) {
while (*string) {
if (*string >= 'A' && *string <= 'Z') {
*string += ('a' - 'A');
}
string++;
}
}
static void open_iso(fs_node_t *node, unsigned int flags) {
/* Nothing to do here */
}
static void close_iso(fs_node_t *node) {
/* Nothing to do here */
}
static struct dirent * readdir_iso(fs_node_t *node, uint32_t index) {
if (index == 0) {
struct dirent * out = malloc(sizeof(struct dirent));
memset(out, 0x00, sizeof(struct dirent));
out->ino = 0;
strcpy(out->name, ".");
return out;
}
if (index == 1) {
struct dirent * out = malloc(sizeof(struct dirent));
memset(out, 0x00, sizeof(struct dirent));
out->ino = 0;
strcpy(out->name, "..");
return out;
}
iso_9660_fs_t * this = node->device;
char * buffer = malloc(this->block_size);
read_sector(this, node->inode, buffer);
iso_9660_directory_entry_t * root_entry = (iso_9660_directory_entry_t *)(buffer + node->impl);
debug_print(WARNING, "[iso] Reading directory for readdir; sector = %d, offset = %d", node->inode, node->impl);
uint8_t * root_data = malloc(root_entry->extent_length_LSB);
uint8_t * offset = root_data;
size_t sector_offset = 0;
size_t length_to_read = root_entry->extent_length_LSB;
while (length_to_read) {
read_sector(this, root_entry->extent_start_LSB + sector_offset, (char*)offset);
if (length_to_read >= this->block_size) {
offset += this->block_size;
sector_offset += 1;
length_to_read -= this->block_size;
} else {
break;
}
}
debug_print(WARNING, "[iso] Done, want index = %d", index);
/* Examine directory */
offset = root_data;
unsigned int i = 0;
struct dirent *dirent = malloc(sizeof(struct dirent));
fs_node_t * out = malloc(sizeof(fs_node_t));
memset(dirent, 0, sizeof(struct dirent));
while (1) {
iso_9660_directory_entry_t * dir = (iso_9660_directory_entry_t *)offset;
if (dir->length == 0) break;
if (!(dir->flags & FLAG_HIDDEN)) {
debug_print(WARNING, "[iso] Found file %d", i);
if (i == index) {
file_from_dir_entry(this, (root_entry->extent_start_LSB)+(offset - root_data)/this->block_size, dir, (offset - root_data) % this->block_size, out);
memcpy(&dirent->name, out->name, strlen(out->name)+1);
dirent->ino = out->inode;
goto cleanup;
}
i += 1;
}
offset += dir->length;
if ((size_t)(offset - root_data) > root_entry->extent_length_LSB) break;
}
free(dirent);
dirent = NULL;
cleanup:
free(root_data);
free(buffer);
free(out);
return dirent;
}
static uint32_t read_iso(fs_node_t * node, uint32_t offset, uint32_t size, uint8_t * buffer) {
iso_9660_fs_t * this = node->device;
char * tmp = malloc(this->block_size);
read_sector(this, node->inode, tmp);
iso_9660_directory_entry_t * root_entry = (iso_9660_directory_entry_t *)(tmp + node->impl);
uint32_t end;
/* We can do this in a single underlying read to the filesystem */
if (offset + size > root_entry->extent_length_LSB) {
end = root_entry->extent_length_LSB;
} else {
end = offset + size;
}
uint32_t size_to_read = end - offset;
read_fs(this->block_device, root_entry->extent_start_LSB * this->block_size + offset, size_to_read, (uint8_t *)buffer);
free(tmp);
return size_to_read;
}
static fs_node_t * finddir_iso(fs_node_t *node, char *name) {
iso_9660_fs_t * this = node->device;
char * buffer = malloc(this->block_size);
read_sector(this, node->inode, buffer);
iso_9660_directory_entry_t * root_entry = (iso_9660_directory_entry_t *)(buffer + node->impl);
uint8_t * root_data = malloc(root_entry->extent_length_LSB);
uint8_t * offset = root_data;
size_t sector_offset = 0;
size_t length_to_read = root_entry->extent_length_LSB;
while (length_to_read) {
read_sector(this, root_entry->extent_start_LSB + sector_offset, (char*)offset);
if (length_to_read >= this->block_size) {
offset += this->block_size;
sector_offset += 1;
length_to_read -= this->block_size;
} else {
break;
}
}
/* Examine directory */
offset = root_data;
fs_node_t * out = malloc(sizeof(fs_node_t));
while (1) {
iso_9660_directory_entry_t * dir = (iso_9660_directory_entry_t *)offset;
if (dir->length == 0) break;
if (!(dir->flags & FLAG_HIDDEN)) {
memset(out, 0, sizeof(fs_node_t));
file_from_dir_entry(this, (root_entry->extent_start_LSB)+(offset - root_data)/this->block_size, dir, (offset - root_data) % this->block_size, out);
if (!strcmp(out->name, name)) {
goto cleanup; /* found it */
}
}
offset += dir->length;
if ((size_t)(offset - root_data) > root_entry->extent_length_LSB) break;
}
free(out);
out = NULL;
cleanup:
free(root_data);
free(buffer);
return out;
}
static void file_from_dir_entry(iso_9660_fs_t * this, size_t sector, iso_9660_directory_entry_t * dir, size_t offset, fs_node_t * fs) {
fs->device = this;
fs->inode = sector; /* Sector the file is in */
fs->impl = offset; /* Offset */
char * file_name = malloc(dir->name_len + 1);
memcpy(file_name, dir->name, dir->name_len);
file_name[dir->name_len] = 0;
inplace_lower(file_name);
char * dot = strchr(file_name, '.');
if (!dot) {
/* It's a directory. */
} else {
char * ext = dot + 1;
char * semi = strchr(ext, ';');
if (semi) {
*semi = 0;
}
if (strlen(ext) == 0) {
*dot = 0;
} else {
char * derp = ext;
while (*derp == '.') derp++;
if (derp != ext) {
memmove(ext, derp, strlen(derp)+1);
}
}
}
memcpy(fs->name, file_name, strlen(file_name)+1);
free(file_name);
fs->uid = 0;
fs->gid = 0;
fs->length = dir->extent_length_LSB;
fs->mask = 0444;
fs->nlink = 0; /* Unsupported */
if (dir->flags & FLAG_DIRECTORY) {
fs->flags = FS_DIRECTORY;
fs->readdir = readdir_iso;
fs->finddir = finddir_iso;
} else {
fs->flags = FS_FILE;
fs->read = read_iso;
}
/* Other things not supported */
/* TODO actually get these from the CD into Unix time */
fs->atime = now();
fs->mtime = now();
fs->ctime = now();
fs->open = open_iso;
fs->close = close_iso;
}
static fs_node_t * iso_fs_mount(char * device, char * mount_path) {
fs_node_t * dev = kopen(device, 0);
if (!dev) {
debug_print(ERROR, "failed to open %s", device);
return NULL;
}
iso_9660_fs_t * this = malloc(sizeof(iso_9660_fs_t));
this->block_device = dev;
this->block_size = ISO_SECTOR_SIZE;
/* Probably want to put a block cache on this like EXT2 driver does; or do that in the ATAPI layer... */
debug_print(WARNING, "ISO 9660 file system driver mounting %s to %s", device, mount_path);
/* Read the volume descriptors */
uint8_t * tmp = malloc(ISO_SECTOR_SIZE);
int i = 0x10;
int found = 0;
while (1) {
read_sector(this,i,(char*)tmp);
if (tmp[0] == 0x00) {
debug_print(WARNING, " Boot Record");
} else if (tmp[0] == 0x01) {
debug_print(WARNING, " Primary Volume Descriptor");
found = 1;
break;
} else if (tmp[0] == 0x02) {
debug_print(WARNING, " Secondary Volume Descriptor");
} else if (tmp[0] == 0x03) {
debug_print(WARNING, " Volume Partition Descriptor");
}
if (tmp[0] == 0xFF) break;
i++;
}
if (!found) {
debug_print(WARNING, "No primary volume descriptor?");
return NULL;
}
iso_9660_volume_descriptor_t * root = (iso_9660_volume_descriptor_t *)tmp;
debug_print(WARNING, " Volume space: %d", root->volume_space_LSB);
debug_print(WARNING, " Volume set: %d", root->volume_set_LSB);
debug_print(WARNING, " Volume seq: %d", root->volume_seq_LSB);
debug_print(WARNING, " Block size: %d", root->logical_block_size_LSB);
debug_print(WARNING, " Path table size: %d", root->path_table_size_LSB);
debug_print(WARNING, " Path table loc: %d", root->path_table_LSB);
iso_9660_directory_entry_t * root_entry = (iso_9660_directory_entry_t *)&root->root;
debug_print(WARNING, "ISO root info:");
debug_print(WARNING, " Entry len: %d", root_entry->length);
debug_print(WARNING, " File start: %d", root_entry->extent_start_LSB);
debug_print(WARNING, " File len: %d", root_entry->extent_length_LSB);
debug_print(WARNING, " Is a directory: %s", (root_entry->flags & FLAG_DIRECTORY) ? "yes" : "no?");
debug_print(WARNING, " Interleave units: %d", root_entry->interleave_units);
debug_print(WARNING, " Interleave gap: %d", root_entry->interleave_gap);
debug_print(WARNING, " Volume Seq: %d", root_entry->volume_seq_LSB);
fs_node_t * fs = malloc(sizeof(fs_node_t));
memset(fs, 0, sizeof(fs_node_t));
file_from_dir_entry(this, i, root_entry, 156, fs);
return fs;
}
static int init(void) {
vfs_register("iso", iso_fs_mount);
return 0;
}
static int fini(void) {
return 0;
}
MODULE_DEF(iso9660, init, fini);