diff options
author | Ralf Baechle <ralf@linux-mips.org> | 1994-11-28 11:59:19 +0000 |
---|---|---|
committer | <ralf@linux-mips.org> | 1994-11-28 11:59:19 +0000 |
commit | 1513ff9b7899ab588401c89db0e99903dbf5f886 (patch) | |
tree | f69cc81a940a502ea23d664c3ffb2d215a479667 /fs/isofs/inode.c |
Import of Linus's Linux 1.1.68
Diffstat (limited to 'fs/isofs/inode.c')
-rw-r--r-- | fs/isofs/inode.c | 707 |
1 files changed, 707 insertions, 0 deletions
diff --git a/fs/isofs/inode.c b/fs/isofs/inode.c new file mode 100644 index 000000000..c1754e337 --- /dev/null +++ b/fs/isofs/inode.c @@ -0,0 +1,707 @@ +/* + * linux/fs/isofs/inode.c + * + * (C) 1992, 1993, 1994 Eric Youngdale Modified for ISO9660 filesystem. + * + * (C) 1991 Linus Torvalds - minix filesystem + */ + +#include <linux/stat.h> +#include <linux/sched.h> +#include <linux/iso_fs.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/mm.h> +#include <linux/string.h> +#include <linux/locks.h> +#include <linux/malloc.h> +#include <linux/errno.h> + +#include <asm/system.h> +#include <asm/segment.h> + +#ifdef LEAK_CHECK +static int check_malloc = 0; +static int check_bread = 0; +#endif + +void isofs_put_super(struct super_block *sb) +{ + lock_super(sb); + +#ifdef LEAK_CHECK + printk("Outstanding mallocs:%d, outstanding buffers: %d\n", + check_malloc, check_bread); +#endif + sb->s_dev = 0; + unlock_super(sb); + return; +} + +static struct super_operations isofs_sops = { + isofs_read_inode, + NULL, /* notify_change */ + NULL, /* write_inode */ + NULL, /* put_inode */ + isofs_put_super, + NULL, /* write_super */ + isofs_statfs, + NULL +}; + +struct iso9660_options{ + char map; + char rock; + char cruft; + unsigned char conversion; + unsigned int blocksize; + gid_t gid; + uid_t uid; +}; + +static int parse_options(char *options, struct iso9660_options * popt) +{ + char *this_char,*value; + + popt->map = 'n'; + popt->rock = 'y'; + popt->cruft = 'n'; + popt->conversion = 'a'; + popt->blocksize = 1024; + popt->gid = 0; + popt->uid = 0; + if (!options) return 1; + for (this_char = strtok(options,","); this_char; this_char = strtok(NULL,",")) { + if (strncmp(this_char,"norock",6) == 0) { + popt->rock = 'n'; + continue; + }; + if (strncmp(this_char,"cruft",5) == 0) { + popt->cruft = 'y'; + continue; + }; + if ((value = strchr(this_char,'=')) != NULL) + *value++ = 0; + if (!strcmp(this_char,"map") && value) { + if (value[0] && !value[1] && strchr("on",*value)) + popt->map = *value; + else if (!strcmp(value,"off")) popt->map = 'o'; + else if (!strcmp(value,"normal")) popt->map = 'n'; + else return 0; + } + else if (!strcmp(this_char,"conv") && value) { + if (value[0] && !value[1] && strchr("btma",*value)) + popt->conversion = *value; + else if (!strcmp(value,"binary")) popt->conversion = 'b'; + else if (!strcmp(value,"text")) popt->conversion = 't'; + else if (!strcmp(value,"mtext")) popt->conversion = 'm'; + else if (!strcmp(value,"auto")) popt->conversion = 'a'; + else return 0; + } + else if (value && + (!strcmp(this_char,"block") || + !strcmp(this_char,"uid") || + !strcmp(this_char,"gid"))) { + char * vpnt = value; + unsigned int ivalue; + ivalue = 0; + while(*vpnt){ + if(*vpnt < '0' || *vpnt > '9') break; + ivalue = ivalue * 10 + (*vpnt - '0'); + vpnt++; + }; + if (*vpnt) return 0; + switch(*this_char) { + case 'b': + if (ivalue != 1024 && ivalue != 2048) return 0; + popt->blocksize = ivalue; + break; + case 'u': + popt->uid = ivalue; + break; + case 'g': + popt->gid = ivalue; + break; + } + } + else return 0; + } + return 1; +} + +struct super_block *isofs_read_super(struct super_block *s,void *data, + int silent) +{ + struct buffer_head *bh; + int iso_blknum; + unsigned int blocksize_bits; + int high_sierra; + int dev=s->s_dev; + struct iso_volume_descriptor *vdp; + struct hs_volume_descriptor *hdp; + + struct iso_primary_descriptor *pri = NULL; + struct hs_primary_descriptor *h_pri = NULL; + + struct iso_directory_record *rootp; + + struct iso9660_options opt; + + if (!parse_options((char *) data,&opt)) { + s->s_dev = 0; + return NULL; + } + +#if 0 + printk("map = %c\n", opt.map); + printk("rock = %c\n", opt.rock); + printk("cruft = %c\n", opt.cruft); + printk("conversion = %c\n", opt.conversion); + printk("blocksize = %d\n", opt.blocksize); + printk("gid = %d\n", opt.gid); + printk("uid = %d\n", opt.uid); +#endif + + blocksize_bits = 0; + { + int i = opt.blocksize; + while (i != 1){ + blocksize_bits++; + i >>=1; + }; + }; + set_blocksize(dev, opt.blocksize); + + lock_super(s); + + s->u.isofs_sb.s_high_sierra = high_sierra = 0; /* default is iso9660 */ + + for (iso_blknum = 16; iso_blknum < 100; iso_blknum++) { + if (!(bh = bread(dev, iso_blknum << (ISOFS_BLOCK_BITS-blocksize_bits), opt.blocksize))) { + s->s_dev=0; + printk("isofs_read_super: bread failed, dev 0x%x iso_blknum %d\n", + dev, iso_blknum); + unlock_super(s); + return NULL; + } + + vdp = (struct iso_volume_descriptor *)bh->b_data; + hdp = (struct hs_volume_descriptor *)bh->b_data; + + + if (strncmp (hdp->id, HS_STANDARD_ID, sizeof hdp->id) == 0) { + if (isonum_711 (hdp->type) != ISO_VD_PRIMARY) + goto out; + if (isonum_711 (hdp->type) == ISO_VD_END) + goto out; + + s->u.isofs_sb.s_high_sierra = 1; + high_sierra = 1; + opt.rock = 'n'; + h_pri = (struct hs_primary_descriptor *)vdp; + break; + }; + + if (strncmp (vdp->id, ISO_STANDARD_ID, sizeof vdp->id) == 0) { + if (isonum_711 (vdp->type) != ISO_VD_PRIMARY) + goto out; + if (isonum_711 (vdp->type) == ISO_VD_END) + goto out; + + pri = (struct iso_primary_descriptor *)vdp; + break; + }; + + brelse(bh); + } + if(iso_blknum == 100) { + if (!silent) + printk("Unable to identify CD-ROM format.\n"); + s->s_dev = 0; + unlock_super(s); + return NULL; + }; + + + if(high_sierra){ + rootp = (struct iso_directory_record *) h_pri->root_directory_record; + if (isonum_723 (h_pri->volume_set_size) != 1) { + printk("Multi-volume disks not (yet) supported.\n"); + goto out; + }; + s->u.isofs_sb.s_nzones = isonum_733 (h_pri->volume_space_size); + s->u.isofs_sb.s_log_zone_size = isonum_723 (h_pri->logical_block_size); + s->u.isofs_sb.s_max_size = isonum_733(h_pri->volume_space_size); + } else { + rootp = (struct iso_directory_record *) pri->root_directory_record; + if (isonum_723 (pri->volume_set_size) != 1) { + printk("Multi-volume disks not (yet) supported.\n"); + goto out; + }; + s->u.isofs_sb.s_nzones = isonum_733 (pri->volume_space_size); + s->u.isofs_sb.s_log_zone_size = isonum_723 (pri->logical_block_size); + s->u.isofs_sb.s_max_size = isonum_733(pri->volume_space_size); + } + + s->u.isofs_sb.s_ninodes = 0; /* No way to figure this out easily */ + + /* RDE: convert log zone size to bit shift */ + + switch (s -> u.isofs_sb.s_log_zone_size) + { case 512: s -> u.isofs_sb.s_log_zone_size = 9; break; + case 1024: s -> u.isofs_sb.s_log_zone_size = 10; break; + case 2048: s -> u.isofs_sb.s_log_zone_size = 11; break; + + default: + printk("Bad logical zone size %ld\n", s -> u.isofs_sb.s_log_zone_size); + goto out; + } + + /* RDE: data zone now byte offset! */ + + s->u.isofs_sb.s_firstdatazone = (isonum_733( rootp->extent) + << s -> u.isofs_sb.s_log_zone_size); + s->s_magic = ISOFS_SUPER_MAGIC; + + /* 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 */; + + brelse(bh); + + printk("Max size:%ld Log zone size:%ld\n", + s->u.isofs_sb.s_max_size, + 1UL << s->u.isofs_sb.s_log_zone_size); + printk("First datazone:%ld Root inode number %d\n", + s->u.isofs_sb.s_firstdatazone >> s -> u.isofs_sb.s_log_zone_size, + isonum_733 (rootp->extent) << s -> u.isofs_sb.s_log_zone_size); + if(high_sierra) printk("Disc in High Sierra format.\n"); + unlock_super(s); + /* set up enough so that it can read an inode */ + + s->s_dev = dev; + s->s_op = &isofs_sops; + s->u.isofs_sb.s_mapping = opt.map; + s->u.isofs_sb.s_rock = (opt.rock == 'y' ? 1 : 0); + s->u.isofs_sb.s_conversion = opt.conversion; + s->u.isofs_sb.s_cruft = opt.cruft; + s->u.isofs_sb.s_uid = opt.uid; + s->u.isofs_sb.s_gid = opt.gid; + s->s_blocksize = opt.blocksize; + s->s_blocksize_bits = blocksize_bits; + s->s_mounted = iget(s, isonum_733 (rootp->extent) << s -> u.isofs_sb.s_log_zone_size); + unlock_super(s); + + if (!(s->s_mounted)) { + s->s_dev=0; + printk("get root inode failed\n"); + return NULL; + } + + if(!check_disk_change(s->s_dev)) return s; + out: /* Kick out for various error conditions */ + brelse(bh); + s->s_dev = 0; + unlock_super(s); + return NULL; +} + +void isofs_statfs (struct super_block *sb, struct statfs *buf) +{ + put_fs_long(ISOFS_SUPER_MAGIC, &buf->f_type); + put_fs_long(1 << ISOFS_BLOCK_BITS, &buf->f_bsize); + put_fs_long(sb->u.isofs_sb.s_nzones, &buf->f_blocks); + put_fs_long(0, &buf->f_bfree); + put_fs_long(0, &buf->f_bavail); + put_fs_long(sb->u.isofs_sb.s_ninodes, &buf->f_files); + put_fs_long(0, &buf->f_ffree); + put_fs_long(NAME_MAX, &buf->f_namelen); + /* Don't know what value to put in buf->f_fsid */ +} + +int isofs_bmap(struct inode * inode,int block) +{ + + if (block<0) { + printk("_isofs_bmap: block<0"); + return 0; + } + return (inode->u.isofs_i.i_first_extent >> ISOFS_BUFFER_BITS(inode)) + block; +} + +void isofs_read_inode(struct inode * inode) +{ + unsigned long bufsize = ISOFS_BUFFER_SIZE(inode); + struct buffer_head * bh; + struct iso_directory_record * raw_inode; + unsigned char *pnt = NULL; + void *cpnt = NULL; + int high_sierra; + int block; + int i; + + block = inode->i_ino >> ISOFS_BUFFER_BITS(inode); + if (!(bh=bread(inode->i_dev,block, bufsize))) { + printk("unable to read i-node block"); + goto fail; + } + + pnt = ((unsigned char *) bh->b_data + + (inode->i_ino & (bufsize - 1))); + raw_inode = ((struct iso_directory_record *) pnt); + high_sierra = inode->i_sb->u.isofs_sb.s_high_sierra; + + if ((inode->i_ino & (bufsize - 1)) + *pnt > bufsize){ + int frag1, offset; + + offset = (inode->i_ino & (bufsize - 1)); + frag1 = bufsize - offset; + cpnt = kmalloc(*pnt,GFP_KERNEL); + if (cpnt == NULL) { + printk(KERN_INFO "NoMem ISO inode %lu\n",inode->i_ino); + brelse(bh); + goto fail; + } + memcpy(cpnt, bh->b_data + offset, frag1); + brelse(bh); + if (!(bh = bread(inode->i_dev,++block, bufsize))) { + kfree(cpnt); + printk("unable to read i-node block"); + goto fail; + } + offset += *pnt - bufsize; + memcpy((char *)cpnt+frag1, bh->b_data, offset); + pnt = ((unsigned char *) cpnt); + raw_inode = ((struct iso_directory_record *) pnt); + } + + inode->i_mode = S_IRUGO; /* Everybody gets to read the file. */ + inode->i_nlink = 1; + + if (raw_inode->flags[-high_sierra] & 2) { + inode->i_mode = S_IRUGO | S_IXUGO | S_IFDIR; + inode->i_nlink = 1; /* Set to 1. We know there are 2, but + the find utility tries to optimize + if it is 2, and it screws up. It is + easier to give 1 which tells find to + do it the hard way. */ + } else { + inode->i_mode = S_IRUGO; /* Everybody gets to read the file. */ + inode->i_nlink = 1; + inode->i_mode |= S_IFREG; +/* If there are no periods in the name, then set the execute permission bit */ + for(i=0; i< raw_inode->name_len[0]; i++) + if(raw_inode->name[i]=='.' || raw_inode->name[i]==';') + break; + if(i == raw_inode->name_len[0] || raw_inode->name[i] == ';') + inode->i_mode |= S_IXUGO; /* execute permission */ + } + inode->i_uid = inode->i_sb->u.isofs_sb.s_uid; + inode->i_gid = inode->i_sb->u.isofs_sb.s_gid; + inode->i_size = isonum_733 (raw_inode->size); + + /* There are defective discs out there - we do this to protect + ourselves. A cdrom will never contain more than 700Mb */ + if((inode->i_size < 0 || inode->i_size > 700000000) && + inode->i_sb->u.isofs_sb.s_cruft == 'n') { + printk("Warning: defective cdrom. Enabling \"cruft\" mount option.\n"); + inode->i_sb->u.isofs_sb.s_cruft = 'y'; + } + +/* Some dipshit decided to store some other bit of information in the high + byte of the file length. Catch this and holler. WARNING: this will make + it impossible for a file to be > 16Mb on the CDROM!!!*/ + + if(inode->i_sb->u.isofs_sb.s_cruft == 'y' && + inode->i_size & 0xff000000){ +/* printk("Illegal format on cdrom. Pester manufacturer.\n"); */ + inode->i_size &= 0x00ffffff; + } + + if (raw_inode->interleave[0]) { + printk("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(raw_inode->file_unit_size[0] != 0){ + printk("File unit size != 0 for ISO file (%ld).\n",inode->i_ino); + } + + /* I have no idea what other flag bits are used for, so + we will flag it for now */ +#ifdef DEBUG + if((raw_inode->flags[-high_sierra] & ~2)!= 0){ + printk("Unusual flag settings for ISO file (%ld %x).\n", + inode->i_ino, raw_inode->flags[-high_sierra]); + } +#endif + +#ifdef DEBUG + printk("Get inode %d: %d %d: %d\n",inode->i_ino, block, + ((int)pnt) & 0x3ff, inode->i_size); +#endif + + inode->i_mtime = inode->i_atime = inode->i_ctime = + iso_date(raw_inode->date, high_sierra); + + inode->u.isofs_i.i_first_extent = (isonum_733 (raw_inode->extent) + + isonum_711 (raw_inode->ext_attr_length)) + << inode -> i_sb -> u.isofs_sb.s_log_zone_size; + + inode->u.isofs_i.i_backlink = 0xffffffff; /* Will be used for previous directory */ + switch (inode->i_sb->u.isofs_sb.s_conversion){ + case 'a': + inode->u.isofs_i.i_file_format = ISOFS_FILE_UNKNOWN; /* File type */ + break; + case 'b': + inode->u.isofs_i.i_file_format = ISOFS_FILE_BINARY; /* File type */ + break; + case 't': + inode->u.isofs_i.i_file_format = ISOFS_FILE_TEXT; /* File type */ + break; + case 'm': + inode->u.isofs_i.i_file_format = ISOFS_FILE_TEXT_M; /* File type */ + break; + } + +/* 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(raw_inode, inode); + +#ifdef DEBUG + printk("Inode: %x extent: %x\n",inode->i_ino, inode->u.isofs_i.i_first_extent); +#endif + brelse(bh); + + inode->i_op = NULL; + + /* A volume number of 0 is nonsense. Disable checking if we see + this */ + if (inode->i_sb->u.isofs_sb.s_cruft == 'n' && + isonum_723 (raw_inode->volume_sequence_number) == 0) { + printk("Warning: defective cdrom. Enabling \"cruft\" mount option.\n"); + inode->i_sb->u.isofs_sb.s_cruft = 'y'; + } + + if (inode->i_sb->u.isofs_sb.s_cruft != 'y' && + isonum_723 (raw_inode->volume_sequence_number) != 1) { + printk("Multi volume CD somehow got mounted.\n"); + } else { + if (S_ISREG(inode->i_mode)) + inode->i_op = &isofs_file_inode_operations; + else if (S_ISDIR(inode->i_mode)) + inode->i_op = &isofs_dir_inode_operations; + else if (S_ISLNK(inode->i_mode)) + inode->i_op = &isofs_symlink_inode_operations; + else if (S_ISCHR(inode->i_mode)) + inode->i_op = &chrdev_inode_operations; + else if (S_ISBLK(inode->i_mode)) + inode->i_op = &blkdev_inode_operations; + else if (S_ISFIFO(inode->i_mode)) + init_fifo(inode); + } + if (cpnt) { + kfree (cpnt); + cpnt = NULL; + } + return; + fail: + /* With a data error we return this information */ + inode->i_mtime = inode->i_atime = inode->i_ctime = 0; + inode->u.isofs_i.i_first_extent = 0; + inode->u.isofs_i.i_backlink = 0xffffffff; + inode->i_size = 0; + inode->i_nlink = 1; + inode->i_uid = inode->i_gid = 0; + inode->i_mode = S_IFREG; /*Regular file, no one gets to read*/ + inode->i_op = NULL; + return; +} + +/* There are times when we need to know the inode number of a parent of + a particular directory. When control passes through a routine that + has access to the parent information, it fills it into the inode structure, + but sometimes the inode gets flushed out of the queue, and someone + remembers the number. When they try to open up again, we have lost + the information. The '..' entry on the disc points to the data area + for a particular inode, so we can follow these links back up, but since + we do not know the inode number, we do not actually know how large the + directory is. The disc is almost always correct, and there is + enough error checking on the drive itself, but an open ended search + makes me a little nervous. + + The bsd iso filesystem uses the extent number for an inode, and this + would work really nicely for us except that the read_inode function + would not have any clean way of finding the actual directory record + that goes with the file. If we had such info, then it would pay + to change the inode numbers and eliminate this function. +*/ + +int isofs_lookup_grandparent(struct inode * parent, int extent) +{ + unsigned long bufsize = ISOFS_BUFFER_SIZE(parent); + unsigned char bufbits = ISOFS_BUFFER_BITS(parent); + unsigned int block,offset; + int parent_dir, inode_number; + int old_offset; + void * cpnt = NULL; + int result; + int directory_size; + struct buffer_head * bh; + struct iso_directory_record * de; + + offset = 0; + block = extent << (ISOFS_BLOCK_BITS - bufbits); + if (!(bh = bread(parent->i_dev, block, bufsize))) return -1; + + while (1 == 1) { + de = (struct iso_directory_record *) (bh->b_data + offset); + if (*((unsigned char *) de) == 0) + { + brelse(bh); + return -1; + } + + offset += *((unsigned char *) de); + + if (offset >= bufsize) + { + printk(".. Directory not in first block" + " of directory.\n"); + brelse(bh); + return -1; + } + + if (de->name_len[0] == 1 && de->name[0] == 1) + { + parent_dir = find_rock_ridge_relocation(de, parent); + directory_size = isonum_733 (de->size); + brelse(bh); + break; + } + } +#ifdef DEBUG + printk("Parent dir:%x\n",parent_dir); +#endif + /* Now we know the extent where the parent dir starts on. */ + + result = -1; + + offset = 0; + block = parent_dir << (ISOFS_BLOCK_BITS - bufbits); + if (!block || !(bh = bread(parent->i_dev,block, bufsize))) + return -1; + + for(;;) + { + 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; + block++; + directory_size -= bufsize; + if(directory_size < 0) return -1; + if((block & 1) && (ISOFS_BLOCK_BITS - bufbits)) + return -1; + if (!block + || !(bh = bread(parent->i_dev,block, bufsize))) + return -1; + 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); + + if (offset >= bufsize) + { + unsigned int frag1; + frag1 = bufsize - old_offset; + cpnt = kmalloc(*((unsigned char *) de),GFP_KERNEL); + if (!cpnt) return -1; + memcpy(cpnt, bh->b_data + old_offset, frag1); + de = (struct iso_directory_record *) ((char *)cpnt); + brelse(bh); + offset -= bufsize; + directory_size -= bufsize; + if(directory_size < 0) return -1; + block++; + if(!(bh = bread(parent->i_dev,block,bufsize))) { + kfree(cpnt); + return -1; + }; + memcpy((char *)cpnt+frag1, bh->b_data, offset); + } + + if (find_rock_ridge_relocation(de, parent) == extent){ + result = inode_number; + goto out; + } + + if (cpnt) { + kfree(cpnt); + cpnt = NULL; + } + } + + /* 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); + cpnt = NULL; + } + brelse(bh); +#ifdef DEBUG + printk("Resultant Inode %d\n",result); +#endif + return result; +} + +#ifdef LEAK_CHECK +#undef malloc +#undef free_s +#undef bread +#undef brelse + +void * leak_check_malloc(unsigned int size){ + void * tmp; + check_malloc++; + tmp = kmalloc(size, GFP_KERNEL); + return tmp; +} + +void leak_check_free_s(void * obj, int size){ + check_malloc--; + return kfree_s(obj, size); +} + +struct buffer_head * leak_check_bread(int dev, int block, int size){ + check_bread++; + return bread(dev, block, size); +} + +void leak_check_brelse(struct buffer_head * bh){ + check_bread--; + return brelse(bh); +} + +#endif |