mirror of
https://github.com/LIV2/WinUAE.git
synced 2025-12-06 00:12:52 +00:00
2711 lines
65 KiB
C++
2711 lines
65 KiB
C++
/*
|
|
* UAE - The Un*x Amiga Emulator
|
|
*
|
|
* Linux isofs/UAE filesystem wrapper
|
|
*
|
|
* Copyright 2012 Toni Wilen
|
|
*
|
|
*/
|
|
|
|
#include "sysconfig.h"
|
|
#include "sysdeps.h"
|
|
|
|
#include "options.h"
|
|
#include "blkdev.h"
|
|
#include "isofs_api.h"
|
|
#include "uae/string.h"
|
|
#include "zfile.h"
|
|
|
|
#include "isofs.h"
|
|
|
|
#define MAX_CACHED_BH_COUNT 100
|
|
//#define MAX_CACHE_INODE_COUNT 10
|
|
#define HASH_SIZE 65536
|
|
|
|
#define CD_BLOCK_SIZE 2048
|
|
#define ISOFS_INVALID_MODE ((isofs_mode_t) -1)
|
|
|
|
#define ISOFS_I(x) (&x->ei)
|
|
#define ISOFS_SB(x) (&x->ei)
|
|
#define IS_ERR(x) (x == NULL)
|
|
|
|
#define XS_IFDIR 0x4000
|
|
#define XS_IFCHR 0x2000
|
|
#define XS_IFIFO 0x1000
|
|
#define XS_IFREG 0x8000
|
|
|
|
#define XS_ISDIR(x) (x & XS_IFDIR)
|
|
|
|
struct buffer_head
|
|
{
|
|
struct buffer_head *next;
|
|
uae_u8 *b_data;
|
|
uae_u32 b_blocknr;
|
|
bool linked;
|
|
int usecnt;
|
|
struct super_block *sb;
|
|
};
|
|
|
|
struct inode
|
|
{
|
|
struct inode *next;
|
|
uae_u32 i_mode;
|
|
isofs_uid_t i_uid;
|
|
isofs_gid_t i_gid;
|
|
uae_u32 i_ino;
|
|
uae_u32 i_size;
|
|
uae_u32 i_blocks;
|
|
struct super_block *i_sb;
|
|
timeval i_mtime;
|
|
timeval i_atime;
|
|
timeval i_ctime;
|
|
iso_inode_info ei;
|
|
TCHAR *name;
|
|
int i_blkbits;
|
|
bool linked;
|
|
int usecnt;
|
|
int lockcnt;
|
|
bool i_isaflags;
|
|
uae_u8 i_aflags;
|
|
TCHAR *i_comment;
|
|
};
|
|
|
|
|
|
struct super_block
|
|
{
|
|
int s_high_sierra;
|
|
int s_blocksize;
|
|
int s_blocksize_bits;
|
|
isofs_sb_info ei;
|
|
int unitnum;
|
|
struct inode *inodes, *root;
|
|
int inode_cnt;
|
|
struct buffer_head *buffer_heads;
|
|
int bh_count;
|
|
bool unknown_media;
|
|
struct inode *hash[HASH_SIZE];
|
|
int hash_miss, hash_hit;
|
|
};
|
|
|
|
static int gethashindex(struct inode *inode)
|
|
{
|
|
return inode->i_ino & (HASH_SIZE - 1);
|
|
}
|
|
|
|
static void free_inode(struct inode *inode)
|
|
{
|
|
if (!inode)
|
|
return;
|
|
inode->i_sb->hash[gethashindex(inode)] = NULL;
|
|
inode->i_sb->inode_cnt--;
|
|
xfree(inode->name);
|
|
xfree(inode->i_comment);
|
|
xfree(inode);
|
|
}
|
|
|
|
static void free_bh(struct buffer_head *bh)
|
|
{
|
|
if (!bh)
|
|
return;
|
|
bh->sb->bh_count--;
|
|
xfree(bh->b_data);
|
|
xfree(bh);
|
|
}
|
|
|
|
static void lock_inode(struct inode *inode)
|
|
{
|
|
inode->lockcnt++;
|
|
}
|
|
static void unlock_inode(struct inode *inode)
|
|
{
|
|
inode->lockcnt--;
|
|
}
|
|
|
|
|
|
static void iput(struct inode *inode)
|
|
{
|
|
if (!inode || inode->linked)
|
|
return;
|
|
|
|
struct super_block *sb = inode->i_sb;
|
|
|
|
#if 0
|
|
struct inode *in;
|
|
while (inode->i_sb->inode_cnt > MAX_CACHE_INODE_COUNT) {
|
|
/* not very fast but better than nothing.. */
|
|
struct inode *minin = NULL, *mininprev = NULL;
|
|
struct inode *prev = NULL;
|
|
in = sb->inodes;
|
|
while (in) {
|
|
if (!in->lockcnt && (minin == NULL || in->usecnt < minin->usecnt)) {
|
|
minin = in;
|
|
mininprev = prev;
|
|
}
|
|
prev = in;
|
|
in = in->next;
|
|
}
|
|
if (!minin)
|
|
break;
|
|
if (mininprev)
|
|
mininprev->next = minin->next;
|
|
else
|
|
sb->inodes = minin->next;
|
|
free_inode(minin);
|
|
}
|
|
#endif
|
|
inode->next = sb->inodes;
|
|
sb->inodes = inode;
|
|
inode->linked = true;
|
|
sb->inode_cnt++;
|
|
sb->hash[gethashindex(inode)] = inode;
|
|
}
|
|
|
|
static struct inode *find_inode(struct super_block *sb, uae_u64 uniq)
|
|
{
|
|
struct inode *inode;
|
|
|
|
inode = sb->hash[uniq & (HASH_SIZE - 1)];
|
|
if (inode && inode->i_ino == uniq) {
|
|
sb->hash_hit++;
|
|
return inode;
|
|
}
|
|
sb->hash_miss++;
|
|
|
|
inode = sb->inodes;
|
|
while (inode) {
|
|
if (inode->i_ino == uniq) {
|
|
inode->usecnt++;
|
|
return inode;
|
|
}
|
|
inode = inode->next;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static buffer_head *sb_bread(struct super_block *sb, uae_u32 block)
|
|
{
|
|
struct buffer_head *bh;
|
|
|
|
bh = sb->buffer_heads;
|
|
while (bh) {
|
|
if (bh->b_blocknr == block) {
|
|
bh->usecnt++;
|
|
return bh;
|
|
}
|
|
bh = bh->next;
|
|
}
|
|
// simple LRU block cache. Should be in blkdev, not here..
|
|
while (sb->bh_count > MAX_CACHED_BH_COUNT) {
|
|
struct buffer_head *minbh = NULL, *minbhprev = NULL;
|
|
struct buffer_head *prev = NULL;
|
|
bh = sb->buffer_heads;
|
|
while (bh) {
|
|
if (minbh == NULL || bh->usecnt < minbh->usecnt) {
|
|
minbh = bh;
|
|
minbhprev = prev;
|
|
}
|
|
prev = bh;
|
|
bh = bh->next;
|
|
}
|
|
if (minbh) {
|
|
if (minbhprev)
|
|
minbhprev->next = minbh->next;
|
|
else
|
|
sb->buffer_heads = minbh->next;
|
|
free_bh(minbh);
|
|
}
|
|
}
|
|
bh = xcalloc (struct buffer_head, 1);
|
|
bh->sb = sb;
|
|
bh->b_data = xmalloc (uae_u8, CD_BLOCK_SIZE);
|
|
bh->b_blocknr = block;
|
|
if (sys_command_cd_read (sb->unitnum, bh->b_data, block, 1)) {
|
|
bh->next = sb->buffer_heads;
|
|
sb->buffer_heads = bh;
|
|
bh->linked = true;
|
|
sb->bh_count++;
|
|
return bh;
|
|
}
|
|
xfree (bh);
|
|
return NULL;
|
|
}
|
|
|
|
static void brelse(struct buffer_head *sh)
|
|
{
|
|
}
|
|
|
|
static TCHAR *getname(char *name, int len)
|
|
{
|
|
TCHAR *s;
|
|
char old;
|
|
|
|
old = name[len];
|
|
while (len > 0 && name[len - 1] == ' ')
|
|
len--;
|
|
name[len] = 0;
|
|
s = au (name);
|
|
name[len] = old;
|
|
return s;
|
|
}
|
|
|
|
static inline uae_u32 isofs_get_ino(unsigned long block, unsigned long offset, unsigned long bufbits)
|
|
{
|
|
return (block << (bufbits - 5)) | (offset >> 5);
|
|
}
|
|
|
|
static inline int isonum_711(char *p)
|
|
{
|
|
return *(uae_u8*)p;
|
|
}
|
|
static inline int isonum_712(char *p)
|
|
{
|
|
return *(uae_u8*)p;
|
|
}
|
|
static inline unsigned int isonum_721(char *pp)
|
|
{
|
|
uae_u8 *p = (uae_u8*)pp;
|
|
return p[0] | (p[1] << 8);
|
|
}
|
|
static inline unsigned int isonum_722(char *pp)
|
|
{
|
|
uae_u8 *p = (uae_u8*)pp;
|
|
return p[1] | (p[0] << 8);
|
|
}
|
|
static inline unsigned int isonum_723(char *pp)
|
|
{
|
|
uae_u8 *p = (uae_u8*)pp;
|
|
/* Ignore bigendian datum due to broken mastering programs */
|
|
return p[0] | (p[1] << 8);
|
|
}
|
|
static inline unsigned int isonum_731(char *pp)
|
|
{
|
|
uae_u8 *p = (uae_u8*)pp;
|
|
return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | (p[3] << 0);
|
|
}
|
|
static inline unsigned int isonum_732(char *pp)
|
|
{
|
|
uae_u8 *p = (uae_u8*)pp;
|
|
return (p[3] << 24) | (p[2] << 16) | (p[1] << 8) | (p[0] << 0);
|
|
}
|
|
static inline unsigned int isonum_733(char *pp)
|
|
{
|
|
uae_u8 *p = (uae_u8*)pp;
|
|
/* Ignore bigendian datum due to broken mastering programs */
|
|
return (p[3] << 24) | (p[2] << 16) | (p[1] << 8) | (p[0] << 0);
|
|
}
|
|
|
|
static void isofs_normalize_block_and_offset(struct iso_directory_record* de, unsigned long *block, unsigned long *offset)
|
|
{
|
|
#if 0
|
|
/* Only directories are normalized. */
|
|
if (de->flags[0] & 2) {
|
|
*offset = 0;
|
|
*block = (unsigned long)isonum_733(de->extent)
|
|
+ (unsigned long)isonum_711(de->ext_attr_length);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static int make_date(int year, int month, int day, int hour, int minute, int second, int tz)
|
|
{
|
|
int crtime, days, i;
|
|
|
|
if (year < 0) {
|
|
crtime = 0;
|
|
} else {
|
|
int monlen[12] = {31,28,31,30,31,30,31,31,30,31,30,31};
|
|
|
|
days = year * 365;
|
|
if (year > 2)
|
|
days += (year+1) / 4;
|
|
for (i = 1; i < month; i++)
|
|
days += monlen[i-1];
|
|
if (((year+2) % 4) == 0 && month > 2)
|
|
days++;
|
|
days += day - 1;
|
|
crtime = ((((days * 24) + hour) * 60 + minute) * 60)
|
|
+ second;
|
|
|
|
/* sign extend */
|
|
if (tz & 0x80)
|
|
tz |= (-1 << 8);
|
|
|
|
/*
|
|
* The timezone offset is unreliable on some disks,
|
|
* so we make a sanity check. In no case is it ever
|
|
* more than 13 hours from GMT, which is 52*15min.
|
|
* The time is always stored in localtime with the
|
|
* timezone offset being what get added to GMT to
|
|
* get to localtime. Thus we need to subtract the offset
|
|
* to get to true GMT, which is what we store the time
|
|
* as internally. On the local system, the user may set
|
|
* their timezone any way they wish, of course, so GMT
|
|
* gets converted back to localtime on the receiving
|
|
* system.
|
|
*
|
|
* NOTE: mkisofs in versions prior to mkisofs-1.10 had
|
|
* the sign wrong on the timezone offset. This has now
|
|
* been corrected there too, but if you are getting screwy
|
|
* results this may be the explanation. If enough people
|
|
* complain, a user configuration option could be added
|
|
* to add the timezone offset in with the wrong sign
|
|
* for 'compatibility' with older discs, but I cannot see how
|
|
* it will matter that much.
|
|
*
|
|
* Thanks to kuhlmav@elec.canterbury.ac.nz (Volker Kuhlmann)
|
|
* for pointing out the sign error.
|
|
*/
|
|
if (-52 <= tz && tz <= 52)
|
|
crtime -= tz * 15 * 60;
|
|
}
|
|
return crtime;
|
|
}
|
|
|
|
/*
|
|
* We have to convert from a MM/DD/YY format to the Unix ctime format.
|
|
* We have to take into account leap years and all of that good stuff.
|
|
* Unfortunately, the kernel does not have the information on hand to
|
|
* take into account daylight savings time, but it shouldn't matter.
|
|
* The time stored should be localtime (with or without DST in effect),
|
|
* and the timezone offset should hold the offset required to get back
|
|
* to GMT. Thus we should always be correct.
|
|
*/
|
|
|
|
static int iso_date(char * p, int flag)
|
|
{
|
|
int year, month, day, hour, minute, second, tz;
|
|
|
|
year = p[0] - 70;
|
|
month = p[1];
|
|
day = p[2];
|
|
hour = p[3];
|
|
minute = p[4];
|
|
second = p[5];
|
|
if (flag == 0) tz = p[6]; /* High sierra has no time zone */
|
|
else tz = 0;
|
|
|
|
return make_date(year, month, day, hour, minute, second, tz);
|
|
}
|
|
|
|
static int iso_ltime(char *p)
|
|
{
|
|
int year, month, day, hour, minute, second;
|
|
char t;
|
|
|
|
t = p[4];
|
|
p[4] = 0;
|
|
year = atol(p);
|
|
p[4] = t;
|
|
t = p[6];
|
|
p[6] = 0;
|
|
month = atol(p + 4);
|
|
p[6] = t;
|
|
t = p[8];
|
|
p[8] = 0;
|
|
day = atol(p + 6);
|
|
p[8] = t;
|
|
t = p[10];
|
|
p[10] = 0;
|
|
hour = atol(p + 8);
|
|
p[10] = t;
|
|
t = p[12];
|
|
p[12] = 0;
|
|
minute = atol(p + 10);
|
|
p[12] = t;
|
|
t = p[14];
|
|
p[14] = 0;
|
|
second = atol(p + 12);
|
|
p[14] = t;
|
|
|
|
return make_date(year - 1970, month, day, hour, minute, second, 0);
|
|
}
|
|
|
|
static int isofs_read_level3_size(struct inode *inode)
|
|
{
|
|
unsigned long bufsize = ISOFS_BUFFER_SIZE(inode);
|
|
int high_sierra = ISOFS_SB(inode->i_sb)->s_high_sierra;
|
|
struct buffer_head *bh = NULL;
|
|
unsigned long block, offset, block_saved, offset_saved;
|
|
int i = 0;
|
|
int more_entries = 0;
|
|
struct iso_directory_record *tmpde = NULL;
|
|
struct iso_inode_info *ei = ISOFS_I(inode);
|
|
|
|
inode->i_size = 0;
|
|
|
|
/* The first 16 blocks are reserved as the System Area. Thus,
|
|
* no inodes can appear in block 0. We use this to flag that
|
|
* this is the last section. */
|
|
ei->i_next_section_block = 0;
|
|
ei->i_next_section_offset = 0;
|
|
|
|
block = ei->i_iget5_block;
|
|
offset = ei->i_iget5_offset;
|
|
|
|
do {
|
|
struct iso_directory_record *de;
|
|
unsigned int de_len;
|
|
|
|
if (!bh) {
|
|
bh = sb_bread(inode->i_sb, block);
|
|
if (!bh)
|
|
goto out_noread;
|
|
}
|
|
de = (struct iso_directory_record *) (bh->b_data + offset);
|
|
de_len = *(unsigned char *) de;
|
|
|
|
if (de_len == 0) {
|
|
brelse(bh);
|
|
bh = NULL;
|
|
++block;
|
|
offset = 0;
|
|
continue;
|
|
}
|
|
|
|
block_saved = block;
|
|
offset_saved = offset;
|
|
offset += de_len;
|
|
|
|
/* Make sure we have a full directory entry */
|
|
if (offset >= bufsize) {
|
|
int slop = bufsize - offset + de_len;
|
|
if (!tmpde) {
|
|
tmpde = (struct iso_directory_record*)xmalloc(uae_u8, 256);
|
|
if (!tmpde)
|
|
goto out_nomem;
|
|
}
|
|
memcpy(tmpde, de, slop);
|
|
offset &= bufsize - 1;
|
|
block++;
|
|
brelse(bh);
|
|
bh = NULL;
|
|
if (offset) {
|
|
bh = sb_bread(inode->i_sb, block);
|
|
if (!bh)
|
|
goto out_noread;
|
|
memcpy((uae_u8*)tmpde+slop, bh->b_data, offset);
|
|
}
|
|
de = tmpde;
|
|
}
|
|
|
|
inode->i_size += isonum_733(de->size);
|
|
if (i == 1) {
|
|
ei->i_next_section_block = block_saved;
|
|
ei->i_next_section_offset = offset_saved;
|
|
}
|
|
|
|
more_entries = de->flags[-high_sierra] & 0x80;
|
|
|
|
i++;
|
|
if (i > 100)
|
|
goto out_toomany;
|
|
} while (more_entries);
|
|
out:
|
|
xfree(tmpde);
|
|
if (bh)
|
|
brelse(bh);
|
|
return 0;
|
|
|
|
out_nomem:
|
|
if (bh)
|
|
brelse(bh);
|
|
return -ENOMEM;
|
|
|
|
out_noread:
|
|
write_log (_T("ISOFS: unable to read i-node block %lu\n"), block);
|
|
xfree(tmpde);
|
|
return -EIO;
|
|
|
|
out_toomany:
|
|
write_log (_T("ISOFS: More than 100 file sections ?!?, aborting... isofs_read_level3_size: inode=%u\n"), inode->i_ino);
|
|
goto out;
|
|
}
|
|
|
|
static int parse_rock_ridge_inode(struct iso_directory_record *de, struct inode *inode);
|
|
|
|
static int isofs_read_inode(struct inode *inode)
|
|
{
|
|
struct super_block *sb = inode->i_sb;
|
|
struct isofs_sb_info *sbi = ISOFS_SB(sb);
|
|
unsigned long bufsize = ISOFS_BUFFER_SIZE(inode);
|
|
unsigned long block;
|
|
int high_sierra = sbi->s_high_sierra;
|
|
struct buffer_head *bh = NULL;
|
|
struct iso_directory_record *de;
|
|
struct iso_directory_record *tmpde = NULL;
|
|
unsigned int de_len;
|
|
unsigned long offset;
|
|
struct iso_inode_info *ei = ISOFS_I(inode);
|
|
int ret = -EIO;
|
|
|
|
block = ei->i_iget5_block;
|
|
bh = sb_bread(inode->i_sb, block);
|
|
if (!bh)
|
|
goto out_badread;
|
|
|
|
offset = ei->i_iget5_offset;
|
|
|
|
de = (struct iso_directory_record *) (bh->b_data + offset);
|
|
de_len = *(unsigned char *) de;
|
|
|
|
if (offset + de_len > bufsize) {
|
|
int frag1 = bufsize - offset;
|
|
|
|
tmpde = (struct iso_directory_record*)xmalloc (uae_u8, de_len);
|
|
if (tmpde == NULL) {
|
|
ret = -ENOMEM;
|
|
goto fail;
|
|
}
|
|
memcpy(tmpde, bh->b_data + offset, frag1);
|
|
brelse(bh);
|
|
bh = sb_bread(inode->i_sb, ++block);
|
|
if (!bh)
|
|
goto out_badread;
|
|
memcpy((char *)tmpde+frag1, bh->b_data, de_len - frag1);
|
|
de = tmpde;
|
|
}
|
|
|
|
inode->i_ino = isofs_get_ino(ei->i_iget5_block, ei->i_iget5_offset, ISOFS_BUFFER_BITS(inode));
|
|
|
|
/* Assume it is a normal-format file unless told otherwise */
|
|
ei->i_file_format = isofs_file_normal;
|
|
|
|
if (de->flags[-high_sierra] & 2) {
|
|
if (sbi->s_dmode != ISOFS_INVALID_MODE)
|
|
inode->i_mode = XS_IFDIR | sbi->s_dmode;
|
|
else
|
|
inode->i_mode = XS_IFDIR; // | S_IRUGO | S_IXUGO;
|
|
} else {
|
|
if (sbi->s_fmode != ISOFS_INVALID_MODE) {
|
|
inode->i_mode = XS_IFREG | sbi->s_fmode;
|
|
} else {
|
|
/*
|
|
* Set default permissions: r-x for all. The disc
|
|
* could be shared with DOS machines so virtually
|
|
* anything could be a valid executable.
|
|
*/
|
|
inode->i_mode = XS_IFREG; // | S_IRUGO | S_IXUGO;
|
|
}
|
|
}
|
|
|
|
inode->i_uid = sbi->s_uid;
|
|
inode->i_gid = sbi->s_gid;
|
|
inode->i_blocks = 0;
|
|
|
|
ei->i_format_parm[0] = 0;
|
|
ei->i_format_parm[1] = 0;
|
|
ei->i_format_parm[2] = 0;
|
|
|
|
ei->i_section_size = isonum_733(de->size);
|
|
if (de->flags[-high_sierra] & 0x80) {
|
|
ret = isofs_read_level3_size(inode);
|
|
if (ret < 0)
|
|
goto fail;
|
|
// FIXME: this value is never used (?), because it is overwritten
|
|
// with ret = 0 further down.
|
|
ret = -EIO;
|
|
} else {
|
|
ei->i_next_section_block = 0;
|
|
ei->i_next_section_offset = 0;
|
|
inode->i_size = isonum_733(de->size);
|
|
}
|
|
|
|
/*
|
|
* Some dipshit decided to store some other bit of information
|
|
* in the high byte of the file length. Truncate size in case
|
|
* this CDROM was mounted with the cruft option.
|
|
*/
|
|
|
|
if (sbi->s_cruft)
|
|
inode->i_size &= 0x00ffffff;
|
|
|
|
if (de->interleave[0]) {
|
|
write_log (_T("ISOFS: Interleaved files not (yet) supported.\n"));
|
|
inode->i_size = 0;
|
|
}
|
|
|
|
/* I have no idea what file_unit_size is used for, so
|
|
we will flag it for now */
|
|
if (de->file_unit_size[0] != 0) {
|
|
write_log (_T("ISOFS: File unit size != 0 for ISO file (%d).\n"), inode->i_ino);
|
|
}
|
|
|
|
/* I have no idea what other flag bits are used for, so
|
|
we will flag it for now */
|
|
if((de->flags[-high_sierra] & ~2)!= 0){
|
|
write_log (_T("ISOFS: Unusual flag settings for ISO file (%d %x).\n"), inode->i_ino, de->flags[-high_sierra]);
|
|
}
|
|
|
|
inode->i_mtime.tv_sec =
|
|
inode->i_atime.tv_sec =
|
|
inode->i_ctime.tv_sec = iso_date(de->date, high_sierra);
|
|
inode->i_mtime.tv_usec =
|
|
inode->i_atime.tv_usec =
|
|
inode->i_ctime.tv_usec = 0;
|
|
|
|
ei->i_first_extent = (isonum_733(de->extent) +
|
|
isonum_711(de->ext_attr_length));
|
|
|
|
/* Set the number of blocks for stat() - should be done before RR */
|
|
inode->i_blocks = (inode->i_size + 511) >> 9;
|
|
|
|
/*
|
|
* Now test for possible Rock Ridge extensions which will override
|
|
* some of these numbers in the inode structure.
|
|
*/
|
|
if (!high_sierra) {
|
|
parse_rock_ridge_inode(de, inode);
|
|
/* if we want uid/gid set, override the rock ridge setting */
|
|
if (sbi->s_uid_set)
|
|
inode->i_uid = sbi->s_uid;
|
|
if (sbi->s_gid_set)
|
|
inode->i_gid = sbi->s_gid;
|
|
}
|
|
|
|
#if 0
|
|
/* Now set final access rights if overriding rock ridge setting */
|
|
if (XS_ISDIR(inode->i_mode) && sbi->s_overriderockperm &&
|
|
sbi->s_dmode != ISOFS_INVALID_MODE)
|
|
inode->i_mode = XS_IFDIR | sbi->s_dmode;
|
|
if (XS_ISREG(inode->i_mode) && sbi->s_overriderockperm &&
|
|
sbi->s_fmode != ISOFS_INVALID_MODE)
|
|
inode->i_mode = XS_IFREG | sbi->s_fmode;
|
|
/* Install the inode operations vector */
|
|
if (XS_ISREG(inode->i_mode)) {
|
|
inode->i_fop = &generic_ro_fops;
|
|
switch (ei->i_file_format) {
|
|
#ifdef CONFIG_ZISOFS
|
|
case isofs_file_compressed:
|
|
inode->i_data.a_ops = &zisofs_aops;
|
|
break;
|
|
#endif
|
|
default:
|
|
inode->i_data.a_ops = &isofs_aops;
|
|
break;
|
|
}
|
|
} else if (XS_ISDIR(inode->i_mode)) {
|
|
inode->i_op = &isofs_dir_inode_operations;
|
|
inode->i_fop = &isofs_dir_operations;
|
|
} else if (XS_ISLNK(inode->i_mode)) {
|
|
inode->i_op = &page_symlink_inode_operations;
|
|
inode->i_data.a_ops = &isofs_symlink_aops;
|
|
} else
|
|
/* XXX - parse_rock_ridge_inode() had already set i_rdev. */
|
|
init_special_inode(inode, inode->i_mode, inode->i_rdev);
|
|
#endif
|
|
|
|
ret = 0;
|
|
out:
|
|
xfree(tmpde);
|
|
if (bh)
|
|
brelse(bh);
|
|
return ret;
|
|
|
|
out_badread:
|
|
write_log(_T("ISOFS: unable to read i-node block\n"));
|
|
fail:
|
|
goto out;
|
|
}
|
|
|
|
|
|
static struct inode *isofs_iget(struct super_block *sb, unsigned long block, unsigned long offset, const TCHAR *name)
|
|
{
|
|
struct inode *inode;
|
|
uae_u32 id;
|
|
|
|
if (offset >= 1ul << sb->s_blocksize_bits)
|
|
return NULL;
|
|
if (sb->root) {
|
|
unsigned char bufbits = ISOFS_BUFFER_BITS(sb->root);
|
|
id = isofs_get_ino(block, offset, bufbits);
|
|
inode = find_inode (sb, id);
|
|
if (inode)
|
|
return inode;
|
|
}
|
|
inode = xcalloc(struct inode, 1);
|
|
inode->name = name ? my_strdup(name) : NULL;
|
|
inode->i_sb = sb;
|
|
inode->ei.i_iget5_block = block;
|
|
inode->ei.i_iget5_offset = offset;
|
|
inode->i_blkbits = sb->s_blocksize_bits;
|
|
isofs_read_inode(inode);
|
|
return inode;
|
|
}
|
|
|
|
|
|
/**************************************************************
|
|
|
|
ROCK RIDGE
|
|
|
|
**************************************************************/
|
|
|
|
|
|
#define SIG(A,B) ((A) | ((B) << 8)) /* isonum_721() */
|
|
|
|
struct rock_state {
|
|
void *buffer;
|
|
unsigned char *chr;
|
|
int len;
|
|
int cont_size;
|
|
int cont_extent;
|
|
int cont_offset;
|
|
struct inode *inode;
|
|
};
|
|
|
|
/*
|
|
* This is a way of ensuring that we have something in the system
|
|
* use fields that is compatible with Rock Ridge. Return zero on success.
|
|
*/
|
|
|
|
static int check_sp(struct rock_ridge *rr, struct inode *inode)
|
|
{
|
|
if (rr->u.SP.magic[0] != 0xbe)
|
|
return -1;
|
|
if (rr->u.SP.magic[1] != 0xef)
|
|
return -1;
|
|
ISOFS_SB(inode->i_sb)->s_rock_offset = rr->u.SP.skip;
|
|
return 0;
|
|
}
|
|
|
|
static void setup_rock_ridge(struct iso_directory_record *de, struct inode *inode, struct rock_state *rs)
|
|
{
|
|
rs->len = sizeof(struct iso_directory_record) + de->name_len[0];
|
|
if (rs->len & 1)
|
|
(rs->len)++;
|
|
rs->chr = (unsigned char *)de + rs->len;
|
|
rs->len = *((unsigned char *)de) - rs->len;
|
|
if (rs->len < 0)
|
|
rs->len = 0;
|
|
|
|
if (ISOFS_SB(inode->i_sb)->s_rock_offset != -1) {
|
|
rs->len -= ISOFS_SB(inode->i_sb)->s_rock_offset;
|
|
rs->chr += ISOFS_SB(inode->i_sb)->s_rock_offset;
|
|
if (rs->len < 0)
|
|
rs->len = 0;
|
|
}
|
|
}
|
|
|
|
static void init_rock_state(struct rock_state *rs, struct inode *inode)
|
|
{
|
|
memset(rs, 0, sizeof(*rs));
|
|
rs->inode = inode;
|
|
}
|
|
|
|
/*
|
|
* Returns 0 if the caller should continue scanning, 1 if the scan must end
|
|
* and -ve on error.
|
|
*/
|
|
static int rock_continue(struct rock_state *rs)
|
|
{
|
|
int ret = 1;
|
|
int blocksize = 1 << rs->inode->i_blkbits;
|
|
const int min_de_size = offsetof(struct rock_ridge, u);
|
|
|
|
xfree(rs->buffer);
|
|
rs->buffer = NULL;
|
|
|
|
if ((unsigned)rs->cont_offset > blocksize - min_de_size || (unsigned)rs->cont_size > blocksize || (unsigned)(rs->cont_offset + rs->cont_size) > blocksize) {
|
|
write_log (_T("rock: corrupted directory entry. extent=%d, offset=%d, size=%d\n"), rs->cont_extent, rs->cont_offset, rs->cont_size);
|
|
ret = -EIO;
|
|
goto out;
|
|
}
|
|
|
|
if (rs->cont_extent) {
|
|
struct buffer_head *bh;
|
|
|
|
rs->buffer = xmalloc(uae_u8, rs->cont_size);
|
|
if (!rs->buffer) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
ret = -EIO;
|
|
bh = sb_bread(rs->inode->i_sb, rs->cont_extent);
|
|
if (bh) {
|
|
memcpy(rs->buffer, bh->b_data + rs->cont_offset, rs->cont_size);
|
|
brelse(bh);
|
|
rs->chr = (unsigned char*)rs->buffer;
|
|
rs->len = rs->cont_size;
|
|
rs->cont_extent = 0;
|
|
rs->cont_size = 0;
|
|
rs->cont_offset = 0;
|
|
return 0;
|
|
}
|
|
write_log (_T("Unable to read rock-ridge attributes\n"));
|
|
}
|
|
out:
|
|
xfree(rs->buffer);
|
|
rs->buffer = NULL;
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* We think there's a record of type `sig' at rs->chr. Parse the signature
|
|
* and make sure that there's really room for a record of that type.
|
|
*/
|
|
static int rock_check_overflow(struct rock_state *rs, int sig)
|
|
{
|
|
int len;
|
|
|
|
switch (sig) {
|
|
case SIG('S', 'P'):
|
|
len = sizeof(struct SU_SP_s);
|
|
break;
|
|
case SIG('C', 'E'):
|
|
len = sizeof(struct SU_CE_s);
|
|
break;
|
|
case SIG('E', 'R'):
|
|
len = sizeof(struct SU_ER_s);
|
|
break;
|
|
case SIG('R', 'R'):
|
|
len = sizeof(struct RR_RR_s);
|
|
break;
|
|
case SIG('P', 'X'):
|
|
len = sizeof(struct RR_PX_s);
|
|
break;
|
|
case SIG('P', 'N'):
|
|
len = sizeof(struct RR_PN_s);
|
|
break;
|
|
case SIG('S', 'L'):
|
|
len = sizeof(struct RR_SL_s);
|
|
break;
|
|
case SIG('N', 'M'):
|
|
len = sizeof(struct RR_NM_s);
|
|
break;
|
|
case SIG('C', 'L'):
|
|
len = sizeof(struct RR_CL_s);
|
|
break;
|
|
case SIG('P', 'L'):
|
|
len = sizeof(struct RR_PL_s);
|
|
break;
|
|
case SIG('T', 'F'):
|
|
len = sizeof(struct RR_TF_s);
|
|
break;
|
|
case SIG('Z', 'F'):
|
|
len = sizeof(struct RR_ZF_s);
|
|
break;
|
|
case SIG('A', 'S'):
|
|
len = sizeof(struct RR_AS_s);
|
|
break;
|
|
default:
|
|
len = 0;
|
|
break;
|
|
}
|
|
len += offsetof(struct rock_ridge, u);
|
|
if (len > rs->len) {
|
|
write_log(_T("rock: directory entry would overflow storage\n"));
|
|
write_log(_T("rock: sig=0x%02x, size=%d, remaining=%d\n"), sig, len, rs->len);
|
|
return -EIO;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* return length of name field; 0: not found, -1: to be ignored
|
|
*/
|
|
static int get_rock_ridge_filename(struct iso_directory_record *de,
|
|
char *retname, struct inode *inode)
|
|
{
|
|
struct rock_state rs;
|
|
struct rock_ridge *rr;
|
|
int sig;
|
|
int retnamlen = 0;
|
|
int truncate = 0;
|
|
int ret = 0;
|
|
|
|
if (!ISOFS_SB(inode->i_sb)->s_rock)
|
|
return 0;
|
|
*retname = 0;
|
|
|
|
init_rock_state(&rs, inode);
|
|
setup_rock_ridge(de, inode, &rs);
|
|
repeat:
|
|
|
|
while (rs.len > 2) { /* There may be one byte for padding somewhere */
|
|
rr = (struct rock_ridge *)rs.chr;
|
|
/*
|
|
* Ignore rock ridge info if rr->len is out of range, but
|
|
* don't return -EIO because that would make the file
|
|
* invisible.
|
|
*/
|
|
if (rr->len < 3)
|
|
goto out; /* Something got screwed up here */
|
|
sig = isonum_721((char*)rs.chr);
|
|
if (rock_check_overflow(&rs, sig))
|
|
goto eio;
|
|
rs.chr += rr->len;
|
|
rs.len -= rr->len;
|
|
/*
|
|
* As above, just ignore the rock ridge info if rr->len
|
|
* is bogus.
|
|
*/
|
|
if (rs.len < 0)
|
|
goto out; /* Something got screwed up here */
|
|
|
|
switch (sig) {
|
|
case SIG('R', 'R'):
|
|
if ((rr->u.RR.flags[0] & RR_NM) == 0)
|
|
goto out;
|
|
break;
|
|
case SIG('S', 'P'):
|
|
if (check_sp(rr, inode))
|
|
goto out;
|
|
break;
|
|
case SIG('C', 'E'):
|
|
rs.cont_extent = isonum_733(rr->u.CE.extent);
|
|
rs.cont_offset = isonum_733(rr->u.CE.offset);
|
|
rs.cont_size = isonum_733(rr->u.CE.size);
|
|
break;
|
|
case SIG('N', 'M'):
|
|
if (truncate)
|
|
break;
|
|
if (rr->len < 5)
|
|
break;
|
|
/*
|
|
* If the flags are 2 or 4, this indicates '.' or '..'.
|
|
* We don't want to do anything with this, because it
|
|
* screws up the code that calls us. We don't really
|
|
* care anyways, since we can just use the non-RR
|
|
* name.
|
|
*/
|
|
if (rr->u.NM.flags & 6)
|
|
break;
|
|
|
|
if (rr->u.NM.flags & ~1) {
|
|
write_log(_T("Unsupported NM flag settings (%d)\n"), rr->u.NM.flags);
|
|
break;
|
|
}
|
|
if ((strlen(retname) + rr->len - 5) >= 254) {
|
|
truncate = 1;
|
|
break;
|
|
}
|
|
strncat(retname, rr->u.NM.name, rr->len - 5);
|
|
retnamlen += rr->len - 5;
|
|
break;
|
|
case SIG('R', 'E'):
|
|
xfree(rs.buffer);
|
|
return -1;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
ret = rock_continue(&rs);
|
|
if (ret == 0)
|
|
goto repeat;
|
|
if (ret == 1)
|
|
return retnamlen; /* If 0, this file did not have a NM field */
|
|
out:
|
|
xfree(rs.buffer);
|
|
return ret;
|
|
eio:
|
|
ret = -EIO;
|
|
goto out;
|
|
}
|
|
|
|
static int parse_rock_ridge_inode_internal(struct iso_directory_record *de, struct inode *inode, int regard_xa)
|
|
{
|
|
int symlink_len = 0;
|
|
int cnt, sig;
|
|
struct inode *reloc;
|
|
struct rock_ridge *rr;
|
|
int rootflag;
|
|
struct rock_state rs;
|
|
int ret = 0;
|
|
|
|
if (!ISOFS_SB(inode->i_sb)->s_rock)
|
|
return 0;
|
|
|
|
init_rock_state(&rs, inode);
|
|
setup_rock_ridge(de, inode, &rs);
|
|
if (regard_xa) {
|
|
rs.chr += 14;
|
|
rs.len -= 14;
|
|
if (rs.len < 0)
|
|
rs.len = 0;
|
|
}
|
|
|
|
repeat:
|
|
while (rs.len > 2) { /* There may be one byte for padding somewhere */
|
|
rr = (struct rock_ridge *)rs.chr;
|
|
/*
|
|
* Ignore rock ridge info if rr->len is out of range, but
|
|
* don't return -EIO because that would make the file
|
|
* invisible.
|
|
*/
|
|
if (rr->len < 3)
|
|
goto out; /* Something got screwed up here */
|
|
sig = isonum_721((char*)rs.chr);
|
|
if (rock_check_overflow(&rs, sig))
|
|
goto eio;
|
|
rs.chr += rr->len;
|
|
rs.len -= rr->len;
|
|
/*
|
|
* As above, just ignore the rock ridge info if rr->len
|
|
* is bogus.
|
|
*/
|
|
if (rs.len < 0)
|
|
goto out; /* Something got screwed up here */
|
|
|
|
switch (sig) {
|
|
case SIG('A', 'S'):
|
|
{
|
|
char *p = &rr->u.AS.data[0];
|
|
if (rr->u.AS.flags & 1) { // PROTECTION
|
|
inode->i_isaflags = true;
|
|
inode->i_aflags = p[3];
|
|
p += 4;
|
|
}
|
|
if (rr->u.AS.flags & 2) { // COMMENT
|
|
const int maxcomment = 80;
|
|
if (!inode->i_comment)
|
|
inode->i_comment = xcalloc (TCHAR, maxcomment + 1);
|
|
int l = p[0];
|
|
char t = p[l];
|
|
p[l] = 0;
|
|
au_copy (inode->i_comment + uaetcslen (inode->i_comment), maxcomment + 1 - uaetcslen (inode->i_comment), p + 1);
|
|
p[l] = t;
|
|
}
|
|
break;
|
|
}
|
|
case SIG('S', 'P'):
|
|
if (check_sp(rr, inode))
|
|
goto out;
|
|
break;
|
|
case SIG('C', 'E'):
|
|
rs.cont_extent = isonum_733(rr->u.CE.extent);
|
|
rs.cont_offset = isonum_733(rr->u.CE.offset);
|
|
rs.cont_size = isonum_733(rr->u.CE.size);
|
|
break;
|
|
case SIG('E', 'R'):
|
|
ISOFS_SB(inode->i_sb)->s_rock = 1;
|
|
write_log(_T("ISO 9660 Extensions: "));
|
|
{
|
|
int p;
|
|
for (p = 0; p < rr->u.ER.len_id; p++)
|
|
write_log(_T("%c"), rr->u.ER.data[p]);
|
|
}
|
|
write_log(_T("\n"));
|
|
break;
|
|
case SIG('P', 'X'):
|
|
inode->i_mode = isonum_733(rr->u.PX.mode);
|
|
//set_nlink(inode, isonum_733(rr->u.PX.n_links));
|
|
inode->i_uid = isonum_733(rr->u.PX.uid);
|
|
inode->i_gid = isonum_733(rr->u.PX.gid);
|
|
break;
|
|
case SIG('P', 'N'):
|
|
{
|
|
int high, low;
|
|
high = isonum_733(rr->u.PN.dev_high);
|
|
low = isonum_733(rr->u.PN.dev_low);
|
|
/*
|
|
* The Rock Ridge standard specifies that if
|
|
* sizeof(dev_t) <= 4, then the high field is
|
|
* unused, and the device number is completely
|
|
* stored in the low field. Some writers may
|
|
* ignore this subtlety,
|
|
* and as a result we test to see if the entire
|
|
* device number is
|
|
* stored in the low field, and use that.
|
|
*/
|
|
#if 0
|
|
if ((low & ~0xff) && high == 0) {
|
|
inode->i_rdev =
|
|
MKDEV(low >> 8, low & 0xff);
|
|
} else {
|
|
inode->i_rdev =
|
|
MKDEV(high, low);
|
|
}
|
|
#endif
|
|
}
|
|
break;
|
|
case SIG('T', 'F'):
|
|
/*
|
|
* Some RRIP writers incorrectly place ctime in the
|
|
* TF_CREATE field. Try to handle this correctly for
|
|
* either case.
|
|
*/
|
|
/* Rock ridge never appears on a High Sierra disk */
|
|
cnt = 0;
|
|
if (rr->u.TF.flags & TF_CREATE) {
|
|
inode->i_ctime.tv_sec =
|
|
iso_date(rr->u.TF.times[cnt++].time,
|
|
0);
|
|
inode->i_ctime.tv_usec = 0;
|
|
}
|
|
if (rr->u.TF.flags & TF_MODIFY) {
|
|
inode->i_mtime.tv_sec =
|
|
iso_date(rr->u.TF.times[cnt++].time,
|
|
0);
|
|
inode->i_mtime.tv_usec = 0;
|
|
}
|
|
if (rr->u.TF.flags & TF_ACCESS) {
|
|
inode->i_atime.tv_sec =
|
|
iso_date(rr->u.TF.times[cnt++].time,
|
|
0);
|
|
inode->i_atime.tv_usec = 0;
|
|
}
|
|
if (rr->u.TF.flags & TF_ATTRIBUTES) {
|
|
inode->i_ctime.tv_sec =
|
|
iso_date(rr->u.TF.times[cnt++].time,
|
|
0);
|
|
inode->i_ctime.tv_usec = 0;
|
|
}
|
|
break;
|
|
case SIG('S', 'L'):
|
|
{
|
|
int slen;
|
|
struct SL_component *slp;
|
|
struct SL_component *oldslp;
|
|
slen = rr->len - 5;
|
|
slp = &rr->u.SL.link;
|
|
inode->i_size = symlink_len;
|
|
while (slen > 1) {
|
|
rootflag = 0;
|
|
switch (slp->flags & ~1) {
|
|
case 0:
|
|
inode->i_size +=
|
|
slp->len;
|
|
break;
|
|
case 2:
|
|
inode->i_size += 1;
|
|
break;
|
|
case 4:
|
|
inode->i_size += 2;
|
|
break;
|
|
case 8:
|
|
rootflag = 1;
|
|
inode->i_size += 1;
|
|
break;
|
|
default:
|
|
write_log(_T("Symlink component flag not implemented\n"));
|
|
}
|
|
slen -= slp->len + 2;
|
|
oldslp = slp;
|
|
slp = (struct SL_component *)
|
|
(((char *)slp) + slp->len + 2);
|
|
|
|
if (slen < 2) {
|
|
if (((rr->u.SL.
|
|
flags & 1) != 0)
|
|
&&
|
|
((oldslp->
|
|
flags & 1) == 0))
|
|
inode->i_size +=
|
|
1;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* If this component record isn't
|
|
* continued, then append a '/'.
|
|
*/
|
|
if (!rootflag
|
|
&& (oldslp->flags & 1) == 0)
|
|
inode->i_size += 1;
|
|
}
|
|
}
|
|
symlink_len = inode->i_size;
|
|
break;
|
|
case SIG('R', 'E'):
|
|
write_log(_T("Attempt to read inode for relocated directory\n"));
|
|
goto out;
|
|
case SIG('C', 'L'):
|
|
ISOFS_I(inode)->i_first_extent = isonum_733(rr->u.CL.location);
|
|
reloc = isofs_iget(inode->i_sb, ISOFS_I(inode)->i_first_extent, 0, NULL);
|
|
if (IS_ERR(reloc)) {
|
|
ret = -1; //PTR_ERR(reloc);
|
|
goto out;
|
|
}
|
|
inode->i_mode = reloc->i_mode;
|
|
//set_nlink(inode, reloc->i_nlink);
|
|
inode->i_uid = reloc->i_uid;
|
|
inode->i_gid = reloc->i_gid;
|
|
//inode->i_rdev = reloc->i_rdev;
|
|
inode->i_size = reloc->i_size;
|
|
inode->i_blocks = reloc->i_blocks;
|
|
inode->i_atime = reloc->i_atime;
|
|
inode->i_ctime = reloc->i_ctime;
|
|
inode->i_mtime = reloc->i_mtime;
|
|
iput(reloc);
|
|
break;
|
|
#ifdef CONFIG_ZISOFS
|
|
case SIG('Z', 'F'): {
|
|
int algo;
|
|
|
|
if (ISOFS_SB(inode->i_sb)->s_nocompress)
|
|
break;
|
|
algo = isonum_721(rr->u.ZF.algorithm);
|
|
if (algo == SIG('p', 'z')) {
|
|
int block_shift =
|
|
isonum_711(&rr->u.ZF.parms[1]);
|
|
if (block_shift > 17) {
|
|
printk(KERN_WARNING "isofs: "
|
|
"Can't handle ZF block "
|
|
"size of 2^%d\n",
|
|
block_shift);
|
|
} else {
|
|
/*
|
|
* Note: we don't change
|
|
* i_blocks here
|
|
*/
|
|
ISOFS_I(inode)->i_file_format =
|
|
isofs_file_compressed;
|
|
/*
|
|
* Parameters to compression
|
|
* algorithm (header size,
|
|
* block size)
|
|
*/
|
|
ISOFS_I(inode)->i_format_parm[0] =
|
|
isonum_711(&rr->u.ZF.parms[0]);
|
|
ISOFS_I(inode)->i_format_parm[1] =
|
|
isonum_711(&rr->u.ZF.parms[1]);
|
|
inode->i_size =
|
|
isonum_733(rr->u.ZF.
|
|
real_size);
|
|
}
|
|
} else {
|
|
printk(KERN_WARNING
|
|
"isofs: Unknown ZF compression "
|
|
"algorithm: %c%c\n",
|
|
rr->u.ZF.algorithm[0],
|
|
rr->u.ZF.algorithm[1]);
|
|
}
|
|
break;
|
|
}
|
|
#endif
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
ret = rock_continue(&rs);
|
|
if (ret == 0)
|
|
goto repeat;
|
|
if (ret == 1)
|
|
ret = 0;
|
|
out:
|
|
xfree(rs.buffer);
|
|
return ret;
|
|
eio:
|
|
ret = -EIO;
|
|
goto out;
|
|
}
|
|
|
|
static char *get_symlink_chunk(char *rpnt, struct rock_ridge *rr, char *plimit)
|
|
{
|
|
int slen;
|
|
int rootflag;
|
|
struct SL_component *oldslp;
|
|
struct SL_component *slp;
|
|
slen = rr->len - 5;
|
|
slp = &rr->u.SL.link;
|
|
while (slen > 1) {
|
|
rootflag = 0;
|
|
switch (slp->flags & ~1) {
|
|
case 0:
|
|
if (slp->len > plimit - rpnt)
|
|
return NULL;
|
|
memcpy(rpnt, slp->text, slp->len);
|
|
rpnt += slp->len;
|
|
break;
|
|
case 2:
|
|
if (rpnt >= plimit)
|
|
return NULL;
|
|
*rpnt++ = '.';
|
|
break;
|
|
case 4:
|
|
if (2 > plimit - rpnt)
|
|
return NULL;
|
|
*rpnt++ = '.';
|
|
*rpnt++ = '.';
|
|
break;
|
|
case 8:
|
|
if (rpnt >= plimit)
|
|
return NULL;
|
|
rootflag = 1;
|
|
*rpnt++ = '/';
|
|
break;
|
|
default:
|
|
write_log(_T("Symlink component flag not implemented (%d)\n"), slp->flags);
|
|
}
|
|
slen -= slp->len + 2;
|
|
oldslp = slp;
|
|
slp = (struct SL_component *)((char *)slp + slp->len + 2);
|
|
|
|
if (slen < 2) {
|
|
/*
|
|
* If there is another SL record, and this component
|
|
* record isn't continued, then add a slash.
|
|
*/
|
|
if ((!rootflag) && (rr->u.SL.flags & 1) &&
|
|
!(oldslp->flags & 1)) {
|
|
if (rpnt >= plimit)
|
|
return NULL;
|
|
*rpnt++ = '/';
|
|
}
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* If this component record isn't continued, then append a '/'.
|
|
*/
|
|
if (!rootflag && !(oldslp->flags & 1)) {
|
|
if (rpnt >= plimit)
|
|
return NULL;
|
|
*rpnt++ = '/';
|
|
}
|
|
}
|
|
return rpnt;
|
|
}
|
|
|
|
static int parse_rock_ridge_inode(struct iso_directory_record *de, struct inode *inode)
|
|
{
|
|
int result = parse_rock_ridge_inode_internal(de, inode, 0);
|
|
|
|
/*
|
|
* if rockridge flag was reset and we didn't look for attributes
|
|
* behind eventual XA attributes, have a look there
|
|
*/
|
|
if ((ISOFS_SB(inode->i_sb)->s_rock_offset == -1)
|
|
&& (ISOFS_SB(inode->i_sb)->s_rock == 2)) {
|
|
result = parse_rock_ridge_inode_internal(de, inode, 14);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#if 0
|
|
/*
|
|
* readpage() for symlinks: reads symlink contents into the page and either
|
|
* makes it uptodate and returns 0 or returns error (-EIO)
|
|
*/
|
|
static int rock_ridge_symlink_readpage(struct file *file, struct page *page)
|
|
{
|
|
struct inode *inode = page->mapping->host;
|
|
struct iso_inode_info *ei = ISOFS_I(inode);
|
|
struct isofs_sb_info *sbi = ISOFS_SB(inode->i_sb);
|
|
char *link = kmap(page);
|
|
unsigned long bufsize = ISOFS_BUFFER_SIZE(inode);
|
|
struct buffer_head *bh;
|
|
char *rpnt = link;
|
|
unsigned char *pnt;
|
|
struct iso_directory_record *raw_de;
|
|
unsigned long block, offset;
|
|
int sig;
|
|
struct rock_ridge *rr;
|
|
struct rock_state rs;
|
|
int ret;
|
|
|
|
if (!sbi->s_rock)
|
|
goto error;
|
|
|
|
init_rock_state(&rs, inode);
|
|
block = ei->i_iget5_block;
|
|
bh = sb_bread(inode->i_sb, block);
|
|
if (!bh)
|
|
goto out_noread;
|
|
|
|
offset = ei->i_iget5_offset;
|
|
pnt = (unsigned char *)bh->b_data + offset;
|
|
|
|
raw_de = (struct iso_directory_record *)pnt;
|
|
|
|
/*
|
|
* If we go past the end of the buffer, there is some sort of error.
|
|
*/
|
|
if (offset + *pnt > bufsize)
|
|
goto out_bad_span;
|
|
|
|
/*
|
|
* Now test for possible Rock Ridge extensions which will override
|
|
* some of these numbers in the inode structure.
|
|
*/
|
|
|
|
setup_rock_ridge(raw_de, inode, &rs);
|
|
|
|
repeat:
|
|
while (rs.len > 2) { /* There may be one byte for padding somewhere */
|
|
rr = (struct rock_ridge *)rs.chr;
|
|
if (rr->len < 3)
|
|
goto out; /* Something got screwed up here */
|
|
sig = isonum_721(rs.chr);
|
|
if (rock_check_overflow(&rs, sig))
|
|
goto out;
|
|
rs.chr += rr->len;
|
|
rs.len -= rr->len;
|
|
if (rs.len < 0)
|
|
goto out; /* corrupted isofs */
|
|
|
|
switch (sig) {
|
|
case SIG('R', 'R'):
|
|
if ((rr->u.RR.flags[0] & RR_SL) == 0)
|
|
goto out;
|
|
break;
|
|
case SIG('S', 'P'):
|
|
if (check_sp(rr, inode))
|
|
goto out;
|
|
break;
|
|
case SIG('S', 'L'):
|
|
rpnt = get_symlink_chunk(rpnt, rr,
|
|
link + (PAGE_SIZE - 1));
|
|
if (rpnt == NULL)
|
|
goto out;
|
|
break;
|
|
case SIG('C', 'E'):
|
|
/* This tells is if there is a continuation record */
|
|
rs.cont_extent = isonum_733(rr->u.CE.extent);
|
|
rs.cont_offset = isonum_733(rr->u.CE.offset);
|
|
rs.cont_size = isonum_733(rr->u.CE.size);
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
ret = rock_continue(&rs);
|
|
if (ret == 0)
|
|
goto repeat;
|
|
if (ret < 0)
|
|
goto fail;
|
|
|
|
if (rpnt == link)
|
|
goto fail;
|
|
brelse(bh);
|
|
*rpnt = '\0';
|
|
SetPageUptodate(page);
|
|
kunmap(page);
|
|
unlock_page(page);
|
|
return 0;
|
|
|
|
/* error exit from macro */
|
|
out:
|
|
kfree(rs.buffer);
|
|
goto fail;
|
|
out_noread:
|
|
printk("unable to read i-node block");
|
|
goto fail;
|
|
out_bad_span:
|
|
printk("symlink spans iso9660 blocks\n");
|
|
fail:
|
|
brelse(bh);
|
|
error:
|
|
SetPageError(page);
|
|
kunmap(page);
|
|
unlock_page(page);
|
|
return -EIO;
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
static TCHAR *get_joliet_name(char *name, unsigned char len, bool utf8)
|
|
{
|
|
TCHAR *out;
|
|
|
|
if (utf8) {
|
|
/* probably never used */
|
|
uae_char *o = xmalloc(uae_char, len + 1);
|
|
for (int i = 0; i < len; i++)
|
|
o[i] = name[i];
|
|
o[len] = 0;
|
|
out = utf8u(o);
|
|
xfree(o);
|
|
} else {
|
|
len /= 2;
|
|
out = xmalloc(TCHAR, len + 1);
|
|
for (int i = 0; i < len; i++)
|
|
out[i] = isonum_722(name + i * 2);
|
|
out[len] = 0;
|
|
}
|
|
|
|
if ((len > 2) && (out[len - 2] == ';') && (out[len - 1] == '1')) {
|
|
len -= 2;
|
|
out[len] = 0;
|
|
}
|
|
|
|
/*
|
|
* Windows doesn't like periods at the end of a name,
|
|
* so neither do we
|
|
*/
|
|
while (len >= 2 && (out[len - 1] == '.')) {
|
|
len--;
|
|
out[len] = 0;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
static TCHAR *get_joliet_filename(struct iso_directory_record * de, struct inode * inode)
|
|
{
|
|
unsigned char utf8;
|
|
//struct nls_table *nls;
|
|
TCHAR *out;
|
|
|
|
utf8 = ISOFS_SB(inode->i_sb)->s_utf8;
|
|
//nls = ISOFS_SB(inode->i_sb)->s_nls_iocharset;
|
|
|
|
out = get_joliet_name(de->name, de->name_len[0], utf8 != 0);
|
|
return out;
|
|
}
|
|
|
|
|
|
/************************************************************ */
|
|
|
|
|
|
|
|
/*
|
|
* Get a set of blocks; filling in buffer_heads if already allocated
|
|
* or getblk() if they are not. Returns the number of blocks inserted
|
|
* (-ve == error.)
|
|
*/
|
|
static int isofs_get_blocks(struct inode *inode, uae_u32 iblock, struct buffer_head *bh, unsigned long nblocks)
|
|
{
|
|
unsigned int b_off = iblock;
|
|
unsigned offset, sect_size;
|
|
unsigned int firstext;
|
|
unsigned int nextblk, nextoff;
|
|
int section, rv, error;
|
|
struct iso_inode_info *ei = ISOFS_I(inode);
|
|
|
|
error = -1;
|
|
rv = 0;
|
|
#if 0
|
|
if (iblock != b_off) {
|
|
write(KERN_DEBUG "%s: block number too large\n", __func__);
|
|
goto abort;
|
|
}
|
|
#endif
|
|
|
|
offset = 0;
|
|
firstext = ei->i_first_extent;
|
|
sect_size = (unsigned int)(ei->i_section_size >> ISOFS_BUFFER_BITS(inode));
|
|
nextblk = ei->i_next_section_block;
|
|
nextoff = ei->i_next_section_offset;
|
|
section = 0;
|
|
|
|
while (nblocks) {
|
|
/* If we are *way* beyond the end of the file, print a message.
|
|
* Access beyond the end of the file up to the next page boundary
|
|
* is normal, however because of the way the page cache works.
|
|
* In this case, we just return 0 so that we can properly fill
|
|
* the page with useless information without generating any
|
|
* I/O errors.
|
|
*/
|
|
if (b_off > ((inode->i_size) >> ISOFS_BUFFER_BITS(inode))) {
|
|
write_log (_T("ISOFS: block >= EOF (%u, %llu)\n"), b_off, (unsigned long long)inode->i_size);
|
|
goto abort;
|
|
}
|
|
|
|
/* On the last section, nextblk == 0, section size is likely to
|
|
* exceed sect_size by a partial block, and access beyond the
|
|
* end of the file will reach beyond the section size, too.
|
|
*/
|
|
while (nextblk && (b_off >= (offset + sect_size))) {
|
|
struct inode *ninode;
|
|
|
|
offset += sect_size;
|
|
ninode = isofs_iget(inode->i_sb, nextblk, nextoff, NULL);
|
|
if (IS_ERR(ninode)) {
|
|
//error = PTR_ERR(ninode);
|
|
goto abort;
|
|
}
|
|
firstext = ISOFS_I(ninode)->i_first_extent;
|
|
sect_size = (unsigned int)(ISOFS_I(ninode)->i_section_size >> ISOFS_BUFFER_BITS(ninode));
|
|
nextblk = ISOFS_I(ninode)->i_next_section_block;
|
|
nextoff = ISOFS_I(ninode)->i_next_section_offset;
|
|
iput(ninode);
|
|
|
|
if (++section > 100) {
|
|
write_log (_T("ISOFS: More than 100 file sections ?!? aborting...\n"));
|
|
goto abort;
|
|
}
|
|
}
|
|
|
|
if (bh) {
|
|
bh->b_blocknr = firstext + b_off - offset;
|
|
}
|
|
bh++; /* Next buffer head */
|
|
b_off++; /* Next buffer offset */
|
|
nblocks--;
|
|
rv++;
|
|
}
|
|
|
|
error = 0;
|
|
abort:
|
|
return rv != 0 ? rv : -1;
|
|
}
|
|
|
|
static struct buffer_head *isofs_bread(struct inode *inode, uae_u32 block)
|
|
{
|
|
struct buffer_head dummy[1];
|
|
int error;
|
|
|
|
error = isofs_get_blocks(inode, block, dummy, 1);
|
|
if (error < 0)
|
|
return NULL;
|
|
return sb_bread(inode->i_sb, dummy[0].b_blocknr);
|
|
}
|
|
|
|
|
|
/*
|
|
* Check if root directory is empty (has less than 3 files).
|
|
*
|
|
* Used to detect broken CDs where ISO root directory is empty but Joliet root
|
|
* directory is OK. If such CD has Rock Ridge extensions, they will be disabled
|
|
* (and Joliet used instead) or else no files would be visible.
|
|
*/
|
|
static bool rootdir_empty(struct super_block *sb, unsigned long block)
|
|
{
|
|
int offset = 0, files = 0, de_len;
|
|
struct iso_directory_record *de;
|
|
struct buffer_head *bh;
|
|
|
|
bh = sb_bread(sb, block);
|
|
if (!bh)
|
|
return true;
|
|
while (files < 3) {
|
|
de = (struct iso_directory_record *) (bh->b_data + offset);
|
|
de_len = *(unsigned char *) de;
|
|
if (de_len == 0)
|
|
break;
|
|
files++;
|
|
offset += de_len;
|
|
}
|
|
brelse(bh);
|
|
return files < 3;
|
|
}
|
|
|
|
/*
|
|
* Initialize the superblock and read the root inode.
|
|
*
|
|
* Note: a check_disk_change() has been done immediately prior
|
|
* to this call, so we don't need to check again.
|
|
*/
|
|
static int isofs_fill_super(struct super_block *s, void *data, int silent, uae_u64 *uniq)
|
|
{
|
|
struct buffer_head *bh = NULL, *pri_bh = NULL;
|
|
struct hs_primary_descriptor *h_pri = NULL;
|
|
struct iso_primary_descriptor *pri = NULL;
|
|
struct iso_supplementary_descriptor *sec = NULL;
|
|
struct iso_directory_record *rootp;
|
|
struct inode *inode;
|
|
struct iso9660_options opt;
|
|
struct isofs_sb_info *sbi;
|
|
unsigned long first_data_zone;
|
|
int joliet_level = 0;
|
|
int iso_blknum, block;
|
|
int orig_zonesize;
|
|
int table, error = -EINVAL;
|
|
unsigned int vol_desc_start;
|
|
TCHAR *volume_name = NULL, *ch;
|
|
uae_u32 volume_date;
|
|
|
|
//save_mount_options(s, data);
|
|
|
|
sbi = &s->ei;
|
|
|
|
memset (&opt, 0, sizeof opt);
|
|
//if (!parse_options((char *)data, &opt))
|
|
// goto out_freesbi;
|
|
|
|
opt.blocksize = 2048;
|
|
opt.map = 'n';
|
|
opt.rock = 1;
|
|
opt.joliet = 1;
|
|
|
|
sbi->s_high_sierra = 0; /* default is iso9660 */
|
|
|
|
vol_desc_start = 0;
|
|
#if 0
|
|
struct device_info di;
|
|
if (sys_command_info (s->unitnum, &di, true)) {
|
|
vol_desc_start = di.toc.firstaddress;
|
|
}
|
|
#endif
|
|
|
|
for (iso_blknum = vol_desc_start+16; iso_blknum < vol_desc_start+100; iso_blknum++) {
|
|
struct hs_volume_descriptor *hdp;
|
|
struct iso_volume_descriptor *vdp;
|
|
|
|
block = iso_blknum << ISOFS_BLOCK_BITS;
|
|
if (!(bh = sb_bread(s, block)))
|
|
goto out_no_read;
|
|
|
|
vdp = (struct iso_volume_descriptor *)bh->b_data;
|
|
hdp = (struct hs_volume_descriptor *)bh->b_data;
|
|
|
|
/*
|
|
* Due to the overlapping physical location of the descriptors,
|
|
* ISO CDs can match hdp->id==HS_STANDARD_ID as well. To ensure
|
|
* proper identification in this case, we first check for ISO.
|
|
*/
|
|
if (strncmp (vdp->id, ISO_STANDARD_ID, sizeof vdp->id) == 0) {
|
|
if (isonum_711(vdp->type) == ISO_VD_END)
|
|
break;
|
|
if (isonum_711(vdp->type) == ISO_VD_PRIMARY) {
|
|
if (pri == NULL) {
|
|
pri = (struct iso_primary_descriptor *)vdp;
|
|
/* Save the buffer in case we need it ... */
|
|
pri_bh = bh;
|
|
bh = NULL;
|
|
}
|
|
}
|
|
else if (isonum_711(vdp->type) == ISO_VD_SUPPLEMENTARY) {
|
|
sec = (struct iso_supplementary_descriptor *)vdp;
|
|
if (sec->escape[0] == 0x25 && sec->escape[1] == 0x2f) {
|
|
if (opt.joliet) {
|
|
if (sec->escape[2] == 0x40)
|
|
joliet_level = 1;
|
|
else if (sec->escape[2] == 0x43)
|
|
joliet_level = 2;
|
|
else if (sec->escape[2] == 0x45)
|
|
joliet_level = 3;
|
|
|
|
write_log (_T("ISO 9660 Extensions: Microsoft Joliet Level %d\n"), joliet_level);
|
|
}
|
|
goto root_found;
|
|
} else {
|
|
/* Unknown supplementary volume descriptor */
|
|
sec = NULL;
|
|
}
|
|
}
|
|
} else {
|
|
if (strncmp (hdp->id, HS_STANDARD_ID, sizeof hdp->id) == 0) {
|
|
if (isonum_711(hdp->type) != ISO_VD_PRIMARY)
|
|
goto out_freebh;
|
|
|
|
sbi->s_high_sierra = 1;
|
|
opt.rock = 0;
|
|
h_pri = (struct hs_primary_descriptor *)vdp;
|
|
goto root_found;
|
|
}
|
|
}
|
|
|
|
/* Just skip any volume descriptors we don't recognize */
|
|
|
|
brelse(bh);
|
|
bh = NULL;
|
|
}
|
|
/*
|
|
* If we fall through, either no volume descriptor was found,
|
|
* or else we passed a primary descriptor looking for others.
|
|
*/
|
|
if (!pri)
|
|
goto out_unknown_format;
|
|
brelse(bh);
|
|
bh = pri_bh;
|
|
pri_bh = NULL;
|
|
|
|
root_found:
|
|
|
|
if (joliet_level && (pri == NULL || !opt.rock)) {
|
|
/* This is the case of Joliet with the norock mount flag.
|
|
* A disc with both Joliet and Rock Ridge is handled later
|
|
*/
|
|
pri = (struct iso_primary_descriptor *) sec;
|
|
}
|
|
|
|
if(sbi->s_high_sierra){
|
|
rootp = (struct iso_directory_record *) h_pri->root_directory_record;
|
|
sbi->s_nzones = isonum_733(h_pri->volume_space_size);
|
|
sbi->s_log_zone_size = isonum_723(h_pri->logical_block_size);
|
|
sbi->s_max_size = isonum_733(h_pri->volume_space_size);
|
|
} else {
|
|
if (!pri)
|
|
goto out_freebh;
|
|
rootp = (struct iso_directory_record *) pri->root_directory_record;
|
|
sbi->s_nzones = isonum_733(pri->volume_space_size);
|
|
sbi->s_log_zone_size = isonum_723(pri->logical_block_size);
|
|
sbi->s_max_size = isonum_733(pri->volume_space_size);
|
|
}
|
|
|
|
sbi->s_ninodes = 0; /* No way to figure this out easily */
|
|
|
|
orig_zonesize = sbi->s_log_zone_size;
|
|
/*
|
|
* If the zone size is smaller than the hardware sector size,
|
|
* this is a fatal error. This would occur if the disc drive
|
|
* had sectors that were 2048 bytes, but the filesystem had
|
|
* blocks that were 512 bytes (which should only very rarely
|
|
* happen.)
|
|
*/
|
|
if (orig_zonesize < opt.blocksize)
|
|
goto out_bad_size;
|
|
|
|
/* RDE: convert log zone size to bit shift */
|
|
switch (sbi->s_log_zone_size) {
|
|
case 512: sbi->s_log_zone_size = 9; break;
|
|
case 1024: sbi->s_log_zone_size = 10; break;
|
|
case 2048: sbi->s_log_zone_size = 11; break;
|
|
|
|
default:
|
|
goto out_bad_zone_size;
|
|
}
|
|
|
|
//s->s_magic = ISOFS_SUPER_MAGIC;
|
|
|
|
/*
|
|
* With multi-extent files, file size is only limited by the maximum
|
|
* size of a file system, which is 8 TB.
|
|
*/
|
|
//s->s_maxbytes = 0x80000000000LL;
|
|
|
|
/*
|
|
* The CDROM is read-only, has no nodes (devices) on it, and since
|
|
* all of the files appear to be owned by root, we really do not want
|
|
* to allow suid. (suid or devices will not show up unless we have
|
|
* Rock Ridge extensions)
|
|
*/
|
|
|
|
//s->s_flags |= MS_RDONLY /* | MS_NODEV | MS_NOSUID */;
|
|
|
|
/* Set this for reference. Its not currently used except on write
|
|
which we don't have .. */
|
|
|
|
first_data_zone = isonum_733(rootp->extent) + isonum_711(rootp->ext_attr_length);
|
|
sbi->s_firstdatazone = first_data_zone;
|
|
|
|
write_log (_T("ISOFS: Max size:%ld Log zone size:%ld\n"), sbi->s_max_size, 1UL << sbi->s_log_zone_size);
|
|
write_log (_T("ISOFS: First datazone:%ld\n"), sbi->s_firstdatazone);
|
|
if(sbi->s_high_sierra)
|
|
write_log(_T("ISOFS: Disc in High Sierra format.\n"));
|
|
ch = getname(pri->system_id, 4);
|
|
write_log (_T("ISOFS: System ID: %s"), ch);
|
|
xfree(ch);
|
|
volume_name = getname(pri->volume_id, 32);
|
|
volume_date = iso_ltime(pri->creation_date);
|
|
write_log (_T(" Volume ID: '%s'\n"), volume_name);
|
|
if (!strncmp(pri->system_id, ISO_SYSTEM_ID_CDTV, strlen(ISO_SYSTEM_ID_CDTV)))
|
|
sbi->s_cdtv = 1;
|
|
|
|
/*
|
|
* If the Joliet level is set, we _may_ decide to use the
|
|
* secondary descriptor, but can't be sure until after we
|
|
* read the root inode. But before reading the root inode
|
|
* we may need to change the device blocksize, and would
|
|
* rather release the old buffer first. So, we cache the
|
|
* first_data_zone value from the secondary descriptor.
|
|
*/
|
|
if (joliet_level) {
|
|
pri = (struct iso_primary_descriptor *) sec;
|
|
rootp = (struct iso_directory_record *)pri->root_directory_record;
|
|
first_data_zone = isonum_733(rootp->extent) + isonum_711(rootp->ext_attr_length);
|
|
}
|
|
|
|
|
|
/*
|
|
* We're all done using the volume descriptor, and may need
|
|
* to change the device blocksize, so release the buffer now.
|
|
*/
|
|
brelse(pri_bh);
|
|
brelse(bh);
|
|
|
|
#if 0
|
|
if (joliet_level && opt.utf8 == 0) {
|
|
char *p = opt.iocharset ? opt.iocharset : CONFIG_NLS_DEFAULT;
|
|
sbi->s_nls_iocharset = load_nls(p);
|
|
if (! sbi->s_nls_iocharset) {
|
|
/* Fail only if explicit charset specified */
|
|
if (opt.iocharset)
|
|
goto out_freesbi;
|
|
sbi->s_nls_iocharset = load_nls_default();
|
|
}
|
|
}
|
|
#endif
|
|
//s->s_op = &isofs_sops;
|
|
//s->s_export_op = &isofs_export_ops;
|
|
|
|
sbi->s_mapping = opt.map;
|
|
sbi->s_rock = (opt.rock ? 2 : 0);
|
|
sbi->s_rock_offset = -1; /* initial offset, will guess until SP is found*/
|
|
sbi->s_cruft = opt.cruft;
|
|
sbi->s_hide = opt.hide;
|
|
sbi->s_showassoc = opt.showassoc;
|
|
sbi->s_uid = opt.uid;
|
|
sbi->s_gid = opt.gid;
|
|
sbi->s_uid_set = opt.uid_set;
|
|
sbi->s_gid_set = opt.gid_set;
|
|
sbi->s_utf8 = opt.utf8;
|
|
sbi->s_nocompress = opt.nocompress;
|
|
sbi->s_overriderockperm = opt.overriderockperm;
|
|
|
|
/*
|
|
* Read the root inode, which _may_ result in changing
|
|
* the s_rock flag. Once we have the final s_rock value,
|
|
* we then decide whether to use the Joliet descriptor.
|
|
*/
|
|
inode = isofs_iget(s, sbi->s_firstdatazone, 0, NULL);
|
|
if (IS_ERR(inode))
|
|
goto out_no_root;
|
|
|
|
|
|
/*
|
|
* Fix for broken CDs with Rock Ridge and empty ISO root directory but
|
|
* correct Joliet root directory.
|
|
*/
|
|
if (sbi->s_rock == 1 && joliet_level && rootdir_empty(s, sbi->s_firstdatazone)) {
|
|
write_log(_T("ISOFS: primary root directory is empty. Disabling Rock Ridge and switching to Joliet.\n"));
|
|
sbi->s_rock = 0;
|
|
}
|
|
|
|
/*
|
|
* If this disk has both Rock Ridge and Joliet on it, then we
|
|
* want to use Rock Ridge by default. This can be overridden
|
|
* by using the norock mount option. There is still one other
|
|
* possibility that is not taken into account: a Rock Ridge
|
|
* CD with Unicode names. Until someone sees such a beast, it
|
|
* will not be supported.
|
|
*/
|
|
if (sbi->s_rock == 1) {
|
|
joliet_level = 0;
|
|
sbi->s_cdtv = 1; /* only convert if plain iso9660 */
|
|
} else if (joliet_level) {
|
|
sbi->s_rock = 0;
|
|
sbi->s_cdtv = 1; /* only convert if plain iso9660 */
|
|
if (sbi->s_firstdatazone != first_data_zone) {
|
|
sbi->s_firstdatazone = first_data_zone;
|
|
write_log (_T("ISOFS: changing to secondary root\n"));
|
|
iput(inode);
|
|
inode = isofs_iget(s, sbi->s_firstdatazone, 0, NULL);
|
|
if (IS_ERR(inode))
|
|
goto out_no_root;
|
|
TCHAR *volname = get_joliet_name(pri->volume_id, 28, sbi->s_utf8);
|
|
if (volname && volname[0] != '\0') {
|
|
xfree(volume_name);
|
|
volume_name = volname;
|
|
write_log(_T("ISOFS: Joliet Volume ID: '%s'\n"), volume_name);
|
|
} else {
|
|
xfree(volname);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (opt.check == 'u') {
|
|
/* Only Joliet is case insensitive by default */
|
|
if (joliet_level)
|
|
opt.check = 'r';
|
|
else
|
|
opt.check = 's';
|
|
}
|
|
sbi->s_joliet_level = joliet_level;
|
|
|
|
/* Make sure the root inode is a directory */
|
|
if (!XS_ISDIR(inode->i_mode)) {
|
|
write_log (_T("isofs_fill_super: root inode is not a directory. Corrupted media?\n"));
|
|
goto out_iput;
|
|
}
|
|
|
|
table = 0;
|
|
if (joliet_level)
|
|
table += 2;
|
|
if (opt.check == 'r')
|
|
table++;
|
|
|
|
//s->s_d_op = &isofs_dentry_ops[table];
|
|
|
|
/* get the root dentry */
|
|
//s->s_root = d_alloc_root(inode);
|
|
//if (!(s->s_root))
|
|
// goto out_no_root;
|
|
|
|
//kfree(opt.iocharset);
|
|
|
|
iput(inode);
|
|
s->root = inode;
|
|
inode->name = volume_name;
|
|
inode->i_ctime.tv_sec = volume_date;
|
|
*uniq = inode->i_ino;
|
|
return 0;
|
|
|
|
/*
|
|
* Display error messages and free resources.
|
|
*/
|
|
out_iput:
|
|
iput(inode);
|
|
goto out_no_inode;
|
|
out_no_root:
|
|
write_log (_T("ISOFS: get root inode failed\n"));
|
|
out_no_inode:
|
|
#ifdef CONFIG_JOLIET
|
|
unload_nls(sbi->s_nls_iocharset);
|
|
#endif
|
|
goto out_freesbi;
|
|
out_no_read:
|
|
write_log (_T("ISOFS: bread failed, dev=%d, iso_blknum=%d, block=%d\n"), s->unitnum, iso_blknum, block);
|
|
goto out_freebh;
|
|
out_bad_zone_size:
|
|
write_log(_T("ISOFS: Bad logical zone size %ld\n"), sbi->s_log_zone_size);
|
|
goto out_freebh;
|
|
out_bad_size:
|
|
write_log (_T("ISOFS: Logical zone size(%d) < hardware blocksize(%u)\n"), orig_zonesize, opt.blocksize);
|
|
goto out_freebh;
|
|
out_unknown_format:
|
|
if (!silent)
|
|
write_log (_T("ISOFS: Unable to identify CD-ROM format.\n"));
|
|
out_freebh:
|
|
brelse(bh);
|
|
brelse(pri_bh);
|
|
out_freesbi:
|
|
xfree(volume_name);
|
|
return error;
|
|
}
|
|
|
|
static int isofs_name_translate(struct iso_directory_record *de, char *newn, struct inode *inode)
|
|
{
|
|
char * old = de->name;
|
|
int len = de->name_len[0];
|
|
int i;
|
|
|
|
for (i = 0; i < len; i++) {
|
|
unsigned char c = old[i];
|
|
if (!c)
|
|
break;
|
|
|
|
if (!inode->i_sb->ei.s_cdtv) { /* keep case if Amiga/CDTV/CD32 */
|
|
/* convert from second character (same as CacheCDFS default) */
|
|
if (i > 0 && c >= 'A' && c <= 'Z')
|
|
c |= 0x20; /* lower case */
|
|
}
|
|
|
|
/* Drop trailing '.;1' (ISO 9660:1988 7.5.1 requires period) */
|
|
if (c == '.' && i == len - 3 && old[i + 1] == ';' && old[i + 2] == '1')
|
|
break;
|
|
|
|
/* Drop trailing ';1' */
|
|
if (c == ';' && i == len - 2 && old[i + 1] == '1')
|
|
break;
|
|
|
|
/* Convert remaining ';' to '.' */
|
|
/* Also '/' to '.' (broken Acorn-generated ISO9660 images) */
|
|
if (c == ';' || c == '/')
|
|
c = '.';
|
|
|
|
newn[i] = c;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
static int isofs_cmp(const char *name, char *compare, int dlen)
|
|
{
|
|
if (!compare)
|
|
return 1;
|
|
/* we don't care about special "." and ".." files */
|
|
if (dlen == 1) {
|
|
/* "." */
|
|
if (compare[0] == 0) {
|
|
return 1;
|
|
} else if (compare[0] == 1) {
|
|
return 1;
|
|
}
|
|
}
|
|
char tmp = compare[dlen];
|
|
compare[dlen] = 0;
|
|
int c = stricmp(name, compare);
|
|
compare[dlen] = tmp;
|
|
return c;
|
|
}
|
|
|
|
static struct inode *isofs_find_entry(struct inode *dir, char *tmpname, TCHAR *tmpname2, struct iso_directory_record *tmpde, const char *name, const TCHAR *nameu)
|
|
{
|
|
unsigned long bufsize = ISOFS_BUFFER_SIZE(dir);
|
|
unsigned char bufbits = ISOFS_BUFFER_BITS(dir);
|
|
unsigned long block, f_pos, offset, block_saved, offset_saved;
|
|
struct buffer_head *bh = NULL;
|
|
struct isofs_sb_info *sbi = ISOFS_SB(dir->i_sb);
|
|
int i;
|
|
TCHAR *jname;
|
|
|
|
if (!ISOFS_I(dir)->i_first_extent)
|
|
return 0;
|
|
|
|
f_pos = 0;
|
|
offset = 0;
|
|
block = 0;
|
|
|
|
while (f_pos < dir->i_size) {
|
|
struct iso_directory_record *de;
|
|
int de_len, match, dlen;
|
|
char *dpnt;
|
|
|
|
if (!bh) {
|
|
bh = isofs_bread(dir, block);
|
|
if (!bh)
|
|
return 0;
|
|
}
|
|
|
|
de = (struct iso_directory_record *) (bh->b_data + offset);
|
|
|
|
de_len = *(unsigned char *) de;
|
|
if (!de_len) {
|
|
brelse(bh);
|
|
bh = NULL;
|
|
f_pos = (f_pos + ISOFS_BLOCK_SIZE) & ~(ISOFS_BLOCK_SIZE - 1);
|
|
block = f_pos >> bufbits;
|
|
offset = 0;
|
|
continue;
|
|
}
|
|
|
|
block_saved = bh->b_blocknr;
|
|
offset_saved = offset;
|
|
offset += de_len;
|
|
f_pos += de_len;
|
|
|
|
/* Make sure we have a full directory entry */
|
|
if (offset >= bufsize) {
|
|
int slop = bufsize - offset + de_len;
|
|
memcpy((uae_u8*)tmpde, de, slop);
|
|
offset &= bufsize - 1;
|
|
block++;
|
|
brelse(bh);
|
|
bh = NULL;
|
|
if (offset) {
|
|
bh = isofs_bread(dir, block);
|
|
if (!bh)
|
|
return 0;
|
|
memcpy((uae_u8*)tmpde + slop, bh->b_data, offset);
|
|
}
|
|
de = tmpde;
|
|
}
|
|
|
|
dlen = de->name_len[0];
|
|
dpnt = de->name;
|
|
/* Basic sanity check, whether name doesn't exceed dir entry */
|
|
if (de_len < dlen + sizeof(struct iso_directory_record)) {
|
|
write_log (_T("iso9660: Corrupted directory entry in block %lu of inode %u\n"), block, dir->i_ino);
|
|
return 0;
|
|
}
|
|
|
|
jname = NULL;
|
|
if (sbi->s_rock && ((i = get_rock_ridge_filename(de, tmpname, dir)))) {
|
|
dlen = i; /* possibly -1 */
|
|
dpnt = tmpname;
|
|
} else if (sbi->s_joliet_level) {
|
|
jname = get_joliet_filename(de, dir);
|
|
} else if (sbi->s_mapping == 'n') {
|
|
dlen = isofs_name_translate(de, tmpname, dir);
|
|
dpnt = tmpname;
|
|
}
|
|
|
|
/*
|
|
* Skip hidden or associated files unless hide or showassoc,
|
|
* respectively, is set
|
|
*/
|
|
match = 0;
|
|
if (dlen > 0 && (!sbi->s_hide || (!(de->flags[0-sbi->s_high_sierra] & 1))) && (sbi->s_showassoc || (!(de->flags[0-sbi->s_high_sierra] & 4)))) {
|
|
if (jname)
|
|
match = _tcsicmp(jname, nameu) == 0;
|
|
else
|
|
match = isofs_cmp(name, dpnt, dlen) == 0;
|
|
}
|
|
xfree (jname);
|
|
if (match) {
|
|
isofs_normalize_block_and_offset(de, &block_saved, &offset_saved);
|
|
struct inode *dinode = isofs_iget(dir->i_sb, block_saved, offset_saved, nameu);
|
|
iput(dinode);
|
|
brelse(bh);
|
|
return dinode;
|
|
}
|
|
|
|
}
|
|
brelse(bh);
|
|
return 0;
|
|
}
|
|
|
|
/* Acorn extensions written by Matthew Wilcox <willy@bofh.ai> 1998 */
|
|
static int get_acorn_filename(struct iso_directory_record *de, char *retname, struct inode *inode)
|
|
{
|
|
int std;
|
|
unsigned char *chr;
|
|
int retnamlen = isofs_name_translate(de, retname, inode);
|
|
|
|
if (retnamlen == 0)
|
|
return 0;
|
|
std = sizeof(struct iso_directory_record) + de->name_len[0];
|
|
if (std & 1)
|
|
std++;
|
|
if ((*((unsigned char *) de) - std) != 32)
|
|
return retnamlen;
|
|
chr = ((unsigned char *) de) + std;
|
|
if (strncmp((char*)chr, "ARCHIMEDES", 10))
|
|
return retnamlen;
|
|
if ((*retname == '_') && ((chr[19] & 1) == 1))
|
|
*retname = '!';
|
|
if (((de->flags[0] & 2) == 0) && (chr[13] == 0xff) && ((chr[12] & 0xf0) == 0xf0)) {
|
|
retname[retnamlen] = ',';
|
|
sprintf(retname+retnamlen+1, "%3.3x",
|
|
((chr[12] & 0xf) << 8) | chr[11]);
|
|
retnamlen += 4;
|
|
}
|
|
return retnamlen;
|
|
}
|
|
|
|
struct file
|
|
{
|
|
uae_u32 f_pos;
|
|
};
|
|
|
|
static int do_isofs_readdir(struct inode *inode, struct file *filp, char *tmpname, struct iso_directory_record *tmpde, TCHAR *outname, uae_u64 *uniq)
|
|
{
|
|
unsigned long bufsize = ISOFS_BUFFER_SIZE(inode);
|
|
unsigned char bufbits = ISOFS_BUFFER_BITS(inode);
|
|
unsigned long block, offset, block_saved, offset_saved;
|
|
unsigned long inode_number = 0; /* Quiet GCC */
|
|
struct buffer_head *bh = NULL;
|
|
int len;
|
|
int map;
|
|
int first_de = 1;
|
|
char *p = NULL; /* Quiet GCC */
|
|
struct iso_directory_record *de;
|
|
struct isofs_sb_info *sbi = ISOFS_SB(inode->i_sb);
|
|
struct inode *dinode = NULL;
|
|
int bh_block = 0;
|
|
|
|
offset = filp->f_pos & (bufsize - 1);
|
|
block = filp->f_pos >> bufbits;
|
|
|
|
while (filp->f_pos < inode->i_size) {
|
|
int de_len;
|
|
|
|
if (!bh) {
|
|
bh = isofs_bread(inode, block);
|
|
if (!bh)
|
|
return 0;
|
|
bh_block = bh->b_blocknr;
|
|
}
|
|
|
|
de = (struct iso_directory_record *) (bh->b_data + offset);
|
|
|
|
de_len = *(unsigned char *) de;
|
|
|
|
/*
|
|
* If the length byte is zero, we should move on to the next
|
|
* CDROM sector. If we are at the end of the directory, we
|
|
* kick out of the while loop.
|
|
*/
|
|
|
|
if (de_len == 0) {
|
|
brelse(bh);
|
|
bh = NULL;
|
|
filp->f_pos = (filp->f_pos + ISOFS_BLOCK_SIZE) & ~(ISOFS_BLOCK_SIZE - 1);
|
|
block = filp->f_pos >> bufbits;
|
|
offset = 0;
|
|
continue;
|
|
}
|
|
|
|
block_saved = block;
|
|
offset_saved = offset;
|
|
offset += de_len;
|
|
|
|
/* Make sure we have a full directory entry */
|
|
if (offset >= bufsize) {
|
|
int slop = bufsize - offset + de_len;
|
|
memcpy(tmpde, de, slop);
|
|
offset &= bufsize - 1;
|
|
block++;
|
|
brelse(bh);
|
|
bh = NULL;
|
|
if (offset) {
|
|
bh = isofs_bread(inode, block);
|
|
if (!bh)
|
|
return 0;
|
|
memcpy((uae_u8*)tmpde + slop, bh->b_data, offset);
|
|
}
|
|
de = tmpde;
|
|
}
|
|
/* Basic sanity check, whether name doesn't exceed dir entry */
|
|
if (de_len < de->name_len[0] + sizeof(struct iso_directory_record)) {
|
|
write_log (_T("iso9660: Corrupted directory entry in block %lu of inode %u\n"), block, inode->i_ino);
|
|
return 0;
|
|
}
|
|
|
|
if (first_de) {
|
|
isofs_normalize_block_and_offset(de, &block_saved, &offset_saved);
|
|
inode_number = isofs_get_ino(block_saved, offset_saved, bufbits);
|
|
}
|
|
|
|
if (de->flags[0-sbi->s_high_sierra] & 0x80) {
|
|
first_de = 0;
|
|
filp->f_pos += de_len;
|
|
continue;
|
|
}
|
|
first_de = 1;
|
|
|
|
/* Handle the case of the '.' directory */
|
|
if (de->name_len[0] == 1 && de->name[0] == 0) {
|
|
filp->f_pos += de_len;
|
|
continue;
|
|
}
|
|
len = 0;
|
|
/* Handle the case of the '..' directory */
|
|
if (de->name_len[0] == 1 && de->name[0] == 1) {
|
|
filp->f_pos += de_len;
|
|
continue;
|
|
}
|
|
/* Handle everything else. Do name translation if there
|
|
is no Rock Ridge NM field. */
|
|
|
|
/*
|
|
* Do not report hidden files if so instructed, or associated
|
|
* files unless instructed to do so
|
|
*/
|
|
if ((sbi->s_hide && (de->flags[0-sbi->s_high_sierra] & 1)) || (!sbi->s_showassoc && (de->flags[0-sbi->s_high_sierra] & 4))) {
|
|
filp->f_pos += de_len;
|
|
continue;
|
|
}
|
|
|
|
map = 1;
|
|
#if 1
|
|
if (sbi->s_rock) {
|
|
len = get_rock_ridge_filename(de, tmpname, inode);
|
|
if (len != 0) { /* may be -1 */
|
|
p = tmpname;
|
|
map = 0;
|
|
}
|
|
}
|
|
#endif
|
|
TCHAR *jname = NULL;
|
|
if (map) {
|
|
if (sbi->s_joliet_level) {
|
|
jname = get_joliet_filename(de, inode);
|
|
len = 1;
|
|
} else
|
|
if (sbi->s_mapping == 'a') {
|
|
len = get_acorn_filename(de, tmpname, inode);
|
|
p = tmpname;
|
|
} else
|
|
if (sbi->s_mapping == 'n') {
|
|
len = isofs_name_translate(de, tmpname, inode);
|
|
p = tmpname;
|
|
} else {
|
|
p = de->name;
|
|
len = de->name_len[0];
|
|
}
|
|
}
|
|
|
|
filp->f_pos += de_len;
|
|
if (len > 0) {
|
|
if (jname == NULL) {
|
|
if (p == NULL) {
|
|
write_log(_T("ISOFS: no name copied (p == NULL)\n"));
|
|
outname[0] = _T('\0');
|
|
}
|
|
else {
|
|
char t = p[len];
|
|
p[len] = 0;
|
|
au_fs_copy (outname, MAX_DPATH, p);
|
|
p[len] = t;
|
|
}
|
|
} else {
|
|
uae_tcslcpy (outname, jname, MAX_DPATH);
|
|
xfree (jname);
|
|
}
|
|
dinode = isofs_iget(inode->i_sb, bh_block, offset_saved, outname);
|
|
iput(dinode);
|
|
*uniq = dinode->i_ino;
|
|
brelse(bh);
|
|
return 1;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
brelse(bh);
|
|
return 0;
|
|
}
|
|
|
|
|
|
void *isofs_mount(int unitnum, uae_u64 *uniq)
|
|
{
|
|
struct super_block *sb;
|
|
|
|
sb = xcalloc(struct super_block, 1);
|
|
sb->s_blocksize = 2048;
|
|
sb->s_blocksize_bits = 11;
|
|
sb->unitnum = unitnum;
|
|
if (sys_command_ismedia (unitnum, true)) {
|
|
if (isofs_fill_super(sb, NULL, 0, uniq)) {
|
|
sb->unknown_media = true;
|
|
}
|
|
}
|
|
return sb;
|
|
}
|
|
void isofs_unmount(void *sbp)
|
|
{
|
|
struct super_block *sb = (struct super_block*)sbp;
|
|
struct inode *inode;
|
|
struct buffer_head *bh;
|
|
|
|
if (!sb)
|
|
return;
|
|
write_log (_T("miss: %d hit: %d\n"), sb->hash_miss, sb->hash_hit);
|
|
inode = sb->inodes;
|
|
while (inode) {
|
|
struct inode *next = inode->next;
|
|
free_inode(inode);
|
|
inode = next;
|
|
}
|
|
bh = sb->buffer_heads;
|
|
while (bh) {
|
|
struct buffer_head *next = bh->next;
|
|
free_bh(bh);
|
|
bh = next;
|
|
}
|
|
xfree (sb);
|
|
}
|
|
|
|
bool isofs_mediainfo(void *sbp, struct isofs_info *ii)
|
|
{
|
|
struct super_block *sb = (struct super_block*)sbp;
|
|
|
|
memset (ii, 0, sizeof (struct isofs_info));
|
|
|
|
if (!sb)
|
|
return true;
|
|
struct isofs_sb_info *sbi = ISOFS_SB(sb);
|
|
ii->blocksize = 2048;
|
|
if (sys_command_ismedia (sb->unitnum, true)) {
|
|
struct device_info di;
|
|
uae_u32 totalblocks = 0;
|
|
ii->media = true;
|
|
di.cylinders = 0;
|
|
_stprintf (ii->devname, _T("CD%d"), sb->unitnum);
|
|
if (sys_command_info (sb->unitnum, &di, true)) {
|
|
totalblocks = di.cylinders * di.sectorspertrack * di.trackspercylinder;
|
|
uae_tcslcpy (ii->devname, di.label, sizeof (ii->devname));
|
|
}
|
|
ii->unknown_media = sb->unknown_media;
|
|
if (sb->root) {
|
|
if (sb->root->name[0] == '\0') {
|
|
uae_tcslcpy(ii->volumename, _T("NO_LABEL"), sizeof(ii->volumename));
|
|
} else {
|
|
uae_tcslcpy (ii->volumename, sb->root->name, sizeof(ii->volumename));
|
|
}
|
|
ii->blocks = sbi->s_max_size;
|
|
ii->totalblocks = totalblocks ? totalblocks : ii->blocks;
|
|
ii->creation = sb->root->i_ctime.tv_sec;
|
|
}
|
|
if (!ii->volumename[0] || !ii->blocks)
|
|
ii->unknown_media = true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
struct cd_opendir_s
|
|
{
|
|
struct super_block *sb;
|
|
struct inode *inode;
|
|
struct file f;
|
|
char tmp1[1024];
|
|
char tmp2[1024];
|
|
};
|
|
|
|
struct cd_opendir_s *isofs_opendir(void *sb, uae_u64 uniq)
|
|
{
|
|
struct cd_opendir_s *od = xcalloc(struct cd_opendir_s, 1);
|
|
od->sb = (struct super_block*)sb;
|
|
od->inode = find_inode(od->sb, uniq);
|
|
if (od->inode) {
|
|
lock_inode(od->inode);
|
|
od->f.f_pos = 0;
|
|
return od;
|
|
}
|
|
xfree(od);
|
|
return NULL;
|
|
}
|
|
void isofs_closedir(struct cd_opendir_s *od)
|
|
{
|
|
unlock_inode(od->inode);
|
|
xfree (od);
|
|
}
|
|
bool isofs_readdir(struct cd_opendir_s *od, TCHAR *name, uae_u64 *uniq)
|
|
{
|
|
return do_isofs_readdir(od->inode, &od->f, od->tmp1, (struct iso_directory_record*)od->tmp2, name, uniq) != 0;
|
|
}
|
|
|
|
void isofss_fill_file_attrs(void *sbp, uae_u64 parent, int *dir, int *flags, TCHAR **comment, uae_u64 uniq)
|
|
{
|
|
struct super_block *sb = (struct super_block*)sbp;
|
|
struct inode *inode = find_inode(sb, uniq);
|
|
if (!inode)
|
|
return;
|
|
|
|
*comment = NULL;
|
|
*dir = XS_ISDIR(inode->i_mode) ? 1 : 0;
|
|
if (inode->i_isaflags)
|
|
*flags = inode->i_aflags;
|
|
else
|
|
*flags = 0;
|
|
if (inode->i_comment)
|
|
*comment = my_strdup(inode->i_comment);
|
|
}
|
|
|
|
bool isofs_stat(void *sbp, uae_u64 uniq, struct mystat *statbuf)
|
|
{
|
|
struct super_block *sb = (struct super_block*)sbp;
|
|
struct inode *inode = find_inode(sb, uniq);
|
|
|
|
if (!inode)
|
|
return false;
|
|
|
|
statbuf->mtime.tv_sec = inode->i_mtime.tv_sec;
|
|
statbuf->mtime.tv_usec = 0;
|
|
if (!XS_ISDIR(inode->i_mode)) {
|
|
statbuf->size = inode->i_size;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool isofs_exists(void *sbp, uae_u64 parent, const TCHAR *name, uae_u64 *uniq)
|
|
{
|
|
char tmp1[1024];
|
|
TCHAR tmp1x[1024];
|
|
char tmp2[1024];
|
|
char tmp3[1024];
|
|
struct super_block *sb = (struct super_block*)sbp;
|
|
struct inode *inode = find_inode(sb, parent);
|
|
|
|
if (!inode)
|
|
return false;
|
|
ua_fs_copy(tmp3, sizeof tmp3, name, '_');
|
|
inode = isofs_find_entry(inode, tmp1, tmp1x, (struct iso_directory_record*)tmp2, tmp3, name);
|
|
if (inode) {
|
|
*uniq = inode->i_ino;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void isofs_dispose_inode(void *sbp, uae_u64 uniq)
|
|
{
|
|
struct super_block *sb = (struct super_block*)sbp;
|
|
struct inode *inode;
|
|
struct inode *old = NULL, *prev = NULL;
|
|
|
|
if (!sb)
|
|
return;
|
|
inode = sb->inodes;
|
|
while (inode) {
|
|
if (inode->i_ino == uniq) {
|
|
old = inode;
|
|
break;
|
|
}
|
|
prev = inode;
|
|
inode = inode->next;
|
|
}
|
|
if (!old)
|
|
return;
|
|
|
|
if (prev)
|
|
prev->next = old->next;
|
|
else
|
|
sb->inodes = old->next;
|
|
free_inode(old);
|
|
}
|
|
|
|
struct cd_openfile_s
|
|
{
|
|
struct super_block *sb;
|
|
struct inode *inode;
|
|
uae_s64 seek;
|
|
};
|
|
|
|
struct cd_openfile_s *isofs_openfile(void *sbp, uae_u64 uniq, int flags)
|
|
{
|
|
struct super_block *sb = (struct super_block*)sbp;
|
|
struct inode *inode = find_inode(sb, uniq);
|
|
if (!inode)
|
|
return NULL;
|
|
struct cd_openfile_s *of = xcalloc(struct cd_openfile_s, 1);
|
|
of->sb = sb;
|
|
of->inode = inode;
|
|
return of;
|
|
}
|
|
|
|
void isofs_closefile(struct cd_openfile_s *of)
|
|
{
|
|
xfree(of);
|
|
}
|
|
|
|
uae_s64 isofs_lseek(struct cd_openfile_s *of, uae_s64 offset, int mode)
|
|
{
|
|
struct inode *inode = of->inode;
|
|
uae_s64 ret = -1;
|
|
switch (mode)
|
|
{
|
|
case SEEK_SET:
|
|
of->seek = offset;
|
|
break;
|
|
case SEEK_CUR:
|
|
of->seek += offset;
|
|
break;
|
|
case SEEK_END:
|
|
of->seek = inode->i_size + offset;
|
|
break;
|
|
}
|
|
if (of->seek < 0) {
|
|
of->seek = 0;
|
|
ret = -1;
|
|
} else if (of->seek > inode->i_size) {
|
|
of->seek = inode->i_size;
|
|
ret = -1;
|
|
} else {
|
|
ret = of->seek;
|
|
}
|
|
return ret;
|
|
}
|
|
uae_s64 isofs_fsize(struct cd_openfile_s *of)
|
|
{
|
|
struct inode *inode = of->inode;
|
|
return inode->i_size;
|
|
}
|
|
|
|
uae_s64 isofs_read(struct cd_openfile_s *of, void *bp, unsigned int size)
|
|
{
|
|
struct inode *inode = of->inode;
|
|
uae_u32 bufsize = ISOFS_BUFFER_SIZE(inode);
|
|
uae_u32 bufmask = bufsize - 1;
|
|
uae_s64 offset = of->seek;
|
|
struct buffer_head *bh;
|
|
uae_u64 totalread = 0;
|
|
uae_u32 read;
|
|
uae_u8 *b = (uae_u8*)bp;
|
|
|
|
if (size + of->seek > inode->i_size)
|
|
size = (unsigned int)(inode->i_size - of->seek);
|
|
|
|
// first partial sector
|
|
if (offset & bufmask) {
|
|
bh = isofs_bread(inode, (uae_u32)(offset / bufsize));
|
|
if (!bh)
|
|
return 0;
|
|
read = size < (bufsize - (offset & bufmask)) ? size : (bufsize - (offset & bufmask));
|
|
memcpy (b, bh->b_data + (offset & bufmask), read);
|
|
offset += read;
|
|
size -= read;
|
|
totalread += read;
|
|
b += read;
|
|
of->seek += read;
|
|
brelse(bh);
|
|
}
|
|
// complete sector(s)
|
|
while (size >= bufsize) {
|
|
bh = isofs_bread(inode, (uae_u32)(offset / bufsize));
|
|
if (!bh)
|
|
return totalread;
|
|
read = size < bufsize ? size : bufsize;
|
|
memcpy (b, bh->b_data, read);
|
|
offset += read;
|
|
size -= read;
|
|
totalread += read;
|
|
b += read;
|
|
of->seek += read;
|
|
brelse(bh);
|
|
}
|
|
// and finally last partial sector
|
|
if (size > 0) {
|
|
bh = isofs_bread(inode, (uae_u32)(offset / bufsize));
|
|
if (!bh)
|
|
return totalread;
|
|
read = size;
|
|
memcpy (b, bh->b_data, size);
|
|
totalread += read;
|
|
of->seek += read;
|
|
brelse(bh);
|
|
}
|
|
|
|
return totalread;
|
|
}
|