Goto sanos source index
//
// cdfs.c
//
// ISO-9660 CD-ROM Filesystem
//
// Copyright (C) 2002 Michael Ringgaard. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// 3. Neither the name of the project nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
#include <os/krnl.h>
#include "iso9660.h"
#define CDFS_DEFAULT_CACHESIZE 128
#define CDFS_BLOCKSIZE 2048
struct cdfs {
dev_t devno;
int blks;
int volblks;
int vdblk;
int joliet;
struct bufpool *cache;
unsigned char *path_table_buffer;
struct iso_pathtable_record **path_table;
int path_table_records;
};
struct cdfs_file {
int extent;
int size;
time_t date;
};
static int cdfs_fnmatch(struct cdfs *cdfs, char *fn1, int len1, char *fn2, int len2) {
if (cdfs->joliet) {
wchar_t *wfn2 = (wchar_t *) fn2;
int wlen2 = len2 / 2;
if (wlen2 > 1 && ntohs(wfn2[wlen2 - 2]) == ';') wlen2 -= 2;
if (wlen2 > 0 && ntohs(wfn2[wlen2 - 1]) == '.') wlen2 -= 1;
if (len1 != wlen2) return 0;
while (len1--) if (*fn1++ != ntohs(*wfn2++)) return 0;
return 1;
} else {
if (len2 > 1 && fn2[len2 - 2] == ';') len2 -= 2;
if (len2 > 0 && fn2[len2 - 1] == '.') len2 -= 1;
if (len1 != len2) return 0;
while (len1--) if (*fn1++ != *fn2++) return 0;
return 1;
}
}
static int cdfs_read_path_table(struct cdfs *cdfs, struct iso_volume_descriptor *vd) {
struct buf *buf;
unsigned char *pt;
int ptblk;
int ptlen;
int ptpos;
int n;
// Determine size and location of path table and allocate buffer
ptlen = isonum_733(vd->path_table_size);
ptblk = isonum_731(vd->type_l_path_table);
cdfs->path_table_buffer = malloc(ptlen);
if (!cdfs->path_table_buffer) return -ENOMEM;
// Read L path table into buffer
ptpos = 0;
while (ptpos < ptlen) {
buf = get_buffer(cdfs->cache, ptblk++);
if (!buf) return -EIO;
if (ptlen - ptpos > CDFS_BLOCKSIZE) {
memcpy(cdfs->path_table_buffer + ptpos, buf->data, CDFS_BLOCKSIZE);
ptpos += CDFS_BLOCKSIZE;
} else {
memcpy(cdfs->path_table_buffer + ptpos, buf->data, ptlen - ptpos);
ptpos = ptlen;
}
release_buffer(cdfs->cache, buf);
}
// Determine number of records in pathtable
// Path table records are indexed from 1 (first entry not used)
pt = cdfs->path_table_buffer;
n = 1;
while (pt < cdfs->path_table_buffer + ptlen) {
struct iso_pathtable_record *pathrec = (struct iso_pathtable_record *) pt;
int namelen = pathrec->length;
int reclen = sizeof(struct iso_pathtable_record) + namelen + (namelen & 1);
n++;
pt += reclen;
}
cdfs->path_table_records = n;
// Allocate path table
cdfs->path_table = (struct iso_pathtable_record **) kmalloc(cdfs->path_table_records * sizeof(struct iso_pathtable_record **));
if (!cdfs->path_table) return -ENOMEM;
cdfs->path_table[0] = NULL;
// Setup pointers into path table buffer
pt = cdfs->path_table_buffer;
for (n = 1; n < cdfs->path_table_records; n++) {
struct iso_pathtable_record *pathrec = (struct iso_pathtable_record *) pt;
int namelen = pathrec->length;
int reclen = sizeof(struct iso_pathtable_record) + namelen + (namelen & 1);
cdfs->path_table[n] = pathrec;
pt += reclen;
}
return 0;
}
static int cdfs_find_dir(struct cdfs *cdfs, char *name, int len) {
char *p;
int l;
int dir = 2;
int parent = 1;
while (1) {
// Skip path separator
if (*name == PS1 || *name == PS2) {
name++;
len--;
}
if (len == 0) return parent;
// Find next part of name
p = name;
l = 0;
while (l < len && *p != PS1 && *p != PS2) {
l++;
p++;
}
// Find directory for next name part
while (dir < cdfs->path_table_records) {
if (cdfs->path_table[dir]->parent != parent) return -ENOENT;
if (cdfs_fnmatch(cdfs, name, l, cdfs->path_table[dir]->name, cdfs->path_table[dir]->length)) break;
dir++;
}
// If we reached the end of the path table the directory does not exist
if (dir == cdfs->path_table_records) return -ENOENT;
// If we have parsed the whole name return the directory number
if (l == len) return dir;
// Go forward in path table until first entry for directory found
parent = dir;
while (dir < cdfs->path_table_records) {
if (cdfs->path_table[dir]->parent == parent) break;
dir++;
}
// Prepare for next name part
name = p;
len -= l;
}
}
static int cdfs_find_in_dir(struct cdfs *cdfs, int dir, char *name, int len, struct buf **dirbuf, struct iso_directory_record **dirrec) {
struct buf *buf;
char *p;
struct iso_directory_record *rec;
int blk;
int left;
int reclen;
int namelen;
// The first two directory records are . (current) and .. (parent)
blk = cdfs->path_table[dir]->extent;
buf = get_buffer(cdfs->cache, blk++);
if (!buf) return -EIO;
// Get length of directory from the first record
p = buf->data;
rec = (struct iso_directory_record *) p;
left = isonum_733(rec->size);
// Find named entry in directory
while (left > 0) {
// Read next block if all records in current block has been read
// Directory records never cross block boundaries
if (p >= buf->data + CDFS_BLOCKSIZE) {
release_buffer(cdfs->cache, buf);
if (p > buf->data + CDFS_BLOCKSIZE) return -EIO;
buf = get_buffer(cdfs->cache, blk++);
if (!buf) return -EIO;
p = buf->data;
}
// Check for match
rec = (struct iso_directory_record *) p;
reclen = isonum_711(rec->length);
namelen = isonum_711(rec->name_len);
if (reclen > 0) {
if (cdfs_fnmatch(cdfs, name, len, (char *) rec->name, namelen)) {
*dirrec = rec;
*dirbuf = buf;
return 0;
}
// Skip to next record
p += reclen;
left -= reclen;
} else {
// Skip to next block
left -= (buf->data + CDFS_BLOCKSIZE) - p;
p = buf->data + CDFS_BLOCKSIZE;
}
}
release_buffer(cdfs->cache, buf);
return -ENOENT;
}
static int cdfs_find_file(struct cdfs *cdfs, char *name, int len, struct buf **buf, struct iso_directory_record **rec) {
int dir;
int split;
int n;
// If root get directory record from volume descriptor
if (len == 0) {
struct iso_volume_descriptor *vd;
*buf = get_buffer(cdfs->cache, cdfs->vdblk);
if (!*buf) return -EIO;
vd = (struct iso_volume_descriptor *) (*buf)->data;
*rec = (struct iso_directory_record *) vd->root_directory_record;
return 0;
}
// Split path into directory part and file name part
split = -1;
for (n = 0; n < len; n++) if (name[n] == PS1 || name[n] == PS2) split = n;
// Find directly if file located in root directory
if (split == -1) return cdfs_find_in_dir(cdfs, 1, name, len, buf, rec);
// Locate directory
dir = cdfs_find_dir(cdfs, name, split + 1);
if (dir < 0) return dir;
// Find filename in directory
return cdfs_find_in_dir(cdfs, dir, name + split + 1, len - split - 1, buf, rec);
}
static time_t cdfs_isodate(unsigned char *date)
{
struct tm tm;
memset(&tm, 0, sizeof tm);
tm.tm_year = date[0];
tm.tm_mon = date[1] - 1;
tm.tm_mday = date[2];
tm.tm_hour = date[3];
tm.tm_min = date[4];
tm.tm_sec = date[5];
tm.tm_min += (*(char *) &date[6]) * 15;
return mktime(&tm);
}
int cdfs_mount(struct fs *fs, char *opts)
{
struct cdfs *cdfs;
dev_t devno;
int cachebufs;
int rc;
int blk;
struct buf *buf;
struct iso_volume_descriptor *vd;
// Check device
devno = dev_open(fs->mntfrom);
if (devno == NODEV) return -ENODEV;
if (device(devno)->driver->type != DEV_TYPE_BLOCK) return -ENOTBLK;
// Revalidate device and check block size
if (get_option(opts, "revalidate", NULL, 0, NULL)) {
rc = dev_ioctl(devno, IOCTL_REVALIDATE, NULL, 0);
if (rc < 0) return rc;
}
if (dev_ioctl(devno, IOCTL_GETBLKSIZE, NULL, 0) != CDFS_BLOCKSIZE) return -ENXIO;
// Allocate file system
cdfs = (struct cdfs *) kmalloc(sizeof(struct cdfs));
memset(cdfs, 0, sizeof(struct cdfs));
cdfs->devno = devno;
cdfs->blks = dev_ioctl(devno, IOCTL_GETDEVSIZE, NULL, 0);
if (cdfs->blks < 0) return cdfs->blks;
// Allocate cache
cachebufs = get_num_option(opts, "cache", CDFS_DEFAULT_CACHESIZE);
cdfs->cache = init_buffer_pool(devno, cachebufs, CDFS_BLOCKSIZE, NULL, cdfs);
if (!cdfs->cache) return -ENOMEM;
// Read volume descriptors
cdfs->vdblk = 0;
blk = 16;
while (1) {
int type;
unsigned char *esc;
buf = get_buffer(cdfs->cache, blk);
if (!buf) return -EIO;
vd = (struct iso_volume_descriptor *) buf->data;
type = isonum_711(vd->type);
esc = vd->escape_sequences;
if (memcmp(vd->id, "CD001", 5) != 0) {
free_buffer_pool(cdfs->cache);
dev_close(cdfs->devno);
kfree(cdfs);
return -EIO;
}
if (cdfs->vdblk == 0 && type == ISO_VD_PRIMARY) {
cdfs->vdblk = blk;
} else if (type == ISO_VD_SUPPLEMENTAL &&
esc[0] == 0x25 && esc[1] == 0x2F &&
(esc[2] == 0x40 || esc[2] == 0x43 || esc[2] == 0x45)) {
cdfs->vdblk = blk;
cdfs->joliet = 1;
}
release_buffer(cdfs->cache, buf);
if (type == ISO_VD_END) break;
blk++;
}
if (cdfs->vdblk == 0) return -EIO;
// Initialize filesystem from selected volume descriptor and read path table
buf = get_buffer(cdfs->cache, cdfs->vdblk);
if (!buf) return -EIO;
vd = (struct iso_volume_descriptor *) buf->data;
cdfs->volblks = isonum_733(vd->volume_space_size);
rc = cdfs_read_path_table(cdfs, vd);
if (rc < 0) return rc;
release_buffer(cdfs->cache, buf);
// Device mounted successfully
fs->data = cdfs;
return 0;
}
int cdfs_umount(struct fs *fs) {
struct cdfs *cdfs = (struct cdfs *) fs->data;
// Free cache
if (cdfs->cache) free_buffer_pool(cdfs->cache);
// Close device
dev_close(cdfs->devno);
// Deallocate file system
if (cdfs->path_table_buffer) kfree(cdfs->path_table_buffer);
if (cdfs->path_table) kfree(cdfs->path_table);
kfree(cdfs);
return 0;
}
int cdfs_statfs(struct fs *fs, struct statfs *buf) {
struct cdfs *cdfs = (struct cdfs *) fs->data;
buf->bsize = CDFS_BLOCKSIZE;
buf->iosize = CDFS_BLOCKSIZE;
buf->blocks = cdfs->volblks;
buf->bfree = 0;
buf->files = -1;
buf->ffree = 0;
buf->cachesize = cdfs->cache->poolsize * CDFS_BLOCKSIZE;
return 0;
}
int cdfs_open(struct file *filp, char *name) {
struct cdfs *cdfs = (struct cdfs *) filp->fs->data;
struct iso_directory_record *rec;
struct cdfs_file *cdfile;
struct buf *buf;
time_t date;
int size;
int extent;
int flags;
int rc;
// Check open mode
if (filp->flags & (O_CREAT | O_TRUNC | O_APPEND)) return -EROFS;
// Locate file in file system
rc = cdfs_find_file(cdfs, name, strlen(name), &buf, &rec);
if (rc < 0) return rc;
flags = isonum_711(rec->flags);
extent = isonum_733(rec->extent);
date = cdfs_isodate(rec->date);
size = isonum_733(rec->size);
release_buffer(cdfs->cache, buf);
// Allocate and initialize file block
cdfile = (struct cdfs_file *) kmalloc(sizeof(struct cdfs_file));
if (!cdfile) return -ENOMEM;
cdfile->extent = extent;
cdfile->date = date;
cdfile->size = size;
if (flags & 2) filp->flags |= F_DIR;
filp->data = cdfile;
filp->mode = S_IFREG | S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;;
return 0;
}
int cdfs_close(struct file *filp) {
struct cdfs_file *cdfile = (struct cdfs_file *) filp->data;
if (cdfile) kfree(cdfile);
return 0;
}
int cdfs_fsync(struct file *filp) {
return 0;
}
int cdfs_read(struct file *filp, void *data, size_t size, off64_t pos) {
struct cdfs_file *cdfile = (struct cdfs_file *) filp->data;
struct cdfs *cdfs = (struct cdfs *) filp->fs->data;
size_t read;
size_t count;
size_t left;
char *p;
int iblock;
int start;
int blk;
struct buf *buf;
read = 0;
p = (char *) data;
while (pos < cdfile->size && size > 0) {
iblock = (int) pos / CDFS_BLOCKSIZE;
start = (int) pos % CDFS_BLOCKSIZE;
count = CDFS_BLOCKSIZE - start;
if (count > size) count = size;
left = cdfile->size - (int) pos;
if (count > left) count = left;
if (count <= 0) break;
blk = cdfile->extent + iblock;
if (filp->flags & O_DIRECT) {
if (start != 0 || count != CDFS_BLOCKSIZE) return read;
if (dev_read(cdfs->devno, p, count, blk, 0) != (int) count) return read;
} else {
buf = get_buffer(cdfs->cache, blk);
if (!buf) return -EIO;
memcpy(p, buf->data + start, count);
release_buffer(cdfs->cache, buf);
}
pos += count;
p += count;
read += count;
size -= count;
}
return read;
}
off64_t cdfs_tell(struct file *filp) {
return filp->pos;
}
off64_t cdfs_lseek(struct file *filp, off64_t offset, int origin) {
struct cdfs_file *cdfile = (struct cdfs_file *) filp->data;
switch (origin) {
case SEEK_END:
offset += cdfile->size;
break;
case SEEK_CUR:
offset += filp->pos;
}
if (offset < 0) return -EINVAL;
filp->pos = offset;
return offset;
}
int cdfs_fstat(struct file *filp, struct stat64 *buffer) {
struct cdfs_file *cdfile = (struct cdfs_file *) filp->data;
struct cdfs *cdfs = (struct cdfs *) filp->fs->data;
if (buffer) {
memset(buffer, 0, sizeof(struct stat64));
if (filp->flags & F_DIR) {
buffer->st_mode = S_IFDIR | S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
} else {
buffer->st_mode = S_IFREG | S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
}
buffer->st_ino = cdfile->extent;
buffer->st_nlink = 1;
buffer->st_dev = cdfs->devno;
buffer->st_atime = buffer->st_mtime = buffer->st_ctime = cdfile->date;
buffer->st_size = cdfile->size;
}
return cdfile->size;
}
int cdfs_stat(struct fs *fs, char *name, struct stat64 *buffer) {
struct cdfs *cdfs = (struct cdfs *) fs->data;
struct iso_directory_record *rec;
struct buf *buf;
int rc;
int size;
rc = cdfs_find_file(cdfs, name, strlen(name), &buf, &rec);
if (rc < 0) return rc;
size = isonum_733(rec->size);
if (buffer) {
memset(buffer, 0, sizeof(struct stat64));
if (rec->flags[0] & 2) {
buffer->st_mode = S_IFDIR | S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
} else {
buffer->st_mode = S_IFREG | S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
}
buffer->st_ino = isonum_733(rec->extent);
buffer->st_nlink = 1;
buffer->st_dev = cdfs->devno;
buffer->st_atime = buffer->st_mtime = buffer->st_ctime = cdfs_isodate(rec->date);
buffer->st_size = size;
};
release_buffer(cdfs->cache, buf);
return size;
}
int cdfs_opendir(struct file *filp, char *name) {
struct cdfs *cdfs = (struct cdfs *) filp->fs->data;
struct iso_directory_record *rec;
struct cdfs_file *cdfile;
struct buf *buf;
time_t date;
int size;
int extent;
int flags;
int rc;
// Locate directory
rc = cdfs_find_file(cdfs, name, strlen(name), &buf, &rec);
if (rc < 0) return rc;
flags = isonum_711(rec->flags);
extent = isonum_733(rec->extent);
date = cdfs_isodate(rec->date);
size = isonum_733(rec->size);
release_buffer(cdfs->cache, buf);
if (!(flags & 2)) return -ENOTDIR;
// Allocate and initialize file block
cdfile = (struct cdfs_file *) kmalloc(sizeof(struct cdfs_file));
if (!cdfile) return -ENOMEM;
cdfile->extent = extent;
cdfile->date = date;
cdfile->size = size;
filp->data = cdfile;
filp->mode = S_IFDIR | S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
return 0;
}
int cdfs_readdir(struct file *filp, struct direntry *dirp, int count) {
struct cdfs_file *cdfile = (struct cdfs_file *) filp->data;
struct cdfs *cdfs = (struct cdfs *) filp->fs->data;
struct iso_directory_record *rec;
struct buf *buf;
int namelen;
int reclen;
char *name;
wchar_t *wname;
blkagain:
if (count != 1) return -EINVAL;
if (filp->pos >= cdfile->size) return 0;
// Get directory block
buf = get_buffer(cdfs->cache, cdfile->extent + (int) filp->pos / CDFS_BLOCKSIZE);
if (!buf) return -EIO;
// Locate directory record
recagain:
rec = (struct iso_directory_record *) (buf->data + (int) filp->pos % CDFS_BLOCKSIZE);
reclen = isonum_711(rec->length);
namelen = isonum_711(rec->name_len);
// Check for no more records in block
if (reclen == 0) {
int blkleft = CDFS_BLOCKSIZE - ((int) filp->pos % CDFS_BLOCKSIZE);
release_buffer(cdfs->cache, buf);
filp->pos += blkleft;
goto blkagain;
}
// Check for . and .. entries
if (namelen == 1 && (rec->name[0] == 0 || rec->name[0] == 1)) {
filp->pos += reclen;
goto recagain;
}
// Get info from directory record
dirp->ino = isonum_733(rec->extent);
dirp->reclen = sizeof(struct direntry) - MAXPATH + namelen + 1;
if (cdfs->joliet) {
int n;
namelen /= 2;
wname = (wchar_t *) rec->name;
if (namelen > 1 && ntohs(wname[namelen - 2]) == ';') namelen -= 2;
if (namelen > 0 && ntohs(wname[namelen - 1]) == '.') namelen -= 1;
dirp->namelen = namelen;
for (n = 0; n < namelen; n++) dirp->name[n] = (char) ntohs(wname[n]);
dirp->name[namelen] = 0;
} else {
name = (char *) rec->name;
if (namelen > 1 && name[namelen - 2] == ';') namelen -= 2;
if (namelen > 0 && name[namelen - 1] == '.') namelen -= 1;
dirp->namelen = namelen;
memcpy(dirp->name, name, namelen);
dirp->name[namelen] = 0;
}
filp->pos += reclen;
release_buffer(cdfs->cache, buf);
return 1;
}
struct fsops cdfsops = {
FSOP_OPEN | FSOP_CLOSE | FSOP_FSYNC | FSOP_READ |
FSOP_TELL | FSOP_LSEEK | FSOP_STAT | FSOP_FSTAT |
FSOP_OPENDIR | FSOP_READDIR,
NULL,
NULL,
NULL,
cdfs_mount,
cdfs_umount,
cdfs_statfs,
cdfs_open,
cdfs_close,
NULL,
cdfs_fsync,
cdfs_read,
NULL,
NULL,
cdfs_tell,
cdfs_lseek,
NULL,
NULL,
NULL,
cdfs_fstat,
cdfs_stat,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
cdfs_opendir,
cdfs_readdir
};
void init_cdfs() {
register_filesystem("cdfs", &cdfsops);
}