/*
|
/*
|
* linux/fs/isofs/namei.c
|
* linux/fs/isofs/namei.c
|
*
|
*
|
* (C) 1992 Eric Youngdale Modified for ISO9660 filesystem.
|
* (C) 1992 Eric Youngdale Modified for ISO9660 filesystem.
|
*
|
*
|
* (C) 1991 Linus Torvalds - minix filesystem
|
* (C) 1991 Linus Torvalds - minix filesystem
|
*/
|
*/
|
|
|
#include <linux/sched.h>
|
#include <linux/sched.h>
|
#include <linux/iso_fs.h>
|
#include <linux/iso_fs.h>
|
#include <linux/kernel.h>
|
#include <linux/kernel.h>
|
#include <linux/string.h>
|
#include <linux/string.h>
|
#include <linux/stat.h>
|
#include <linux/stat.h>
|
#include <linux/fcntl.h>
|
#include <linux/fcntl.h>
|
#include <asm/segment.h>
|
#include <asm/segment.h>
|
#include <linux/malloc.h>
|
#include <linux/malloc.h>
|
|
|
#include <linux/errno.h>
|
#include <linux/errno.h>
|
|
|
/*
|
/*
|
* ok, we cannot use strncmp, as the name is not in our data space.
|
* ok, we cannot use strncmp, as the name is not in our data space.
|
* Thus we'll have to use isofs_match. No big problem. Match also makes
|
* Thus we'll have to use isofs_match. No big problem. Match also makes
|
* some sanity tests.
|
* some sanity tests.
|
*
|
*
|
* NOTE! unlike strncmp, isofs_match returns 1 for success, 0 for failure.
|
* NOTE! unlike strncmp, isofs_match returns 1 for success, 0 for failure.
|
*/
|
*/
|
static int isofs_match(int len,const char * name, const char * compare, int dlen)
|
static int isofs_match(int len,const char * name, const char * compare, int dlen)
|
{
|
{
|
if (!compare)
|
if (!compare)
|
return 0;
|
return 0;
|
|
|
/* check special "." and ".." files */
|
/* check special "." and ".." files */
|
if (dlen == 1) {
|
if (dlen == 1) {
|
/* "." */
|
/* "." */
|
if (compare[0] == 0) {
|
if (compare[0] == 0) {
|
if (!len)
|
if (!len)
|
return 1;
|
return 1;
|
compare = ".";
|
compare = ".";
|
} else if (compare[0] == 1) {
|
} else if (compare[0] == 1) {
|
compare = "..";
|
compare = "..";
|
dlen = 2;
|
dlen = 2;
|
}
|
}
|
}
|
}
|
#if 0
|
#if 0
|
if (len <= 2) printk("Match: %d %d %s %d %d \n",len,dlen,compare,de->name[0], dlen);
|
if (len <= 2) printk("Match: %d %d %s %d %d \n",len,dlen,compare,de->name[0], dlen);
|
#endif
|
#endif
|
|
|
if (dlen != len)
|
if (dlen != len)
|
return 0;
|
return 0;
|
return !memcmp(name, compare, len);
|
return !memcmp(name, compare, len);
|
}
|
}
|
|
|
/*
|
/*
|
* isofs_find_entry()
|
* isofs_find_entry()
|
*
|
*
|
* finds an entry in the specified directory with the wanted name. It
|
* finds an entry in the specified directory with the wanted name. It
|
* returns the cache buffer in which the entry was found, and the entry
|
* returns the cache buffer in which the entry was found, and the entry
|
* itself (as an inode number). It does NOT read the inode of the
|
* itself (as an inode number). It does NOT read the inode of the
|
* entry - you'll have to do that yourself if you want to.
|
* entry - you'll have to do that yourself if you want to.
|
*/
|
*/
|
static struct buffer_head *
|
static struct buffer_head *
|
isofs_find_entry(struct inode * dir, const char * name, int namelen,
|
isofs_find_entry(struct inode * dir, const char * name, int namelen,
|
unsigned long * ino, unsigned long * ino_back)
|
unsigned long * ino, unsigned long * ino_back)
|
{
|
{
|
unsigned long bufsize = ISOFS_BUFFER_SIZE(dir);
|
unsigned long bufsize = ISOFS_BUFFER_SIZE(dir);
|
unsigned char bufbits = ISOFS_BUFFER_BITS(dir);
|
unsigned char bufbits = ISOFS_BUFFER_BITS(dir);
|
unsigned int block, i, f_pos, offset, inode_number;
|
unsigned int block, i, f_pos, offset, inode_number;
|
struct buffer_head * bh;
|
struct buffer_head * bh;
|
void * cpnt = NULL;
|
void * cpnt = NULL;
|
unsigned int old_offset;
|
unsigned int old_offset;
|
unsigned int backlink;
|
unsigned int backlink;
|
int dlen, match;
|
int dlen, match;
|
char * dpnt;
|
char * dpnt;
|
unsigned char *page = NULL;
|
unsigned char *page = NULL;
|
struct iso_directory_record * de;
|
struct iso_directory_record * de;
|
char c;
|
char c;
|
|
|
*ino = 0;
|
*ino = 0;
|
if (!dir) return NULL;
|
if (!dir) return NULL;
|
|
|
if (!(block = dir->u.isofs_i.i_first_extent)) return NULL;
|
if (!(block = dir->u.isofs_i.i_first_extent)) return NULL;
|
|
|
f_pos = 0;
|
f_pos = 0;
|
|
|
offset = f_pos & (bufsize - 1);
|
offset = f_pos & (bufsize - 1);
|
block = isofs_bmap(dir,f_pos >> bufbits);
|
block = isofs_bmap(dir,f_pos >> bufbits);
|
|
|
if (!block || !(bh = bread(dir->i_dev,block,bufsize))) return NULL;
|
if (!block || !(bh = bread(dir->i_dev,block,bufsize))) return NULL;
|
|
|
while (f_pos < dir->i_size) {
|
while (f_pos < dir->i_size) {
|
de = (struct iso_directory_record *) (bh->b_data + offset);
|
de = (struct iso_directory_record *) (bh->b_data + offset);
|
backlink = dir->i_ino;
|
backlink = dir->i_ino;
|
inode_number = (block << bufbits) + (offset & (bufsize - 1));
|
inode_number = (block << bufbits) + (offset & (bufsize - 1));
|
|
|
/* If byte is zero, this is the end of file, or time to move to
|
/* If byte is zero, this is the end of file, or time to move to
|
the next sector. Usually 2048 byte boundaries. */
|
the next sector. Usually 2048 byte boundaries. */
|
|
|
if (*((unsigned char *) de) == 0) {
|
if (*((unsigned char *) de) == 0) {
|
brelse(bh);
|
brelse(bh);
|
offset = 0;
|
offset = 0;
|
f_pos = ((f_pos & ~(ISOFS_BLOCK_SIZE - 1))
|
f_pos = ((f_pos & ~(ISOFS_BLOCK_SIZE - 1))
|
+ ISOFS_BLOCK_SIZE);
|
+ ISOFS_BLOCK_SIZE);
|
block = isofs_bmap(dir,f_pos>>bufbits);
|
block = isofs_bmap(dir,f_pos>>bufbits);
|
if (!block || !(bh = bread(dir->i_dev,block,bufsize)))
|
if (!block || !(bh = bread(dir->i_dev,block,bufsize)))
|
return NULL;
|
return NULL;
|
continue; /* Will kick out if past end of directory */
|
continue; /* Will kick out if past end of directory */
|
}
|
}
|
|
|
old_offset = offset;
|
old_offset = offset;
|
offset += *((unsigned char *) de);
|
offset += *((unsigned char *) de);
|
f_pos += *((unsigned char *) de);
|
f_pos += *((unsigned char *) de);
|
|
|
/* Handle case where the directory entry spans two blocks.
|
/* Handle case where the directory entry spans two blocks.
|
Usually 1024 byte boundaries */
|
Usually 1024 byte boundaries */
|
if (offset >= bufsize) {
|
if (offset >= bufsize) {
|
unsigned int frag1;
|
unsigned int frag1;
|
frag1 = bufsize - old_offset;
|
frag1 = bufsize - old_offset;
|
cpnt = kmalloc(*((unsigned char *) de),GFP_KERNEL);
|
cpnt = kmalloc(*((unsigned char *) de),GFP_KERNEL);
|
if (!cpnt) return NULL;
|
if (!cpnt) return NULL;
|
memcpy(cpnt, bh->b_data + old_offset, frag1);
|
memcpy(cpnt, bh->b_data + old_offset, frag1);
|
|
|
de = (struct iso_directory_record *) cpnt;
|
de = (struct iso_directory_record *) cpnt;
|
brelse(bh);
|
brelse(bh);
|
offset = f_pos & (bufsize - 1);
|
offset = f_pos & (bufsize - 1);
|
block = isofs_bmap(dir,f_pos>>bufbits);
|
block = isofs_bmap(dir,f_pos>>bufbits);
|
if (!block || !(bh = bread(dir->i_dev,block,bufsize))) {
|
if (!block || !(bh = bread(dir->i_dev,block,bufsize))) {
|
kfree(cpnt);
|
kfree(cpnt);
|
return NULL;
|
return NULL;
|
};
|
};
|
memcpy((char *)cpnt+frag1, bh->b_data, offset);
|
memcpy((char *)cpnt+frag1, bh->b_data, offset);
|
}
|
}
|
|
|
dlen = de->name_len[0];
|
dlen = de->name_len[0];
|
dpnt = de->name;
|
dpnt = de->name;
|
|
|
/* Handle the '.' case */
|
/* Handle the '.' case */
|
|
|
if (*dpnt==0 && dlen==1) {
|
if (*dpnt==0 && dlen==1) {
|
inode_number = dir->i_ino;
|
inode_number = dir->i_ino;
|
backlink = 0;
|
backlink = 0;
|
}
|
}
|
|
|
/* Handle the '..' case */
|
/* Handle the '..' case */
|
|
|
else if (*dpnt==1 && dlen==1) {
|
else if (*dpnt==1 && dlen==1) {
|
#if 0
|
#if 0
|
printk("Doing .. (%d %d)",
|
printk("Doing .. (%d %d)",
|
dir->i_sb->s_firstdatazone,
|
dir->i_sb->s_firstdatazone,
|
dir->i_ino);
|
dir->i_ino);
|
#endif
|
#endif
|
if((dir->i_sb->u.isofs_sb.s_firstdatazone) != dir->i_ino)
|
if((dir->i_sb->u.isofs_sb.s_firstdatazone) != dir->i_ino)
|
inode_number = dir->u.isofs_i.i_backlink;
|
inode_number = dir->u.isofs_i.i_backlink;
|
else
|
else
|
inode_number = dir->i_ino;
|
inode_number = dir->i_ino;
|
backlink = 0;
|
backlink = 0;
|
} else {
|
} else {
|
if (dir->i_sb->u.isofs_sb.s_rock ||
|
if (dir->i_sb->u.isofs_sb.s_rock ||
|
dir->i_sb->u.isofs_sb.s_joliet_level) {
|
dir->i_sb->u.isofs_sb.s_joliet_level) {
|
page = (unsigned char *)
|
page = (unsigned char *)
|
__get_free_page(GFP_KERNEL);
|
__get_free_page(GFP_KERNEL);
|
if (!page) return NULL;
|
if (!page) return NULL;
|
}
|
}
|
if (dir->i_sb->u.isofs_sb.s_rock &&
|
if (dir->i_sb->u.isofs_sb.s_rock &&
|
((i = get_rock_ridge_filename(de, page, dir)))){
|
((i = get_rock_ridge_filename(de, page, dir)))){
|
if (i == -1)
|
if (i == -1)
|
goto out;/* Relocated deep directory */
|
goto out;/* Relocated deep directory */
|
dlen = i;
|
dlen = i;
|
dpnt = page;
|
dpnt = page;
|
} else if (dir->i_sb->u.isofs_sb.s_joliet_level) {
|
} else if (dir->i_sb->u.isofs_sb.s_joliet_level) {
|
dlen = get_joliet_filename(de, dir, page);
|
dlen = get_joliet_filename(de, dir, page);
|
dpnt = page;
|
dpnt = page;
|
} else if (dir->i_sb->u.isofs_sb.s_mapping == 'n') {
|
} else if (dir->i_sb->u.isofs_sb.s_mapping == 'n') {
|
for (i = 0; i < dlen; i++) {
|
for (i = 0; i < dlen; i++) {
|
c = dpnt[i];
|
c = dpnt[i];
|
/* lower case */
|
/* lower case */
|
if (c >= 'A' && c <= 'Z') c |= 0x20;
|
if (c >= 'A' && c <= 'Z') c |= 0x20;
|
if (c == ';' && i == dlen-2 && dpnt[i+1] == '1') {
|
if (c == ';' && i == dlen-2 && dpnt[i+1] == '1') {
|
dlen -= 2;
|
dlen -= 2;
|
break;
|
break;
|
}
|
}
|
if (c == ';') c = '.';
|
if (c == ';') c = '.';
|
dpnt[i] = c;
|
dpnt[i] = c;
|
}
|
}
|
/* This allows us to match with and without
|
/* This allows us to match with and without
|
* a trailing period. */
|
* a trailing period. */
|
if(dpnt[dlen-1] == '.' && namelen == dlen-1)
|
if(dpnt[dlen-1] == '.' && namelen == dlen-1)
|
dlen--;
|
dlen--;
|
}
|
}
|
}
|
}
|
/*
|
/*
|
* Skip hidden or associated files unless unhide is set
|
* Skip hidden or associated files unless unhide is set
|
*/
|
*/
|
match = 0;
|
match = 0;
|
if( !(de->flags[-dir->i_sb->u.isofs_sb.s_high_sierra] & 5)
|
if( !(de->flags[-dir->i_sb->u.isofs_sb.s_high_sierra] & 5)
|
|| dir->i_sb->u.isofs_sb.s_unhide == 'y' )
|
|| dir->i_sb->u.isofs_sb.s_unhide == 'y' )
|
{
|
{
|
match = isofs_match(namelen,name,dpnt,dlen);
|
match = isofs_match(namelen,name,dpnt,dlen);
|
}
|
}
|
|
|
if (cpnt)
|
if (cpnt)
|
{
|
{
|
kfree(cpnt);
|
kfree(cpnt);
|
cpnt = NULL;
|
cpnt = NULL;
|
}
|
}
|
|
|
if (page) free_page((unsigned long) page);
|
if (page) free_page((unsigned long) page);
|
if (match) {
|
if (match) {
|
if(inode_number == -1) {
|
if(inode_number == -1) {
|
/* Should only happen for the '..' entry */
|
/* Should only happen for the '..' entry */
|
inode_number =
|
inode_number =
|
isofs_lookup_grandparent(dir,
|
isofs_lookup_grandparent(dir,
|
find_rock_ridge_relocation(de,dir));
|
find_rock_ridge_relocation(de,dir));
|
if(inode_number == -1){
|
if(inode_number == -1){
|
/* Should never happen */
|
/* Should never happen */
|
printk("Backlink not properly set %x %lx.\n",
|
printk("Backlink not properly set %x %lx.\n",
|
isonum_733(de->extent),
|
isonum_733(de->extent),
|
dir->i_ino);
|
dir->i_ino);
|
goto out;
|
goto out;
|
}
|
}
|
}
|
}
|
*ino = inode_number;
|
*ino = inode_number;
|
*ino_back = backlink;
|
*ino_back = backlink;
|
return bh;
|
return bh;
|
}
|
}
|
}
|
}
|
out:
|
out:
|
if (cpnt)
|
if (cpnt)
|
kfree(cpnt);
|
kfree(cpnt);
|
brelse(bh);
|
brelse(bh);
|
return NULL;
|
return NULL;
|
}
|
}
|
|
|
int isofs_lookup(struct inode * dir,const char * name, int len,
|
int isofs_lookup(struct inode * dir,const char * name, int len,
|
struct inode ** result)
|
struct inode ** result)
|
{
|
{
|
unsigned long ino, ino_back;
|
unsigned long ino, ino_back;
|
struct buffer_head * bh;
|
struct buffer_head * bh;
|
|
|
#ifdef DEBUG
|
#ifdef DEBUG
|
printk("lookup: %x %d\n",dir->i_ino, len);
|
printk("lookup: %x %d\n",dir->i_ino, len);
|
#endif
|
#endif
|
*result = NULL;
|
*result = NULL;
|
if (!dir)
|
if (!dir)
|
return -ENOENT;
|
return -ENOENT;
|
|
|
if (!S_ISDIR(dir->i_mode)) {
|
if (!S_ISDIR(dir->i_mode)) {
|
iput(dir);
|
iput(dir);
|
return -ENOENT;
|
return -ENOENT;
|
}
|
}
|
|
|
ino = 0;
|
ino = 0;
|
|
|
if (dcache_lookup(dir, name, len, &ino)) ino_back = dir->i_ino;
|
if (dcache_lookup(dir, name, len, &ino)) ino_back = dir->i_ino;
|
|
|
|
|
if (!ino) {
|
if (!ino) {
|
char *lcname;
|
char *lcname;
|
|
|
/* First try the original name. If that doesn't work and the fs
|
/* First try the original name. If that doesn't work and the fs
|
* was mounted with check=relaxed, convert the name to lower
|
* was mounted with check=relaxed, convert the name to lower
|
* case and try again.
|
* case and try again.
|
*/
|
*/
|
if (!(bh = isofs_find_entry(dir,name,len, &ino, &ino_back))
|
if (!(bh = isofs_find_entry(dir,name,len, &ino, &ino_back))
|
&& dir->i_sb->u.isofs_sb.s_name_check == 'r'
|
&& dir->i_sb->u.isofs_sb.s_name_check == 'r'
|
&& (lcname = kmalloc(len, GFP_KERNEL)) != NULL) {
|
&& (lcname = kmalloc(len, GFP_KERNEL)) != NULL) {
|
int i;
|
int i;
|
char c;
|
char c;
|
|
|
for (i=0; i<len; i++) {
|
for (i=0; i<len; i++) {
|
c = name[i];
|
c = name[i];
|
if (c >= 'A' && c <= 'Z') c |= 0x20;
|
if (c >= 'A' && c <= 'Z') c |= 0x20;
|
lcname[i] = c;
|
lcname[i] = c;
|
}
|
}
|
bh = isofs_find_entry(dir,lcname,len, &ino, &ino_back);
|
bh = isofs_find_entry(dir,lcname,len, &ino, &ino_back);
|
kfree(lcname);
|
kfree(lcname);
|
}
|
}
|
|
|
if (!bh) {
|
if (!bh) {
|
iput(dir);
|
iput(dir);
|
return -ENOENT;
|
return -ENOENT;
|
}
|
}
|
if (ino_back == dir->i_ino) {
|
if (ino_back == dir->i_ino) {
|
dcache_add(dir, name, len, ino);
|
dcache_add(dir, name, len, ino);
|
}
|
}
|
brelse(bh);
|
brelse(bh);
|
}
|
}
|
|
|
if (!(*result = iget(dir->i_sb,ino))) {
|
if (!(*result = iget(dir->i_sb,ino))) {
|
iput(dir);
|
iput(dir);
|
return -EACCES;
|
return -EACCES;
|
}
|
}
|
|
|
/* We need this backlink for the ".." entry unless the name that we
|
/* We need this backlink for the ".." entry unless the name that we
|
are looking up traversed a mount point (in which case the inode
|
are looking up traversed a mount point (in which case the inode
|
may not even be on an iso9660 filesystem, and writing to
|
may not even be on an iso9660 filesystem, and writing to
|
u.isofs_i would only cause memory corruption).
|
u.isofs_i would only cause memory corruption).
|
*/
|
*/
|
|
|
if (ino_back && !(*result)->i_pipe && (*result)->i_sb == dir->i_sb) {
|
if (ino_back && !(*result)->i_pipe && (*result)->i_sb == dir->i_sb) {
|
(*result)->u.isofs_i.i_backlink = ino_back;
|
(*result)->u.isofs_i.i_backlink = ino_back;
|
}
|
}
|
|
|
iput(dir);
|
iput(dir);
|
return 0;
|
return 0;
|
}
|
}
|
|
|