diff options
Diffstat (limited to 'fs/isofs/dir.c')
-rw-r--r-- | fs/isofs/dir.c | 258 |
1 files changed, 258 insertions, 0 deletions
diff --git a/fs/isofs/dir.c b/fs/isofs/dir.c new file mode 100644 index 000000000..b1934db04 --- /dev/null +++ b/fs/isofs/dir.c @@ -0,0 +1,258 @@ +/* + * linux/fs/isofs/dir.c + * + * (C) 1992, 1993, 1994 Eric Youngdale Modified for ISO9660 filesystem. + * + * (C) 1991 Linus Torvalds - minix filesystem + * + * isofs directory handling functions + */ + +#include <linux/errno.h> + +#include <asm/segment.h> + +#include <linux/fs.h> +#include <linux/iso_fs.h> +#include <linux/kernel.h> +#include <linux/stat.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/malloc.h> +#include <linux/sched.h> +#include <linux/locks.h> + +#define NAME_OFFSET(de) ((int) ((de)->d_name - (char *) (de))) +#define ROUND_UP(x) (((x)+3) & ~3) + +static int isofs_readdir(struct inode *, struct file *, struct dirent *, int); + +static struct file_operations isofs_dir_operations = { + NULL, /* lseek - default */ + NULL, /* read */ + NULL, /* write - bad */ + isofs_readdir, /* readdir */ + NULL, /* select - default */ + NULL, /* ioctl - default */ + NULL, /* no special open code */ + NULL, /* no special release code */ + NULL /* fsync */ +}; + +/* + * directories can handle most operations... + */ +struct inode_operations isofs_dir_inode_operations = { + &isofs_dir_operations, /* default directory file-ops */ + NULL, /* create */ + isofs_lookup, /* lookup */ + NULL, /* link */ + NULL, /* unlink */ + NULL, /* symlink */ + NULL, /* mkdir */ + NULL, /* rmdir */ + NULL, /* mknod */ + NULL, /* rename */ + NULL, /* readlink */ + NULL, /* follow_link */ + isofs_bmap, /* bmap */ + NULL, /* truncate */ + NULL /* permission */ +}; + +static int isofs_readdir(struct inode * inode, struct file * filp, + struct dirent * dirent, int count) +{ + unsigned long bufsize = ISOFS_BUFFER_SIZE(inode); + unsigned char bufbits = ISOFS_BUFFER_BITS(inode); + unsigned int block,offset,i, j; + char c = 0; + int inode_number; + struct buffer_head * bh; + void * cpnt = NULL; + unsigned int old_offset; + int dlen, rrflag; + int high_sierra = 0; + char * dpnt, *dpnt1; + struct iso_directory_record * de; + + dpnt1 = NULL; + if (!inode || !S_ISDIR(inode->i_mode)) + return -EBADF; + + offset = filp->f_pos & (bufsize - 1); + block = isofs_bmap(inode,filp->f_pos>>bufbits); + + if(!block) return 0; + + if(!(bh = breada(inode->i_dev, block, bufsize, filp->f_pos, inode->i_size))) + return 0; + + while (filp->f_pos < inode->i_size) { +#ifdef DEBUG + printk("Block, offset, f_pos: %x %x %x\n", + block, offset, filp->f_pos); +#endif + de = (struct iso_directory_record *) (bh->b_data + offset); + inode_number = (block << bufbits) + (offset & (bufsize - 1)); + + /* 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 (*((unsigned char *) de) == 0) { + brelse(bh); + offset = 0; + filp->f_pos = ((filp->f_pos & ~(ISOFS_BLOCK_SIZE - 1)) + + ISOFS_BLOCK_SIZE); + block = isofs_bmap(inode,(filp->f_pos)>>bufbits); + if (!block + || !(bh = breada(inode->i_dev, block, bufsize, filp->f_pos, + inode->i_size))) + return 0; + continue; + } + + /* Make sure that the entire directory record is in the + current bh block. + If not, we malloc a buffer, and put the two halves together, + so that we can cleanly read the block */ + + old_offset = offset; + offset += *((unsigned char *) de); + filp->f_pos += *((unsigned char *) de); + + if (offset > bufsize) { + unsigned int frag1; + frag1 = bufsize - old_offset; + cpnt = kmalloc(*((unsigned char *) de),GFP_KERNEL); + if (!cpnt) return 0; + memcpy(cpnt, bh->b_data + old_offset, frag1); + de = (struct iso_directory_record *) ((char *)cpnt); + brelse(bh); + offset = filp->f_pos & (bufsize - 1); + block = isofs_bmap(inode,(filp->f_pos)>> bufbits); + if (!block + || !(bh = breada(inode->i_dev, block, bufsize, + filp->f_pos, inode->i_size))) { + kfree(cpnt); + return 0; + }; + memcpy((char *)cpnt+frag1, bh->b_data, offset); + } + + /* Handle the case of the '.' directory */ + + rrflag = 0; + i = 1; + if (de->name_len[0] == 1 && de->name[0] == 0) { + put_fs_byte('.',dirent->d_name); + inode_number = inode->i_ino; + dpnt = "."; + } + + /* Handle the case of the '..' directory */ + + else if (de->name_len[0] == 1 && de->name[0] == 1) { + put_fs_byte('.',dirent->d_name); + put_fs_byte('.',dirent->d_name+1); + i = 2; + dpnt = ".."; + if((inode->i_sb->u.isofs_sb.s_firstdatazone) != inode->i_ino) + inode_number = inode->u.isofs_i.i_backlink; + else + inode_number = inode->i_ino; + + /* This should never happen, but who knows. Try to be forgiving */ + if(inode_number == -1) { + inode_number = + isofs_lookup_grandparent(inode, + find_rock_ridge_relocation(de, inode)); + if(inode_number == -1){ /* Should never happen */ + printk("Backlink not properly set.\n"); + goto out; + }; + } + } + + /* Handle everything else. Do name translation if there + is no Rock Ridge NM field. */ + + else { + /* Do not report hidden or associated files */ + high_sierra = inode->i_sb->u.isofs_sb.s_high_sierra; + if (de->flags[-high_sierra] & 5) { + if (cpnt) { + kfree(cpnt); + cpnt = NULL; + }; + continue; + } + dlen = de->name_len[0]; + dpnt = de->name; + i = dlen; + rrflag = get_rock_ridge_filename(de, &dpnt, &dlen, inode); + if (rrflag) { + if (rrflag == -1) { /* This is a rock ridge reloc dir */ + if (cpnt) { + kfree(cpnt); + cpnt = NULL; + }; + continue; + }; + i = dlen; + } + else + if(inode->i_sb->u.isofs_sb.s_mapping == 'n') { + dpnt1 = dpnt; + dpnt = kmalloc(dlen, GFP_KERNEL); + if (!dpnt) goto out; + for (i = 0; i < dlen && i < NAME_MAX; i++) { + if (!(c = dpnt1[i])) break; + if (c >= 'A' && c <= 'Z') c |= 0x20; /* lower case */ + if (c == '.' && i == dlen-3 && de->name[i+1] == ';' && de->name[i+2] == '1') + break; /* Drop trailing '.;1' (ISO9660:1988 7.5.1 requires period) */ + if (c == ';' && i == dlen-2 && de->name[i+1] == '1') + break; /* Drop trailing ';1' */ + if (c == ';') c = '.'; /* Convert remaining ';' to '.' */ + dpnt[i] = c; + } + } + for(j=0; j<i; j++) + put_fs_byte(dpnt[j],j+dirent->d_name); /* And save it */ + if(dpnt1) { + kfree(dpnt); + dpnt = dpnt1; + } + + dcache_add(inode, dpnt, i, inode_number); + }; +#if 0 + printk("Nchar: %d\n",i); +#endif + + if (rrflag) kfree(dpnt); + if (cpnt) { + kfree(cpnt); + cpnt = NULL; + }; + + if (i) { + put_fs_long(inode_number, &dirent->d_ino); + put_fs_byte(0,i+dirent->d_name); + put_fs_word(i,&dirent->d_reclen); + brelse(bh); + return ROUND_UP(NAME_OFFSET(dirent) + i + 1); + } + } + /* We go here for any condition we cannot handle. We also drop through + to here at the end of the directory. */ + out: + if (cpnt) + kfree(cpnt); + brelse(bh); + return 0; +} + + + |