58217f5900
WOO HOO!
268 lines
6.8 KiB
C
268 lines
6.8 KiB
C
/*
|
|
* linux/fs/isofs/namei.c
|
|
*
|
|
* (C) 1992 Eric Youngdale Modified for ISO9660 filesystem.
|
|
*
|
|
* (C) 1991 Linus Torvalds - minix filesystem
|
|
*/
|
|
|
|
#include <linux/sched.h>
|
|
#include <linux/iso_fs.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/string.h>
|
|
#include <linux/stat.h>
|
|
#include <linux/fcntl.h>
|
|
#include <asm/segment.h>
|
|
#include <linux/malloc.h>
|
|
|
|
#include <linux/errno.h>
|
|
|
|
/*
|
|
* 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
|
|
* some sanity tests.
|
|
*
|
|
* NOTE! unlike strncmp, isofs_match returns 1 for success, 0 for failure.
|
|
*/
|
|
static int isofs_match(int len,const char * name, char * compare, int dlen)
|
|
{
|
|
register int same __asm__("ax");
|
|
|
|
if (!compare) return 0;
|
|
/* "" means "." ---> so paths like "/usr/lib//libc.a" work */
|
|
if (!len && (compare[0]==0) && (dlen==1))
|
|
return 1;
|
|
|
|
if (compare[0]==0 && dlen==1 && len == 1)
|
|
compare = ".";
|
|
if (compare[0]==1 && dlen==1 && len == 2) {
|
|
compare = "..";
|
|
dlen = 2;
|
|
};
|
|
#if 0
|
|
if (len <= 2) printk("Match: %d %d %s %d %d \n",len,dlen,compare,de->name[0], dlen);
|
|
#endif
|
|
|
|
if (dlen != len)
|
|
return 0;
|
|
__asm__("cld\n\t"
|
|
"repe ; cmpsb\n\t"
|
|
"setz %%al"
|
|
:"=a" (same)
|
|
:"0" (0),"S" ((long) name),"D" ((long) compare),"c" (len)
|
|
:"cx","di","si");
|
|
return same;
|
|
}
|
|
|
|
/*
|
|
* isofs_find_entry()
|
|
*
|
|
* 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
|
|
* 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.
|
|
*/
|
|
static struct buffer_head * isofs_find_entry(struct inode * dir,
|
|
const char * name, int namelen, int * ino, int * ino_back)
|
|
{
|
|
unsigned long bufsize = ISOFS_BUFFER_SIZE(dir);
|
|
unsigned char bufbits = ISOFS_BUFFER_BITS(dir);
|
|
unsigned int block, i, f_pos, offset, inode_number;
|
|
struct buffer_head * bh;
|
|
void * cpnt = NULL;
|
|
unsigned int old_offset;
|
|
unsigned int backlink;
|
|
int dlen, rrflag, match;
|
|
char * dpnt;
|
|
struct iso_directory_record * de;
|
|
char c;
|
|
|
|
*ino = 0;
|
|
if (!dir) return NULL;
|
|
|
|
if (!(block = dir->u.isofs_i.i_first_extent)) return NULL;
|
|
|
|
f_pos = 0;
|
|
|
|
offset = f_pos & (bufsize - 1);
|
|
block = isofs_bmap(dir,f_pos >> bufbits);
|
|
|
|
if (!block || !(bh = bread(dir->i_dev,block,bufsize))) return NULL;
|
|
|
|
while (f_pos < dir->i_size) {
|
|
de = (struct iso_directory_record *) (bh->b_data + offset);
|
|
backlink = dir->i_ino;
|
|
inode_number = (block << bufbits) + (offset & (bufsize - 1));
|
|
|
|
/* If byte is zero, this is the end of file, or time to move to
|
|
the next sector. Usually 2048 byte boundaries. */
|
|
|
|
if (*((unsigned char *) de) == 0) {
|
|
brelse(bh);
|
|
offset = 0;
|
|
f_pos = ((f_pos & ~(ISOFS_BLOCK_SIZE - 1))
|
|
+ ISOFS_BLOCK_SIZE);
|
|
block = isofs_bmap(dir,f_pos>>bufbits);
|
|
if (!block || !(bh = bread(dir->i_dev,block,bufsize)))
|
|
return 0;
|
|
continue; /* Will kick out if past end of directory */
|
|
}
|
|
|
|
old_offset = offset;
|
|
offset += *((unsigned char *) de);
|
|
f_pos += *((unsigned char *) de);
|
|
|
|
/* Handle case where the directory entry spans two blocks.
|
|
Usually 1024 byte boundaries */
|
|
if (offset >= bufsize) {
|
|
cpnt = kmalloc(1 << ISOFS_BLOCK_BITS, GFP_KERNEL);
|
|
memcpy(cpnt, bh->b_data, bufsize);
|
|
de = (struct iso_directory_record *)
|
|
((char *)cpnt + old_offset);
|
|
brelse(bh);
|
|
offset = f_pos & (bufsize - 1);
|
|
block = isofs_bmap(dir,f_pos>>bufbits);
|
|
if (!block || !(bh = bread(dir->i_dev,block,bufsize))) {
|
|
kfree_s(cpnt, 1 << ISOFS_BLOCK_BITS);
|
|
return 0;
|
|
};
|
|
memcpy((char *)cpnt+bufsize,bh->b_data,bufsize);
|
|
}
|
|
|
|
/* Handle the '.' case */
|
|
|
|
if (de->name[0]==0 && de->name_len[0]==1) {
|
|
inode_number = dir->i_ino;
|
|
backlink = 0;
|
|
}
|
|
|
|
/* Handle the '..' case */
|
|
|
|
if (de->name[0]==1 && de->name_len[0]==1) {
|
|
#if 0
|
|
printk("Doing .. (%d %d)",
|
|
dir->i_sb->s_firstdatazone << bufbits,
|
|
dir->i_ino);
|
|
#endif
|
|
if((dir->i_sb->u.isofs_sb.s_firstdatazone
|
|
<< bufbits) != dir->i_ino)
|
|
inode_number = dir->u.isofs_i.i_backlink;
|
|
else
|
|
inode_number = dir->i_ino;
|
|
backlink = 0;
|
|
}
|
|
|
|
dlen = de->name_len[0];
|
|
dpnt = de->name;
|
|
/* Now convert the filename in the buffer to lower case */
|
|
rrflag = get_rock_ridge_filename(de, &dpnt, &dlen, dir);
|
|
if (rrflag) {
|
|
if (rrflag == -1) goto out; /* Relocated deep directory */
|
|
} else {
|
|
if(dir->i_sb->u.isofs_sb.s_mapping == 'n') {
|
|
for (i = 0; i < dlen; i++) {
|
|
c = dpnt[i];
|
|
if (c >= 'A' && c <= 'Z') c |= 0x20; /* lower case */
|
|
if (c == ';' && i == dlen-2 && dpnt[i+1] == '1') {
|
|
dlen -= 2;
|
|
break;
|
|
}
|
|
if (c == ';') c = '.';
|
|
de->name[i] = c;
|
|
}
|
|
/* This allows us to match with and without a trailing
|
|
period. */
|
|
if(dpnt[dlen-1] == '.' && namelen == dlen-1)
|
|
dlen--;
|
|
}
|
|
}
|
|
match = isofs_match(namelen,name,dpnt,dlen);
|
|
if (cpnt) {
|
|
kfree_s(cpnt, 1 << ISOFS_BLOCK_BITS);
|
|
cpnt = NULL;
|
|
}
|
|
|
|
if(rrflag) kfree(dpnt);
|
|
if (match) {
|
|
if(inode_number == -1) {
|
|
/* Should only happen for the '..' entry */
|
|
inode_number =
|
|
isofs_lookup_grandparent(dir,
|
|
find_rock_ridge_relocation(de,dir));
|
|
if(inode_number == -1){
|
|
/* Should never happen */
|
|
printk("Backlink not properly set.\n");
|
|
goto out;
|
|
}
|
|
}
|
|
*ino = inode_number;
|
|
*ino_back = backlink;
|
|
return bh;
|
|
}
|
|
}
|
|
out:
|
|
if (cpnt)
|
|
kfree_s(cpnt, 1 << ISOFS_BLOCK_BITS);
|
|
brelse(bh);
|
|
return NULL;
|
|
}
|
|
|
|
int isofs_lookup(struct inode * dir,const char * name, int len,
|
|
struct inode ** result)
|
|
{
|
|
int ino, ino_back;
|
|
struct buffer_head * bh;
|
|
|
|
#ifdef DEBUG
|
|
printk("lookup: %x %d\n",dir->i_ino, len);
|
|
#endif
|
|
*result = NULL;
|
|
if (!dir)
|
|
return -ENOENT;
|
|
|
|
if (!S_ISDIR(dir->i_mode)) {
|
|
iput(dir);
|
|
return -ENOENT;
|
|
}
|
|
|
|
ino = 0;
|
|
if (dir->i_dev == cache.dev &&
|
|
dir->i_ino == cache.dir &&
|
|
len == cache.dlen &&
|
|
isofs_match(len, name, cache.filename, cache.dlen))
|
|
{
|
|
ino = cache.ino;
|
|
ino_back = dir->i_ino;
|
|
/* These two cases are special, but since they are at the start
|
|
of the directory, we can just as easily search there */
|
|
if (cache.dlen == 1 && cache.filename[0] == '.') ino = 0;
|
|
if (cache.dlen == 2 && cache.filename[0] == '.' &&
|
|
cache.filename[1] == '.') ino = 0;
|
|
};
|
|
|
|
if (!ino) {
|
|
if (!(bh = isofs_find_entry(dir,name,len, &ino, &ino_back))) {
|
|
iput(dir);
|
|
return -ENOENT;
|
|
}
|
|
brelse(bh);
|
|
};
|
|
|
|
if (!(*result = iget(dir->i_sb,ino))) {
|
|
iput(dir);
|
|
return -EACCES;
|
|
}
|
|
|
|
/* We need this backlink for the ".." entry unless the name that we
|
|
are looking up traversed a mount point (in which case the inode
|
|
may not even be on an iso9660 filesystem, and writing to
|
|
u.isofs_i would only cause memory corruption).
|
|
*/
|
|
|
|
if (ino_back && !(*result)->i_pipe && (*result)->i_sb == dir->i_sb) {
|
|
(*result)->u.isofs_i.i_backlink = ino_back;
|
|
}
|
|
|
|
iput(dir);
|
|
return 0;
|
|
}
|