Commit 9210c204 authored by Paul Serice's avatar Paul Serice Committed by Linus Torvalds

[PATCH] iso9660: fix handling of inodes beyond 4GB

This is my fourth attempt to patch the isofs code.  It is similar to the last
posting except this one implements the NFS get_parent() method which has
always been missing.

The original problem I set out to addresses is that the current iso9660 file
system cannot reach inodes located beyond the 4GB barrier.  This is caused by
using the inode number as the byte offset of the inode data.  Being 32-bits
wide, the inode number is unable to reach inode data that does not reside on
the first 4GB of the file system.

This causes real problems with "growisofs"

      http://fy.chalmers.se/~appro/linux/DVD+RW/#isofs4gb

and my pet project "shunt"

      http://www.serice.net/shunt/

This patch switches the isofs code from iget() to iget5_locked() which allows
extra data to be passed into isofs_read_inode() so that inode data anywhere on
the disk can be reached.

The inode number scheme was also changed.  Continuing to use the byte offset
would have resulted in non-unique inodes in many common situations, but
because the inode number no longer plays any role in reading the meta-data off
the disk, I was free to set the inode number to some unique characteristic of
the file.  I have chosen to use the block offset which is also 32-bits wide.

Lastly, the pre-patch code uses the default export_operations to handle
accessing the file system through NFS.  The problem with this is that the
default NFS operations assume that iget() works which is no longer the case
because of the necessity of switching to iget5_locked().  So, I had to
implement the NFS operations too.  As a bonus, I went ahead and implemented
the NFS get_parent() method which has always been missing.
Signed-off-by: default avatarAndrew Morton <akpm@osdl.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@osdl.org>
parent f38928f4
......@@ -4,7 +4,7 @@
obj-$(CONFIG_ISO9660_FS) += isofs.o
isofs-objs-y := namei.o inode.o dir.o util.o rock.o
isofs-objs-y := namei.o inode.o dir.o util.o rock.o export.o
isofs-objs-$(CONFIG_JOLIET) += joliet.o
isofs-objs-$(CONFIG_ZISOFS) += compress.o
isofs-objs := $(isofs-objs-y)
......@@ -106,8 +106,8 @@ static int do_isofs_readdir(struct inode *inode, struct file *filp,
{
unsigned long bufsize = ISOFS_BUFFER_SIZE(inode);
unsigned char bufbits = ISOFS_BUFFER_BITS(inode);
unsigned int block, offset;
int inode_number = 0; /* Quiet GCC */
unsigned long block, offset;
unsigned long inode_number = 0; /* Quiet GCC */
struct buffer_head *bh = NULL;
int len;
int map;
......@@ -130,7 +130,7 @@ static int do_isofs_readdir(struct inode *inode, struct file *filp,
de = (struct iso_directory_record *) (bh->b_data + offset);
if (first_de)
inode_number = (bh->b_blocknr << bufbits) + offset;
inode_number = isofs_get_ino(de);
de_len = *(unsigned char *) de;
......
/*
* fs/isofs/export.c
*
* (C) 2004 Paul Serice - The new inode scheme requires switching
* from iget() to iget5_locked() which means
* the NFS export operations have to be hand
* coded because the default routines rely on
* iget().
*
* The following files are helpful:
*
* Documentation/filesystems/Exporting
* fs/exportfs/expfs.c.
*/
#include <linux/buffer_head.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/iso_fs.h>
#include <linux/kernel.h>
static struct dentry *
isofs_export_iget(struct super_block *sb,
unsigned long block,
unsigned long offset,
__u32 generation)
{
struct inode *inode;
struct dentry *result;
if (block == 0)
return ERR_PTR(-ESTALE);
inode = isofs_iget(sb, block, offset);
if (inode == NULL)
return ERR_PTR(-ENOMEM);
if (is_bad_inode(inode)
|| (generation && inode->i_generation != generation))
{
iput(inode);
return ERR_PTR(-ESTALE);
}
result = d_alloc_anon(inode);
if (!result) {
iput(inode);
return ERR_PTR(-ENOMEM);
}
return result;
}
static struct dentry *
isofs_export_get_dentry(struct super_block *sb, void *vobjp)
{
__u32 *objp = vobjp;
unsigned long block = objp[0];
unsigned long offset = objp[1];
__u32 generation = objp[2];
return isofs_export_iget(sb, block, offset, generation);
}
/* This function is surprisingly simple. The trick is understanding
* that "child" is always a directory. So, to find its parent, you
* simply need to find its ".." entry, normalize its block and offset,
* and return the underlying inode. See the comments for
* isofs_normalize_block_and_offset(). */
static struct dentry *isofs_export_get_parent(struct dentry *child)
{
unsigned long parent_block = 0;
unsigned long parent_offset = 0;
struct inode *child_inode = child->d_inode;
struct iso_inode_info *e_child_inode = ISOFS_I(child_inode);
struct inode *parent_inode = NULL;
struct iso_directory_record *de = NULL;
struct buffer_head * bh = NULL;
struct dentry *rv = NULL;
/* "child" must always be a directory. */
if (!S_ISDIR(child_inode->i_mode)) {
printk(KERN_ERR "isofs: isofs_export_get_parent(): "
"child is not a directory!\n");
rv = ERR_PTR(-EACCES);
goto out;
}
/* It is an invariant that the directory offset is zero. If
* it is not zero, it means the directory failed to be
* normalized for some reason. */
if (e_child_inode->i_iget5_offset != 0) {
printk(KERN_ERR "isofs: isofs_export_get_parent(): "
"child directory not normalized!\n");
rv = ERR_PTR(-EACCES);
goto out;
}
/* The child inode has been normalized such that its
* i_iget5_block value points to the "." entry. Fortunately,
* the ".." entry is located in the same block. */
parent_block = e_child_inode->i_iget5_block;
/* Get the block in question. */
bh = sb_bread(child_inode->i_sb, parent_block);
if (bh == NULL) {
rv = ERR_PTR(-EACCES);
goto out;
}
/* This is the "." entry. */
de = (struct iso_directory_record*)bh->b_data;
/* The ".." entry is always the second entry. */
parent_offset = (unsigned long)isonum_711(de->length);
de = (struct iso_directory_record*)(bh->b_data + parent_offset);
/* Verify it is in fact the ".." entry. */
if ((isonum_711(de->name_len) != 1) || (de->name[0] != 1)) {
printk(KERN_ERR "isofs: Unable to find the \"..\" "
"directory for NFS.\n");
rv = ERR_PTR(-EACCES);
goto out;
}
/* Normalize */
isofs_normalize_block_and_offset(de, &parent_block, &parent_offset);
/* Get the inode. */
parent_inode = isofs_iget(child_inode->i_sb,
parent_block,
parent_offset);
if (parent_inode == NULL) {
rv = ERR_PTR(-EACCES);
goto out;
}
/* Allocate the dentry. */
rv = d_alloc_anon(parent_inode);
if (rv == NULL) {
rv = ERR_PTR(-ENOMEM);
goto out;
}
out:
if (bh) {
brelse(bh);
}
return rv;
}
static int
isofs_export_encode_fh(struct dentry *dentry,
__u32 *fh32,
int *max_len,
int connectable)
{
struct inode * inode = dentry->d_inode;
struct iso_inode_info * ei = ISOFS_I(inode);
int len = *max_len;
int type = 1;
__u16 *fh16 = (__u16*)fh32;
/*
* WARNING: max_len is 5 for NFSv2. Because of this
* limitation, we use the lower 16 bits of fh32[1] to hold the
* offset of the inode and the upper 16 bits of fh32[1] to
* hold the offset of the parent.
*/
if (len < 3 || (connectable && len < 5))
return 255;
len = 3;
fh32[0] = ei->i_iget5_block;
fh16[2] = (__u16)ei->i_iget5_offset; /* fh16 [sic] */
fh32[2] = inode->i_generation;
if (connectable && !S_ISDIR(inode->i_mode)) {
struct inode *parent;
struct iso_inode_info *eparent;
spin_lock(&dentry->d_lock);
parent = dentry->d_parent->d_inode;
eparent = ISOFS_I(parent);
fh32[3] = eparent->i_iget5_block;
fh16[3] = (__u16)eparent->i_iget5_offset; /* fh16 [sic] */
fh32[4] = parent->i_generation;
spin_unlock(&dentry->d_lock);
len = 5;
type = 2;
}
*max_len = len;
return type;
}
static struct dentry *
isofs_export_decode_fh(struct super_block *sb,
__u32 *fh32,
int fh_len,
int fileid_type,
int (*acceptable)(void *context, struct dentry *de),
void *context)
{
__u16 *fh16 = (__u16*)fh32;
__u32 child[3]; /* The child is what triggered all this. */
__u32 parent[3]; /* The parent is just along for the ride. */
if (fh_len < 3 || fileid_type > 2)
return NULL;
child[0] = fh32[0];
child[1] = fh16[2]; /* fh16 [sic] */
child[2] = fh32[2];
parent[0] = 0;
parent[1] = 0;
parent[2] = 0;
if (fileid_type == 2) {
if (fh_len > 2) parent[0] = fh32[3];
parent[1] = fh16[3]; /* fh16 [sic] */
if (fh_len > 4) parent[2] = fh32[4];
}
return sb->s_export_op->find_exported_dentry(sb, child, parent,
acceptable, context);
}
struct export_operations isofs_export_ops = {
.decode_fh = isofs_export_decode_fh,
.encode_fh = isofs_export_encode_fh,
.get_dentry = isofs_export_get_dentry,
.get_parent = isofs_export_get_parent,
};
This diff is collapsed.
......@@ -17,6 +17,7 @@
#include <linux/config.h> /* Joliet? */
#include <linux/smp_lock.h>
#include <linux/buffer_head.h>
#include <linux/dcache.h>
#include <asm/uaccess.h>
......@@ -59,12 +60,12 @@ isofs_cmp(struct dentry * dentry, const char * compare, int dlen)
*/
static unsigned long
isofs_find_entry(struct inode *dir, struct dentry *dentry,
unsigned long *block_rv, unsigned long* offset_rv,
char * tmpname, struct iso_directory_record * tmpde)
{
unsigned long inode_number;
unsigned long bufsize = ISOFS_BUFFER_SIZE(dir);
unsigned char bufbits = ISOFS_BUFFER_BITS(dir);
unsigned int block, f_pos, offset;
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);
......@@ -87,7 +88,6 @@ isofs_find_entry(struct inode *dir, struct dentry *dentry,
}
de = (struct iso_directory_record *) (bh->b_data + offset);
inode_number = (bh->b_blocknr << bufbits) + offset;
de_len = *(unsigned char *) de;
if (!de_len) {
......@@ -99,6 +99,8 @@ isofs_find_entry(struct inode *dir, struct dentry *dentry,
continue;
}
block_saved = bh->b_blocknr;
offset_saved = offset;
offset += de_len;
f_pos += de_len;
......@@ -150,8 +152,13 @@ isofs_find_entry(struct inode *dir, struct dentry *dentry,
match = (isofs_cmp(dentry,dpnt,dlen) == 0);
}
if (match) {
isofs_normalize_block_and_offset(de,
&block_saved,
&offset_saved);
*block_rv = block_saved;
*offset_rv = offset_saved;
if (bh) brelse(bh);
return inode_number;
return 1;
}
}
if (bh) brelse(bh);
......@@ -160,7 +167,8 @@ isofs_find_entry(struct inode *dir, struct dentry *dentry,
struct dentry *isofs_lookup(struct inode * dir, struct dentry * dentry, struct nameidata *nd)
{
unsigned long ino;
int found;
unsigned long block, offset;
struct inode *inode;
struct page *page;
......@@ -171,19 +179,23 @@ struct dentry *isofs_lookup(struct inode * dir, struct dentry * dentry, struct n
return ERR_PTR(-ENOMEM);
lock_kernel();
ino = isofs_find_entry(dir, dentry, page_address(page),
1024 + page_address(page));
found = isofs_find_entry(dir, dentry,
&block, &offset,
page_address(page),
1024 + page_address(page));
__free_page(page);
inode = NULL;
if (ino) {
inode = iget(dir->i_sb, ino);
if (found) {
inode = isofs_iget(dir->i_sb, block, offset);
if (!inode) {
unlock_kernel();
return ERR_PTR(-EACCES);
}
}
unlock_kernel();
if (inode)
return d_splice_alias(inode, dentry);
d_add(dentry, inode);
return NULL;
}
......@@ -306,9 +306,7 @@ int parse_rock_ridge_inode_internal(struct iso_directory_record * de,
goto out;
case SIG('C','L'):
ISOFS_I(inode)->i_first_extent = isonum_733(rr->u.CL.location);
reloc = iget(inode->i_sb,
(ISOFS_I(inode)->i_first_extent <<
ISOFS_SB(inode->i_sb)->s_log_zone_size));
reloc = isofs_iget(inode->i_sb, ISOFS_I(inode)->i_first_extent, 0);
if (!reloc)
goto out;
inode->i_mode = reloc->i_mode;
......@@ -447,15 +445,15 @@ int parse_rock_ridge_inode(struct iso_directory_record * de,
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);
char *link = kmap(page);
unsigned long bufsize = ISOFS_BUFFER_SIZE(inode);
unsigned char bufbits = ISOFS_BUFFER_BITS(inode);
struct buffer_head *bh;
char *rpnt = link;
unsigned char *pnt;
struct iso_directory_record *raw_inode;
CONTINUE_DECLS;
int block;
unsigned long block, offset;
int sig;
int len;
unsigned char *chr;
......@@ -464,20 +462,21 @@ static int rock_ridge_symlink_readpage(struct file *file, struct page *page)
if (!ISOFS_SB(inode->i_sb)->s_rock)
panic ("Cannot have symlink with high sierra variant of iso filesystem\n");
block = inode->i_ino >> bufbits;
block = ei->i_iget5_block;
lock_kernel();
bh = sb_bread(inode->i_sb, block);
if (!bh)
goto out_noread;
pnt = (unsigned char *) bh->b_data + (inode->i_ino & (bufsize - 1));
offset = ei->i_iget5_offset;
pnt = (unsigned char *) bh->b_data + offset;
raw_inode = (struct iso_directory_record *) pnt;
/*
* If we go past the end of the buffer, there is some sort of error.
*/
if ((inode->i_ino & (bufsize - 1)) + *pnt > bufsize)
if (offset + *pnt > bufsize)
goto out_bad_span;
/* Now test for possible Rock Ridge extensions which will override
......
......@@ -231,9 +231,75 @@ extern struct dentry *isofs_lookup(struct inode *, struct dentry *, struct namei
extern struct buffer_head *isofs_bread(struct inode *, sector_t);
extern int isofs_get_blocks(struct inode *, sector_t, struct buffer_head **, unsigned long);
extern struct inode *isofs_iget(struct super_block *sb,
unsigned long block,
unsigned long offset);
/* Because the inode number is no longer relevant to finding the
* underlying meta-data for an inode, we are free to choose a more
* convenient 32-bit number as the inode number. Because directories
* and files are block aligned (except in a few very unusual cases)
* and because blocks are limited to 32-bits, I've chosen the starting
* block that holds the file or directory data as the inode number.
*
* One nice side effect of this is that you can use "ls -i" to get the
* inode number which will tell you exactly where you need to start a
* hex dump if you want to see the contents of the directory or
* file. */
static inline unsigned long isofs_get_ino(struct iso_directory_record *d)
{
return (unsigned long)isonum_733(d->extent)
+ (unsigned long)isonum_711(d->ext_attr_length);
}
/* Every directory can have many redundant directory entries scattered
* throughout the directory tree. First there is the directory entry
* with the name of the directory stored in the parent directory.
* Then, there is the "." directory entry stored in the directory
* itself. Finally, there are possibly many ".." directory entries
* stored in all the subdirectories.
*
* In order for the NFS get_parent() method to work and for the
* general consistency of the dcache, we need to make sure the
* "i_iget5_block" and "i_iget5_offset" all point to exactly one of
* the many redundant entries for each directory. We normalize the
* block and offset by always making them point to the "." directory.
*
* Notice that we do not use the entry for the directory with the name
* that is located in the parent directory. Even though choosing this
* first directory is more natural, it is much easier to find the "."
* entry in the NFS get_parent() method because it is implicitly
* encoded in the "extent + ext_attr_length" fields of _all_ the
* redundant entries for the directory. Thus, it can always be
* reached regardless of which directory entry you have in hand.
*
* This works because the "." entry is simply the first directory
* record when you start reading the file that holds all the directory
* records, and this file starts at "extent + ext_attr_length" blocks.
* Because the "." entry is always the first entry listed in the
* directories file, the normalized "offset" value is always 0.
*
* You should pass the directory entry in "de". On return, "block"
* and "offset" will hold normalized values. Only directories are
* affected making it safe to call even for non-directory file
* types. */
static void inline
isofs_normalize_block_and_offset(struct iso_directory_record* de,
unsigned long *block,
unsigned long *offset)
{
/* 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);
}
}
extern struct inode_operations isofs_dir_inode_operations;
extern struct file_operations isofs_dir_operations;
extern struct address_space_operations isofs_symlink_aops;
extern struct export_operations isofs_export_ops;
/* The following macros are used to check for memory leaks. */
#ifdef LEAK_CHECK
......
......@@ -13,10 +13,13 @@ enum isofs_file_format {
* iso fs inode data in memory
*/
struct iso_inode_info {
unsigned long i_iget5_block;
unsigned long i_iget5_offset;
unsigned int i_first_extent;
unsigned char i_file_format;
unsigned char i_format_parm[3];
unsigned long i_next_section_ino;
unsigned long i_next_section_block;
unsigned long i_next_section_offset;
off_t i_section_size;
struct inode vfs_inode;
};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment