diff options
author | Ralf Baechle <ralf@linux-mips.org> | 1997-01-07 02:33:00 +0000 |
---|---|---|
committer | <ralf@linux-mips.org> | 1997-01-07 02:33:00 +0000 |
commit | beb116954b9b7f3bb56412b2494b562f02b864b1 (patch) | |
tree | 120e997879884e1b9d93b265221b939d2ef1ade1 /fs/affs | |
parent | 908d4681a1dc3792ecafbe64265783a86c4cccb6 (diff) |
Import of Linux/MIPS 2.1.14
Diffstat (limited to 'fs/affs')
-rw-r--r-- | fs/affs/Makefile | 14 | ||||
-rw-r--r-- | fs/affs/amigaffs.c | 273 | ||||
-rw-r--r-- | fs/affs/bitmap.c | 385 | ||||
-rw-r--r-- | fs/affs/dir.c | 192 | ||||
-rw-r--r-- | fs/affs/file.c | 916 | ||||
-rw-r--r-- | fs/affs/inode.c | 1013 | ||||
-rw-r--r-- | fs/affs/namei.c | 741 | ||||
-rw-r--r-- | fs/affs/symlink.c | 176 |
8 files changed, 3710 insertions, 0 deletions
diff --git a/fs/affs/Makefile b/fs/affs/Makefile new file mode 100644 index 000000000..77cb4225f --- /dev/null +++ b/fs/affs/Makefile @@ -0,0 +1,14 @@ +# +# Makefile for the linux affs-filesystem routines. +# +# Note! Dependencies are done automagically by 'make dep', which also +# removes any old dependencies. DON'T put your own dependencies here +# unless it's something special (ie not a .c file). +# +# Note 2! The CFLAGS definitions are now in the main makefile... + +O_TARGET := affs.o +O_OBJS := namei.o inode.o file.o dir.o amigaffs.o bitmap.o symlink.o +M_OBJS := $(O_TARGET) + +include $(TOPDIR)/Rules.make diff --git a/fs/affs/amigaffs.c b/fs/affs/amigaffs.c new file mode 100644 index 000000000..63d76a86e --- /dev/null +++ b/fs/affs/amigaffs.c @@ -0,0 +1,273 @@ +/* + * linux/fs/affs/amigaffs.c + * + * (c) 1996 Hans-Joachim Widmaier - Rewritten + * + * (C) 1993 Ray Burr - Amiga FFS filesystem. + * + */ + +#include <linux/stat.h> +#include <linux/sched.h> +#include <linux/affs_fs.h> +#include <linux/string.h> +#include <linux/locks.h> +#include <linux/mm.h> +#include <linux/amigaffs.h> + +extern struct timezone sys_tz; + +/* + * Functions for accessing Amiga-FFS structures. + * + */ + +/* Find the next used hash entry at or after *HASH_POS in a directory's hash + table. *HASH_POS is assigned that entry's number. DIR_DATA points to + the directory header block in memory. If there are no more entries, + 0 is returned. Otherwise, the key number in the next used hash slot + is returned. */ + +int +affs_find_next_hash_entry(int hsize, void *dir_data, int *hash_pos) +{ + struct dir_front *dir_front = dir_data; + int i; + + for (i = *hash_pos; i < hsize; i++) + if (dir_front->hashtable[i] != 0) + break; + if (i >= hsize) + return 0; + *hash_pos = i; + return htonl(dir_front->hashtable[i]); +} + +/* Set *NAME to point to the file name in a file header block in memory + pointed to by FH_DATA. The length of the name is returned. */ + +int +affs_get_file_name(int bsize, void *fh_data, char **name) +{ + struct file_end *file_end; + + file_end = GET_END_PTR(struct file_end, fh_data, bsize); + if (file_end->file_name[0] == 0 + || file_end->file_name[0] > 30) { + printk ("affs_get_file_name: OOPS! bad filename\n"); + printk (" file_end->file_name[0] = %d\n", + file_end->file_name[0]); + *name = "***BAD_FILE***"; + return 14; + } + *name = (char *) &file_end->file_name[1]; + return file_end->file_name[0]; +} + +/* Find the predecessor in the hash chain */ + +int +affs_fix_hash_pred(struct inode *startino, int startoffset, int key, int newkey) +{ + struct buffer_head *bh = NULL; + int nextkey; + int ptype, stype; + int retval; + + nextkey = startino->i_ino; + retval = -ENOENT; + lock_super(startino->i_sb); + while (1) { + pr_debug("AFFS: fix_hash_pred(): next key=%d, offset=%d\n",nextkey,startoffset); + if (nextkey == 0) + break; + if (!(bh = affs_bread(startino->i_dev,nextkey,AFFS_I2BSIZE(startino)))) + break; + if (affs_checksum_block(AFFS_I2BSIZE(startino),bh->b_data,&ptype,&stype) + || ptype != T_SHORT || (stype != ST_FILE && stype != ST_USERDIR && + stype != ST_LINKFILE && stype != ST_LINKDIR && + stype != ST_ROOT && stype != ST_SOFTLINK)) { + printk("AFFS: bad block found in link chain (ptype=%d, stype=%d)\n", + ptype,stype); + affs_brelse(bh); + break; + } + nextkey = htonl(((__u32 *)bh->b_data)[startoffset]); + if (nextkey == key) { + ((__u32 *)bh->b_data)[startoffset] = newkey; + affs_fix_checksum(AFFS_I2BSIZE(startino),bh->b_data,5); + mark_buffer_dirty(bh,1); + affs_brelse(bh); + retval = 0; + break; + } + affs_brelse(bh); + startoffset = AFFS_I2BSIZE(startino) / 4 - 4; + } + unlock_super(startino->i_sb); + + return retval; +} + +/* Remove inode from link chain */ + +int +affs_fix_link_pred(struct inode *startino, int key, int newkey) +{ + struct buffer_head *bh = NULL; + int nextkey; + int offset; + int etype = 0; + int ptype, stype; + int retval; + + offset = AFFS_I2BSIZE(startino) / 4 - 10; + nextkey = startino->i_ino; + retval = -ENOENT; + lock_super(startino->i_sb); + while (1) { + if (nextkey == 0) + break; + pr_debug("AFFS: find_link_pred(): next key=%d\n",nextkey); + if (!(bh = affs_bread(startino->i_dev,nextkey,AFFS_I2BSIZE(startino)))) + break; + if (affs_checksum_block(AFFS_I2BSIZE(startino),bh->b_data,&ptype,&stype) + || ptype != T_SHORT) { + affs_brelse(bh); + break; + } + if (!etype) { + if (stype != ST_FILE && stype != ST_USERDIR) { + affs_brelse(bh); + break; + } + if (stype == ST_FILE) + etype = ST_LINKFILE; + else + etype = ST_LINKDIR; + } else if (stype != etype) { + affs_brelse(bh); + retval = -EPERM; + break; + } + nextkey = htonl(((__u32 *)bh->b_data)[offset]); + if (nextkey == key) { + FILE_END(bh->b_data,startino)->link_chain = newkey; + affs_fix_checksum(AFFS_I2BSIZE(startino),bh->b_data,5); + mark_buffer_dirty(bh,1); + affs_brelse(bh); + retval = 0; + break; + } + affs_brelse(bh); + } + unlock_super(startino->i_sb); + return retval; +} + +/* Checksum a block, do various consistency checks and optionally return + the blocks type number. DATA points to the block. If their pointers + are non-null, *PTYPE and *STYPE are set to the primary and secondary + block types respectively, *HASHSIZE is set to the size of the hashtable + (which lets us calculate the block size). + Returns non-zero if the block is not consistent. */ + +__u32 +affs_checksum_block(int bsize, void *data, int *ptype, int *stype) +{ + __u32 sum; + __u32 *p; + + bsize /= 4; + if (ptype) + *ptype = htonl(((__s32 *)data)[0]); + if (stype) + *stype = htonl(((__s32 *)data)[bsize - 1]); + + sum = 0; + p = data; + while (bsize--) + sum += htonl(*p++); + return sum; +} + +void +affs_fix_checksum(int bsize, void *data, int cspos) +{ + __u32 ocs; + __u32 cs; + + cs = affs_checksum_block(bsize,data,NULL,NULL); + ocs = htonl (((__u32 *)data)[cspos]); + ocs -= cs; + ((__u32 *)data)[cspos] = htonl(ocs); +} + +void +secs_to_datestamp(int secs, struct DateStamp *ds) +{ + __u32 days; + __u32 minute; + + secs -= sys_tz.tz_minuteswest * 60 +((8 * 365 + 2) * 24 * 60 * 60); + if (secs < 0) + secs = 0; + days = secs / 86400; + secs -= days * 86400; + minute = secs / 60; + secs -= minute * 60; + + ds->ds_Days = htonl(days); + ds->ds_Minute = htonl(minute); + ds->ds_Tick = htonl(secs * 50); +} + +int +prot_to_mode(__u32 prot) +{ + int mode = 0; + + if (AFFS_UMAYWRITE(prot)) + mode |= S_IWUSR; + if (AFFS_UMAYREAD(prot)) + mode |= S_IRUSR; + if (AFFS_UMAYEXECUTE(prot)) + mode |= S_IXUSR; + if (AFFS_GMAYWRITE(prot)) + mode |= S_IWGRP; + if (AFFS_GMAYREAD(prot)) + mode |= S_IRGRP; + if (AFFS_GMAYEXECUTE(prot)) + mode |= S_IXGRP; + if (AFFS_OMAYWRITE(prot)) + mode |= S_IWOTH; + if (AFFS_OMAYREAD(prot)) + mode |= S_IROTH; + if (AFFS_OMAYEXECUTE(prot)) + mode |= S_IXOTH; + + return mode; +} + +unsigned int +mode_to_prot(int mode) +{ + unsigned int prot = 0; + + if (mode & S_IXUSR) + prot |= FIBF_SCRIPT; + if (mode & S_IRUSR) + prot |= FIBF_READ; + if (mode & S_IWUSR) + prot |= FIBF_WRITE | FIBF_DELETE; + if (mode & S_IRGRP) + prot |= FIBF_GRP_READ; + if (mode & S_IWGRP) + prot |= FIBF_GRP_WRITE; + if (mode & S_IROTH) + prot |= FIBF_OTR_READ; + if (mode & S_IWOTH) + prot |= FIBF_OTR_WRITE; + + return prot; +} diff --git a/fs/affs/bitmap.c b/fs/affs/bitmap.c new file mode 100644 index 000000000..1d69d5a3d --- /dev/null +++ b/fs/affs/bitmap.c @@ -0,0 +1,385 @@ +/* + * linux/fs/affs/bitmap.c + * + * (c) 1996 Hans-Joachim Widmaier + * + * + * bitmap.c contains the code that handles all bitmap related stuff - + * block allocation, deallocation, calculation of free space. + */ + +#include <linux/sched.h> +#include <linux/affs_fs.h> +#include <linux/stat.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/locks.h> +#include <linux/amigaffs.h> + +#include <asm/bitops.h> + +/* This is, of course, shamelessly stolen from fs/minix */ + +static int nibblemap[] = { 0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4 }; + +int +affs_count_free_bits(int blocksize, const char *data) +{ + int free; + int i; + + free = 0; + for (i = 0; i < blocksize; i++) { + free += nibblemap[data[i] & 0xF] + nibblemap[(data[i]>>4) & 0xF]; + } + + return free; +} + +int +affs_count_free_blocks(struct super_block *s) +{ + int free; + int i; + + pr_debug("AFFS: count_free_blocks()\n"); + + free = 0; + if (s->u.affs_sb.s_flags & SF_BM_VALID) { + for (i = 0; i < s->u.affs_sb.s_num_az; i++) { + free += s->u.affs_sb.s_alloc[i].az_free; + } + } + return free; +} + +void +affs_free_block(struct super_block *sb, int block) +{ + int bmap; + int bit; + int blk; + int zone_no; + struct affs_bm_info *bm; + + pr_debug("AFFS: free_block(%d)\n",block); + + blk = block - sb->u.affs_sb.s_reserved; + bmap = blk / (sb->s_blocksize * 8 - 32); + bit = blk % (sb->s_blocksize * 8 - 32); + zone_no = (bmap << (sb->s_blocksize_bits - 7)) + bit / 1024; + bm = &sb->u.affs_sb.s_bitmap[bmap]; + if (bmap >= sb->u.affs_sb.s_bm_count) { + printk("AFFS: free_block(): block %d outside partition.\n",block); + return; + } + blk = 0; + set_bit(bit & 31,&blk); + + lock_super(sb); + bm->bm_count++; + if (!bm->bm_bh) { + bm->bm_bh = affs_bread(sb->s_dev,bm->bm_key,sb->s_blocksize); + if (!bm->bm_bh) { + bm->bm_count--; + unlock_super(sb); + printk("AFFS: free_block(): Cannot read bitmap block %d\n",bm->bm_key); + return; + } + } + if (set_bit(bit ^ BO_EXBITS,bm->bm_bh->b_data + 4)) + printk("AFFS: free_block(): block %d is already free.\n",block); + else { + sb->u.affs_sb.s_alloc[zone_no].az_free++; + ((__u32 *)bm->bm_bh->b_data)[0] = ntohl(htonl(((__u32 *)bm->bm_bh->b_data)[0]) - blk); + mark_buffer_dirty(bm->bm_bh,1); + sb->s_dirt = 1; + } + if (--bm->bm_count == 0) { + affs_brelse(bm->bm_bh); + bm->bm_bh = NULL; + } + unlock_super(sb); +} + +static int +affs_balloc(struct inode *inode, int zone_no) +{ + __u32 w; + __u32 *bm; + int fb; + int i; + int fwb; + int block; + struct affs_zone *zone; + struct affs_alloc_zone *az; + struct super_block *sb; + + sb = inode->i_sb; + zone = &sb->u.affs_sb.s_zones[zone_no]; + + if (!zone->z_bm || !zone->z_bm->bm_bh) + return 0; + + pr_debug("AFFS: balloc(inode=%lu,zone=%d)\n",inode->i_ino,zone_no); + + az = &sb->u.affs_sb.s_alloc[zone->z_az_no]; + bm = (__u32 *)zone->z_bm->bm_bh->b_data; +repeat: + for (i = zone->z_start; i < zone->z_end; i++) { + if (bm[i]) + goto found; + } + return 0; + +found: + fwb = zone->z_bm->bm_firstblk + (i - 1) * 32; + lock_super(sb); + zone->z_start = i; + w = ~htonl(bm[i]); + fb = find_first_zero_bit(&w,32); + if (fb > 31 || !clear_bit(fb ^ BO_EXBITS,&bm[i])) { + unlock_super(sb); + printk("AFFS: balloc(): empty block disappeared somehow\n"); + goto repeat; + } + block = fwb + fb; + az->az_free--; + + /* prealloc as much as possible within this word, but not in header zone */ + + if (zone_no) { + while (inode->u.affs_i.i_pa_cnt < AFFS_MAX_PREALLOC && ++fb < 32) { + fb = find_next_zero_bit(&w,32,fb); + if (fb > 31) + break; + if (!clear_bit(fb ^ BO_EXBITS,&bm[i])) { + printk("AFFS: balloc(): empty block disappeared\n"); + break; + } + inode->u.affs_i.i_data[inode->u.affs_i.i_pa_last++] = fwb + fb; + inode->u.affs_i.i_pa_last &= AFFS_MAX_PREALLOC - 1; + inode->u.affs_i.i_pa_cnt++; + az->az_free--; + } + } + w = ~w - htonl(bm[i]); + bm[0] = ntohl(htonl(bm[0]) + w); + unlock_super(sb); + mark_buffer_dirty(zone->z_bm->bm_bh,1); + zone->z_lru_time = jiffies; + + return block; +} + +static int +affs_find_new_zone(struct super_block *sb, int zone_no) +{ + struct affs_bm_info *bm; + struct affs_zone *zone; + struct affs_alloc_zone *az; + int bestfree; + int bestno; + int bestused; + int lusers; + int i; + int min; + + pr_debug("AFFS: find_new_zone(zone_no=%d)\n",zone_no); + + bestfree = 0; + bestused = -1; + bestno = -1; + lusers = MAX_ZONES; + min = zone_no ? AFFS_DATA_MIN_FREE : AFFS_HDR_MIN_FREE; + lock_super(sb); + zone = &sb->u.affs_sb.s_zones[zone_no]; + i = zone->z_az_no; + az = &sb->u.affs_sb.s_alloc[i]; + if (zone->z_bm && zone->z_bm->bm_count) { + if (--zone->z_bm->bm_count == 0) { + affs_brelse(zone->z_bm->bm_bh); + zone->z_bm->bm_bh = NULL; + } + if (az->az_count) + az->az_count--; + else + printk("AFFS: find_new_zone(): az_count=0, but bm used\n"); + + } + while (1) { + if (i >= sb->u.affs_sb.s_num_az) + i = 0; + az = &sb->u.affs_sb.s_alloc[i]; + if (!az->az_count) { + if (az->az_free > min) { + break; + } + if (az->az_free > bestfree) { + bestfree = az->az_free; + bestno = i; + } + } else if (az->az_free && az->az_count < lusers) { + lusers = az->az_count; + bestused = i; + } + if (++i == zone->z_az_no) { /* Seen all */ + if (bestno >= 0) { + i = bestno; + } else { + i = bestused; + } + break; + } + } + if (i < 0) { + /* Didn't find a single free block anywhere. */ + unlock_super(sb); + return 0; + } + az = &sb->u.affs_sb.s_alloc[i]; + az->az_count++; + bm = &sb->u.affs_sb.s_bitmap[i >> (sb->s_blocksize_bits - 7)]; + bm->bm_count++; + if (!bm->bm_bh) + bm->bm_bh = affs_bread(sb->s_dev,bm->bm_key,sb->s_blocksize); + if (!bm->bm_bh) { + bm->bm_count--; + az->az_count--; + unlock_super(sb); + printk("AFFS: find_new_zone(): Cannot read bitmap\n"); + return 0; + } + zone->z_bm = bm; + zone->z_start = (i & ((sb->s_blocksize / 128) - 1)) * 32 + 1; + zone->z_end = zone->z_start + az->az_size; + zone->z_az_no = i; + zone->z_lru_time = jiffies; + pr_debug(" ++ found zone (%d) in bm %d at lw offset %d with %d free blocks\n", + i,(i >> (sb->s_blocksize_bits - 7)),zone->z_start,az->az_free); + unlock_super(sb); + return az->az_free; +} + +int +affs_new_header(struct inode *inode) +{ + int block; + struct buffer_head *bh; + + pr_debug("AFFS: new_header(ino=%lu)\n",inode->i_ino); + + if (!(block = affs_balloc(inode,0))) { + while(affs_find_new_zone(inode->i_sb,0)) { + if ((block = affs_balloc(inode,0))) + goto init_block; + schedule(); + } + return 0; + } +init_block: + if (!(bh = getblk(inode->i_dev,block,AFFS_I2BSIZE(inode)))) { + printk("AFFS: balloc(): cannot read block %d\n",block); + return 0; + } + memset(bh->b_data,0,AFFS_I2BSIZE(inode)); + mark_buffer_uptodate(bh,1); + mark_buffer_dirty(bh,1); + affs_brelse(bh); + + return block; +} + +int +affs_new_data(struct inode *inode) +{ + int empty, old; + unsigned long oldest; + struct affs_zone *zone; + struct super_block *sb; + struct buffer_head *bh; + int i = 0; + int block; + + pr_debug("AFFS: new_data(ino=%lu)\n",inode->i_ino); + + sb = inode->i_sb; + lock_super(sb); + if (inode->u.affs_i.i_pa_cnt) { + inode->u.affs_i.i_pa_cnt--; + unlock_super(sb); + block = inode->u.affs_i.i_data[inode->u.affs_i.i_pa_next++]; + inode->u.affs_i.i_pa_next &= AFFS_MAX_PREALLOC - 1; + goto init_block; + } + unlock_super(sb); + oldest = jiffies; + old = 0; + empty = 0; + zone = &sb->u.affs_sb.s_zones[inode->u.affs_i.i_zone]; + if (zone->z_ino == inode->i_ino) { + i = inode->u.affs_i.i_zone; + goto found; + } + for (i = 1; i < MAX_ZONES; i++) { + zone = &sb->u.affs_sb.s_zones[i]; + if (!empty && zone->z_bm && !zone->z_ino) + empty = i; + if (zone->z_bm && zone->z_lru_time < oldest) { + old = i; + oldest = zone->z_lru_time; + } + } + if (empty) + i = empty; + else if (old) + i = old; + else { + inode->u.affs_i.i_zone = 0; + return affs_new_header(inode); + } + + inode->u.affs_i.i_zone = i; + zone->z_ino = inode->i_ino; + +found: + zone = &sb->u.affs_sb.s_zones[i]; + if (!(block = affs_balloc(inode,i))) { /* No data zones left */ + while(affs_find_new_zone(sb,i)) { + if ((block = affs_balloc(inode,i))) + goto init_block; + schedule(); + } + inode->u.affs_i.i_zone = 0; + zone->z_ino = -1; + return 0; + } + +init_block: + if (!(bh = getblk(inode->i_dev,block,sb->s_blocksize))) { + printk("AFFS: balloc(): cannot read block %u\n",block); + return 0; + } + memset(bh->b_data,0,sb->s_blocksize); + mark_buffer_uptodate(bh,1); + mark_buffer_dirty(bh,1); + affs_brelse(bh); + + return block; +} + +void +affs_make_zones(struct super_block *sb) +{ + int i, mid; + + pr_debug("AFFS: make_zones(): num_zones=%d\n",sb->u.affs_sb.s_num_az); + + mid = (sb->u.affs_sb.s_num_az + 1) / 2; + sb->u.affs_sb.s_zones[0].z_az_no = mid; + affs_find_new_zone(sb,0); + for (i = 1; i < MAX_ZONES; i++) { + sb->u.affs_sb.s_zones[i].z_az_no = mid; + affs_find_new_zone(sb,i); + } +} diff --git a/fs/affs/dir.c b/fs/affs/dir.c new file mode 100644 index 000000000..b82a17099 --- /dev/null +++ b/fs/affs/dir.c @@ -0,0 +1,192 @@ +/* + * linux/fs/affs/dir.c + * + * (c) 1996 Hans-Joachim Widmaier - Rewritten + * + * (C) 1993 Ray Burr - Modified for Amiga FFS filesystem. + * + * (C) 1992 Eric Youngdale Modified for ISO9660 filesystem. + * + * (C) 1991 Linus Torvalds - minix filesystem + * + * affs directory handling functions + * + */ + +#include <asm/uaccess.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/affs_fs.h> +#include <linux/stat.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/amigaffs.h> + +static int affs_readdir(struct inode *, struct file *, void *, filldir_t); +static long affs_dir_read(struct inode * inode, struct file * filp, char * buf, + unsigned long count); + +static struct file_operations affs_dir_operations = { + NULL, /* lseek - default */ + affs_dir_read, /* read */ + NULL, /* write - bad */ + affs_readdir, /* readdir */ + NULL, /* select - default */ + NULL, /* ioctl - default */ + NULL, /* mmap */ + NULL, /* no special open code */ + NULL, /* no special release code */ + file_fsync /* default fsync */ +}; + +/* + * directories can handle most operations... + */ +struct inode_operations affs_dir_inode_operations = { + &affs_dir_operations, /* default directory file-ops */ + affs_create, /* create */ + affs_lookup, /* lookup */ + affs_link, /* link */ + affs_unlink, /* unlink */ + affs_symlink, /* symlink */ + affs_mkdir, /* mkdir */ + affs_rmdir, /* rmdir */ + NULL, /* mknod */ + affs_rename, /* rename */ + NULL, /* readlink */ + NULL, /* follow_link */ + NULL, /* readpage */ + NULL, /* writepage */ + NULL, /* bmap */ + NULL, /* truncate */ + NULL /* permissions */ +}; + +static long +affs_dir_read(struct inode * inode, struct file * filp, char * buf, unsigned long count) +{ + return -EISDIR; +} + +static int +affs_readdir(struct inode *inode, struct file *filp, void *dirent, filldir_t filldir) +{ + int j, namelen; + int i; + int hash_pos; + int chain_pos; + unsigned long ino; + unsigned long old; + int stored; + char *name; + struct buffer_head *dir_bh; + struct buffer_head *fh_bh; + struct inode *dir; + + pr_debug("AFFS: readdir(ino=%ld,f_pos=%lu)\n",inode->i_ino,filp->f_pos); + + + if (!inode || !S_ISDIR(inode->i_mode)) + return -EBADF; + + stored = 0; + dir_bh = NULL; + fh_bh = NULL; + dir = NULL; + old = filp->f_pos & 0x80000000; + filp->f_pos &= 0x7FFFFFFF; + + if (filp->f_pos == 0) { + filp->private_data = (void *)0; + if (filldir(dirent,".",1,filp->f_pos,inode->i_ino) < 0) { + return 0; + } + ++filp->f_pos; + stored++; + } + if (filp->f_pos == 1) { + if (filldir(dirent,"..",2,filp->f_pos,affs_parent_ino(inode)) < 0) { + filp->f_pos |= 0x80000000; + return stored; + } + filp->f_pos = 2; + stored++; + } + + /* Read original if this is a link */ + ino = inode->u.affs_i.i_original ? inode->u.affs_i.i_original : inode->i_ino; + if (!(dir = iget(inode->i_sb,ino))) + return stored; + + chain_pos = (filp->f_pos - 2) & 0xffff; + hash_pos = (filp->f_pos - 2) >> 16; + if (chain_pos == 0xffff) { + printk("AFFS: more than 65535 entries in chain\n"); + chain_pos = 0; + hash_pos++; + filp->f_pos = ((hash_pos << 16) | chain_pos) + 2; + } + if (!(dir_bh = affs_bread(inode->i_dev,ino,AFFS_I2BSIZE(inode)))) + goto readdir_done; + + while (!stored || !old) { + while (hash_pos < AFFS_I2HSIZE(inode) && + !((struct dir_front *)dir_bh->b_data)->hashtable[hash_pos]) + hash_pos++; + if (hash_pos >= AFFS_I2HSIZE(inode)) + goto readdir_done; + + i = htonl(((struct dir_front *)dir_bh->b_data)->hashtable[hash_pos]); + j = chain_pos; + /* If the directory hasn't changed since the last call to readdir(), + * we can jump directly to where we left off. + */ + if (filp->private_data && filp->f_version == dir->i_version) { + i = (int)filp->private_data; + j = 0; + pr_debug("AFFS: readdir() left off=%d\n",i); + } + filp->f_version = dir->i_version; + pr_debug("AFFS: hash_pos=%lu chain_pos=%lu\n", hash_pos, chain_pos); + while (i) { + if (!(fh_bh = affs_bread(inode->i_dev,i,AFFS_I2BSIZE(inode)))) { + printk("AFFS: readdir: Can't get block %d\n",i); + goto readdir_done; + } + ino = i; + i = htonl(FILE_END(fh_bh->b_data,inode)->hash_chain); + if (j == 0) + break; + affs_brelse(fh_bh); + fh_bh = NULL; + j--; + } + if (fh_bh) { + namelen = affs_get_file_name(AFFS_I2BSIZE(inode),fh_bh->b_data,&name); + pr_debug("AFFS: readdir(): filldir(..,\"%.*s\",ino=%lu), i=%lu\n", + namelen,name,ino,i); + filp->private_data = (void *)ino; + if (filldir(dirent,name,namelen,filp->f_pos,ino) < 0) + goto readdir_done; + filp->private_data = (void *)i; + affs_brelse(fh_bh); + fh_bh = NULL; + stored++; + } + if (i == 0) { + hash_pos++; + chain_pos = 0; + } else + chain_pos++; + filp->f_pos = ((hash_pos << 16) | chain_pos) + 2; + } + +readdir_done: + filp->f_pos |= old; + affs_brelse(dir_bh); + affs_brelse(fh_bh); + iput(dir); + pr_debug("AFFS: readdir()=%d\n",stored); + return stored; +} diff --git a/fs/affs/file.c b/fs/affs/file.c new file mode 100644 index 000000000..aa37f47a0 --- /dev/null +++ b/fs/affs/file.c @@ -0,0 +1,916 @@ +/* + * linux/fs/affs/file.c + * + * (c) 1996 Hans-Joachim Widmaier - Rewritten + * + * (C) 1993 Ray Burr - Modified for Amiga FFS filesystem. + * + * (C) 1992 Eric Youngdale Modified for ISO9660 filesystem. + * + * (C) 1991 Linus Torvalds - minix filesystem + * + * affs regular file handling primitives + */ + +#include <asm/uaccess.h> +#include <asm/system.h> +#include <linux/sched.h> +#include <linux/affs_fs.h> +#include <linux/fcntl.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/malloc.h> +#include <linux/stat.h> +#include <linux/locks.h> +#include <linux/dirent.h> +#include <linux/fs.h> +#include <linux/amigaffs.h> +#include <linux/mm.h> +#include <linux/pagemap.h> + +#define MIN(a,b) (((a)<(b))?(a):(b)) +#define MAX(a,b) (((a)>(b))?(a):(b)) + +#if PAGE_SIZE < 4096 +#error PAGE_SIZE must be at least 4096 +#endif + +static long affs_file_read_ofs(struct inode *inode, struct file *filp, char *buf, + unsigned long count); +static long affs_file_write(struct inode *inode, struct file *filp, const char *buf, + unsigned long count); +static long affs_file_write_ofs(struct inode *inode, struct file *filp, const char *buf, + unsigned long count); +static int affs_open_file(struct inode *inode, struct file *filp); +static void affs_release_file(struct inode *inode, struct file *filp); + +static struct file_operations affs_file_operations = { + NULL, /* lseek - default */ + generic_file_read, /* read */ + affs_file_write, /* write */ + NULL, /* readdir - bad */ + NULL, /* select - default */ + NULL, /* ioctl - default */ + generic_file_mmap, /* mmap */ + affs_open_file, /* special open is needed */ + affs_release_file, /* release */ + file_fsync /* brute force, but works */ +}; + +struct inode_operations affs_file_inode_operations = { + &affs_file_operations, /* default file operations */ + NULL, /* create */ + NULL, /* lookup */ + NULL, /* link */ + NULL, /* unlink */ + NULL, /* symlink */ + NULL, /* mkdir */ + NULL, /* rmdir */ + NULL, /* mknod */ + NULL, /* rename */ + NULL, /* readlink */ + NULL, /* follow_link */ + generic_readpage, /* readpage */ + NULL, /* writepage */ + affs_bmap, /* bmap */ + affs_truncate, /* truncate */ + NULL, /* permission */ + NULL /* smap */ +}; + +static struct file_operations affs_file_operations_ofs = { + NULL, /* lseek - default */ + affs_file_read_ofs, /* read */ + affs_file_write_ofs, /* write */ + NULL, /* readdir - bad */ + NULL, /* select - default */ + NULL, /* ioctl - default */ + NULL, /* mmap */ + affs_open_file, /* special open is needed */ + affs_release_file, /* release */ + file_fsync /* brute force, but works */ +}; + +struct inode_operations affs_file_inode_operations_ofs = { + &affs_file_operations_ofs, /* default file operations */ + NULL, /* create */ + NULL, /* lookup */ + NULL, /* link */ + NULL, /* unlink */ + NULL, /* symlink */ + NULL, /* mkdir */ + NULL, /* rmdir */ + NULL, /* mknod */ + NULL, /* rename */ + NULL, /* readlink */ + NULL, /* follow_link */ + NULL, /* readpage */ + NULL, /* writepage */ + NULL, /* bmap */ + affs_truncate, /* truncate */ + NULL, /* permission */ + NULL /* smap */ +}; + +#define AFFS_ISINDEX(x) ((x < 129) || \ + (x < 512 && (x & 1) == 0) || \ + (x < 1024 && (x & 3) == 0) || \ + (x < 2048 && (x & 15) == 0) || \ + (x < 4096 && (x & 63) == 0) || \ + (x < 20480 && (x & 255) == 0) || \ + (x < 36864 && (x & 511) == 0)) + +/* The keys of the extension blocks are stored in a 512-entry + * deep cache. In order to save memory, not every key of later + * extension blocks is stored - the larger the file gets, the + * bigger the holes inbetween. + */ + +static int +seqnum_to_index(int seqnum) +{ + /* All of the first 127 keys are stored */ + if (seqnum < 128) + return seqnum; + seqnum -= 128; + + /* Of the next 384 keys, every 2nd is kept */ + if (seqnum < (192 * 2)) + return 128 + (seqnum >> 1); + seqnum -= 192 * 2; + + /* Every 4th of the next 512 */ + if (seqnum < (128 * 4)) + return 128 + 192 + (seqnum >> 2); + seqnum -= 128 * 4; + + /* Every 16th of the next 1024 */ + if (seqnum < (64 * 16)) + return 128 + 192 + 128 + (seqnum >> 4); + seqnum -= 64 * 16; + + /* Every 64th of the next 2048 */ + if (seqnum < (32 * 64)) + return 128 + 192 + 128 + 64 + (seqnum >> 6); + seqnum -= 32 * 64; + + /* Every 256th of the next 16384 */ + if (seqnum < (64 * 256)) + return 128 + 192 + 128 + 64 + 32 + (seqnum >> 8); + seqnum -= 64 * 256; + + /* Every 512th upto 36479 (1.3 GB with 512 byte blocks). + * Seeking to positions behind this will get slower + * than dead snails nailed to the ground. But if + * someone uses files that large with 512-byte blocks, + * he or she deserves no better. + */ + + if (seqnum > (31 * 512)) + seqnum = 31 * 512; + return 128 + 192 + 128 + 64 + 32 + 64 + (seqnum >> 9); +} + +/* Now the other way round: Calculate the sequence + * number of a extension block of a key at the + * given index in the cache. + */ + +static int +index_to_seqnum(int index) +{ + if (index < 128) + return index; + index -= 128; + if (index < 192) + return 128 + (index << 1); + index -= 192; + if (index < 128) + return 128 + 192 * 2 + (index << 2); + index -= 128; + if (index < 64) + return 128 + 192 * 2 + 128 * 4 + (index << 4); + index -= 64; + if (index < 32) + return 128 + 192 * 2 + 128 * 4 + 64 * 16 + (index << 6); + index -= 32; + if (index < 64) + return 128 + 192 * 2 + 128 * 4 + 64 * 16 + 32 * 64 + (index << 8); + index -= 64; + return 128 + 192 * 2 + 128 * 4 + 64 * 16 + 32 * 64 + 64 * 256 + (index << 9); +} + +static int __inline__ +calc_key(struct inode *inode, int *ext) +{ + int index; + struct key_cache *kc; + + for (index = 0; index < 4; index++) { + kc = &inode->u.affs_i.i_ec->kc[index]; + if (*ext == kc->kc_this_seq) { + return kc->kc_this_key; + } else if (*ext == kc->kc_this_seq + 1) { + if (kc->kc_next_key) + return kc->kc_next_key; + else { + (*ext)--; + return kc->kc_this_key; + } + } + } + index = seqnum_to_index(*ext); + if (index > inode->u.affs_i.i_ec->max_ext) + index = inode->u.affs_i.i_ec->max_ext; + *ext = index_to_seqnum(index); + return inode->u.affs_i.i_ec->ec[index]; +} + +int +affs_bmap(struct inode *inode, int block) +{ + struct buffer_head *bh; + int ext, key, nkey; + int ptype, stype; + int index; + int keycount; + struct key_cache *kc; + struct key_cache *tkc; + struct timeval tv; + __s32 *keyp; + int i; + + pr_debug("AFFS: bmap(%lu,%d)\n",inode->i_ino,block); + + if (block < 0) { + printk("affs_bmap: block < 0\n"); + return 0; + } + if (!inode->u.affs_i.i_ec) { + printk("affs_bmap(): No ext_cache!?\n"); + return 0; + } + + /* Try to find the requested key in the cache. + * In order to speed this up as much as possible, + * the cache line lookup is done in a seperate + * step. + */ + + for (i = 0; i < 4; i++) { + tkc = &inode->u.affs_i.i_ec->kc[i]; + /* Look in any cache if the key is there */ + if (block <= tkc->kc_last && block >= tkc->kc_first) { + return tkc->kc_keys[block - tkc->kc_first]; + } + } + kc = NULL; + tv = xtime; + for (i = 0; i < 4; i++) { + tkc = &inode->u.affs_i.i_ec->kc[i]; + if (tkc->kc_lru_time.tv_sec > tv.tv_sec) + continue; + if (tkc->kc_lru_time.tv_sec < tv.tv_sec || + tkc->kc_lru_time.tv_usec < tv.tv_usec) { + kc = tkc; + tv = tkc->kc_lru_time; + } + } + if (!kc) /* Really shouldn't happen */ + kc = tkc; + kc->kc_lru_time = xtime; + keyp = kc->kc_keys; + kc->kc_first = block; + kc->kc_last = -1; + keycount = AFFS_KCSIZE; + + /* Calculate sequence number of the extension block where the + * number of the requested block is stored. 0 means it's in + * the file header. + */ + + ext = block / AFFS_I2HSIZE(inode); + key = calc_key(inode,&ext); + block -= ext * AFFS_I2HSIZE(inode); + + for (;;) { + bh = affs_bread(inode->i_dev,key,AFFS_I2BSIZE(inode)); + if (!bh) + return 0; + index = seqnum_to_index(ext); + if (index > inode->u.affs_i.i_ec->max_ext && + (affs_checksum_block(AFFS_I2BSIZE(inode),bh->b_data,&ptype,&stype) || + (ptype != T_SHORT && ptype != T_LIST) || stype != ST_FILE)) { + affs_brelse(bh); + return 0; + } + nkey = htonl(FILE_END(bh->b_data,inode)->extension); + if (block < AFFS_I2HSIZE(inode)) { + /* Fill cache as much as possible */ + if (keycount) { + kc->kc_first = ext * AFFS_I2HSIZE(inode) + block; + keycount = keycount < AFFS_I2HSIZE(inode) - block ? keycount : + AFFS_I2HSIZE(inode) - block; + for (i = 0; i < keycount; i++) + kc->kc_keys[i] = htonl(AFFS_BLOCK(bh->b_data,inode,block + i)); + kc->kc_last = kc->kc_first + i - 1; + } + break; + } + block -= AFFS_I2HSIZE(inode); + affs_brelse(bh); + ext++; + if (index > inode->u.affs_i.i_ec->max_ext && AFFS_ISINDEX(ext)) { + inode->u.affs_i.i_ec->ec[index] = nkey; + inode->u.affs_i.i_ec->max_ext = index; + } + key = nkey; + } + kc->kc_this_key = key; + kc->kc_this_seq = ext; + kc->kc_next_key = nkey; + key = htonl(AFFS_BLOCK(bh->b_data,inode,block)); + affs_brelse(bh); + return key; +} + +struct buffer_head * +affs_getblock(struct inode *inode, int block) +{ + struct buffer_head *bh; + struct buffer_head *ebh; + struct buffer_head *pbh; + struct key_cache *kc; + int key, nkey; + int ext; + int cf, j, pt; + int index; + int ofs; + + pr_debug("AFFS: getblock(%lu,%d)\n",inode->i_ino,block); + + if (block < 0) + return NULL; + + /* Writers always use cache line 3. In almost all cases, files + * will be written by only one process at the same time, and + * they also will be written in strict sequential order. Thus + * there is not much sense in looking whether the key of the + * requested block is available - it won't be there. + */ + kc = &inode->u.affs_i.i_ec->kc[3]; + ofs = inode->i_sb->u.affs_sb.s_flags & SF_OFS; + ext = block / AFFS_I2HSIZE(inode); + key = calc_key(inode,&ext); + block -= ext * AFFS_I2HSIZE(inode); + pt = ext ? T_LIST : T_SHORT; + pbh = NULL; + + for (;;) { + bh = affs_bread(inode->i_dev,key,AFFS_I2BSIZE(inode)); + if (!bh) + return NULL; + if (affs_checksum_block(AFFS_I2BSIZE(inode),bh->b_data,&cf,&j) || + cf != pt || j != ST_FILE) { + printk("AFFS: getblock(): inode %d is not a valid %s\n",key, + pt == T_SHORT ? "file header" : "extension block"); + affs_brelse(bh); + return NULL; + } + j = htonl(((struct file_front *)bh->b_data)->block_count); + cf = 0; + while (j < AFFS_I2HSIZE(inode) && j <= block) { + if (ofs && !pbh && inode->u.affs_i.i_lastblock >= 0) { + if (j > 0) + pbh = affs_bread(inode->i_dev,ntohl(AFFS_BLOCK(bh->b_data,inode,j - 1)), + AFFS_I2BSIZE(inode)); + else + pbh = affs_getblock(inode,inode->u.affs_i.i_lastblock); + if (!pbh) { + printk("AFFS: getblock(): cannot get last block in file\n"); + break; + } + } + nkey = affs_new_data(inode); + if (!nkey) + break; + lock_super(inode->i_sb); + if (AFFS_BLOCK(bh->b_data,inode,j)) { + unlock_super(inode->i_sb); + printk("AFFS: getblock(): block already allocated\n"); + affs_free_block(inode->i_sb,nkey); + j++; + continue; + } + unlock_super(inode->i_sb); + AFFS_BLOCK(bh->b_data,inode,j) = ntohl(nkey); + if (ofs) { + ebh = affs_bread(inode->i_dev,nkey,AFFS_I2BSIZE(inode)); + if (!ebh) { + printk("AFFS: getblock(): cannot get block %d\n",nkey); + affs_free_block(inode->i_sb,nkey); + AFFS_BLOCK(bh->b_data,inode,j) = 0; + break; + } + inode->u.affs_i.i_lastblock++; + DATA_FRONT(ebh)->primary_type = ntohl(T_DATA); + DATA_FRONT(ebh)->header_key = ntohl(inode->i_ino); + DATA_FRONT(ebh)->sequence_number = ntohl(inode->u.affs_i.i_lastblock + 1); + if (pbh) { + DATA_FRONT(pbh)->data_size = ntohl(AFFS_I2BSIZE(inode) - 24); + DATA_FRONT(pbh)->next_data = ntohl(nkey); + affs_fix_checksum(AFFS_I2BSIZE(inode),pbh->b_data,5); + mark_buffer_dirty(pbh,0); + mark_buffer_dirty(ebh,0); + affs_brelse(pbh); + } + pbh = ebh; + } + j++; + cf = 1; + } + if (cf) { + if (pt == T_SHORT) + ((struct file_front *)bh->b_data)->first_data = + AFFS_BLOCK(bh->b_data,inode,0); + ((struct file_front *)bh->b_data)->block_count = ntohl(j); + affs_fix_checksum(AFFS_I2BSIZE(inode),bh->b_data,5); + mark_buffer_dirty(bh,1); + } + + if (block < j) { + if (pbh) + affs_brelse(pbh); + break; + } + if (j < AFFS_I2HSIZE(inode)) { + affs_brelse(bh); + return NULL; + } + + block -= AFFS_I2HSIZE(inode); + key = htonl(FILE_END(bh->b_data,inode)->extension); + if (!key) { + key = affs_new_header(inode); + if (!key) { + affs_brelse(bh); + return NULL; + } + ebh = affs_bread(inode->i_dev,key,AFFS_I2BSIZE(inode)); + if (!ebh) { + affs_free_block(inode->i_sb,key); + return NULL; + } + ((struct file_front *)ebh->b_data)->primary_type = ntohl(T_LIST); + ((struct file_front *)ebh->b_data)->own_key = ntohl(key); + FILE_END(ebh->b_data,inode)->secondary_type = ntohl(ST_FILE); + FILE_END(ebh->b_data,inode)->parent = ntohl(inode->i_ino); + affs_fix_checksum(AFFS_I2BSIZE(inode),ebh->b_data,5); + FILE_END(bh->b_data,inode)->extension = ntohl(key); + affs_fix_checksum(AFFS_I2BSIZE(inode),bh->b_data,5); + mark_buffer_dirty(bh,1); + affs_brelse(bh); + bh = ebh; + } + affs_brelse(bh); + pt = T_LIST; + ext++; + if ((index = seqnum_to_index(ext)) > inode->u.affs_i.i_ec->max_ext && + AFFS_ISINDEX(ext) && inode->u.affs_i.i_ec) { + inode->u.affs_i.i_ec->ec[index] = key; + inode->u.affs_i.i_ec->max_ext = index; + } + } + kc->kc_this_key = key; + kc->kc_this_seq = ext; + kc->kc_next_key = htonl(FILE_END(bh->b_data,inode)->extension); + key = htonl(AFFS_BLOCK(bh->b_data,inode,block)); + affs_brelse(bh); + if (!key) + return NULL; + + return affs_bread(inode->i_dev,key,AFFS_I2BSIZE(inode)); +} + +/* This could be made static, regardless of what the former comment said. + * You cannot directly read affs directories. + */ + +static long +affs_file_read_ofs(struct inode *inode, struct file *filp, char *buf, unsigned long count) +{ + char *start; + int left, offset, size, sector; + int blocksize; + struct buffer_head *bh; + void *data; + + pr_debug("AFFS: file_read_ofs(ino=%lu,pos=%lu,%d)\n",inode->i_ino,(long)filp->f_pos,count); + + if (!inode) { + printk("affs_file_read: inode = NULL\n"); + return -EINVAL; + } + blocksize = AFFS_I2BSIZE(inode) - 24; + if (!(S_ISREG(inode->i_mode))) { + pr_debug("affs_file_read: mode = %07o\n",inode->i_mode); + return -EINVAL; + } + if (filp->f_pos >= inode->i_size || count <= 0) + return 0; + + start = buf; + for (;;) { + left = MIN (inode->i_size - filp->f_pos,count - (buf - start)); + if (!left) + break; + sector = affs_bmap(inode,(__u32)filp->f_pos / blocksize); + if (!sector) + break; + offset = (__u32)filp->f_pos % blocksize; + bh = affs_bread(inode->i_dev,sector,AFFS_I2BSIZE(inode)); + if (!bh) + break; + data = bh->b_data + 24; + size = MIN(blocksize - offset,left); + filp->f_pos += size; + copy_to_user(buf,data + offset,size); + buf += size; + affs_brelse(bh); + } + if (start == buf) + return -EIO; + return buf - start; +} + +static long +affs_file_write(struct inode *inode, struct file *filp, const char *buf, unsigned long count) +{ + off_t pos; + int written; + int c; + int blocksize; + struct buffer_head *bh; + struct inode *ino; + char *p; + + pr_debug("AFFS: file_write(ino=%lu,pos=%lu,count=%d)\n",inode->i_ino, + (unsigned long)filp->f_pos,count); + + ino = NULL; + if (!inode) { + printk("AFFS: file_write(): inode=NULL\n"); + return -EINVAL; + } + if (inode->u.affs_i.i_original) { + ino = iget(inode->i_sb,inode->u.affs_i.i_original); + if (!ino) { + printk("AFFS: could not follow link from inode %lu to %d\n", + inode->i_ino,inode->u.affs_i.i_original); + return -EINVAL; + } + inode = ino; + } + if (!S_ISREG(inode->i_mode)) { + printk("AFFS: file_write(): mode=%07o\n",inode->i_mode); + iput(inode); + return -EINVAL; + } + if (filp->f_flags & O_APPEND) { + pos = inode->i_size; + } else + pos = filp->f_pos; + written = 0; + blocksize = AFFS_I2BSIZE(inode); + + while (written < count) { + bh = affs_getblock(inode,pos / blocksize); + if (!bh) { + if (!written) + written = -ENOSPC; + break; + } + c = blocksize - (pos % blocksize); + if (c > count - written) + c = count - written; + if (c != blocksize && !buffer_uptodate(bh)) { + ll_rw_block(READ,1,&bh); + wait_on_buffer(bh); + if (!buffer_uptodate(bh)) { + affs_brelse(bh); + if (!written) + written = -EIO; + break; + } + } + p = (pos % blocksize) + bh->b_data; + copy_from_user(p,buf,c); + update_vm_cache(inode,pos,p,c); + mark_buffer_uptodate(bh,1); + mark_buffer_dirty(bh,0); + affs_brelse(bh); + pos += c; + written += c; + buf += c; + } + if (pos > inode->i_size) + inode->i_size = pos; + inode->i_mtime = inode->i_ctime = CURRENT_TIME; + filp->f_pos = pos; + inode->i_dirt = 1; + iput(ino); + return written; +} + +static long +affs_file_write_ofs(struct inode *inode, struct file *filp, const char *buf, unsigned long count) +{ + off_t pos; + int written; + int c; + int blocksize; + struct buffer_head *bh; + struct inode *ino; + char *p; + + pr_debug("AFFS: file_write_ofs(ino=%lu,pos=%lu,count=%d)\n",inode->i_ino, + (unsigned long)filp->f_pos,count); + + if (!inode) { + printk("AFFS: file_write_ofs(): inode=NULL\n"); + return -EINVAL; + } + ino = NULL; + if (inode->u.affs_i.i_original) { + ino = iget(inode->i_sb,inode->u.affs_i.i_original); + if (!ino) { + printk("AFFS: could not follow link from inode %lu to %d\n", + inode->i_ino,inode->u.affs_i.i_original); + return -EINVAL; + } + inode = ino; + } + if (!S_ISREG(inode->i_mode)) { + printk("AFFS: file_write_ofs(): mode=%07o\n",inode->i_mode); + iput(inode); + return -EINVAL; + } + if (filp->f_flags & O_APPEND) + pos = inode->i_size; + else + pos = filp->f_pos; + + bh = NULL; + blocksize = AFFS_I2BSIZE(inode) - 24; + written = 0; + while (written < count) { + bh = affs_getblock(inode,pos / blocksize); + if (!bh) { + if (!written) + written = -ENOSPC; + break; + } + c = blocksize - (pos % blocksize); + if (c > count - written) + c = count - written; + if (c != blocksize && !buffer_uptodate(bh)) { + ll_rw_block(READ,1,&bh); + wait_on_buffer(bh); + if (!buffer_uptodate(bh)) { + affs_brelse(bh); + if (!written) + written = -EIO; + break; + } + } + p = (pos % blocksize) + bh->b_data + 24; + copy_from_user(p,buf,c); + update_vm_cache(inode,pos,p,c); + + pos += c; + buf += c; + written += c; + DATA_FRONT(bh)->data_size = ntohl(htonl(DATA_FRONT(bh)->data_size) + c); + affs_fix_checksum(AFFS_I2BSIZE(inode),bh->b_data,5); + mark_buffer_uptodate(bh,1); + mark_buffer_dirty(bh,0); + affs_brelse(bh); + } + if (pos > inode->i_size) + inode->i_size = pos; + filp->f_pos = pos; + inode->i_mtime = inode->i_ctime = CURRENT_TIME; + inode->i_dirt = 1; + iput(ino); + return written; +} + +void +affs_truncate(struct inode *inode) +{ + struct buffer_head *bh; + struct buffer_head *ebh; + struct inode *ino; + struct affs_zone *zone; + int first; + int block; + int key; + int *keyp; + int ekey; + int ptype, stype; + int freethis; + int blocksize; + int rem; + int ext; + + pr_debug("AFFS: file_truncate(inode=%ld,size=%lu)\n",inode->i_ino,inode->i_size); + + ino = NULL; + if (inode->u.affs_i.i_original) { + ino = iget(inode->i_sb,inode->u.affs_i.i_original); + if (!ino) { + printk("AFFS: truncate(): cannot follow link from %lu to %u\n", + inode->i_ino,inode->u.affs_i.i_original); + return; + } + inode = ino; + } + blocksize = AFFS_I2BSIZE(inode) - ((inode->i_sb->u.affs_sb.s_flags & SF_OFS) ? 24 : 0); + first = (inode->i_size + blocksize - 1) / blocksize; + if (inode->u.affs_i.i_lastblock < first - 1) { + bh = affs_getblock(inode,first - 1); + + while (inode->u.affs_i.i_pa_cnt) { /* Free any preallocated blocks */ + affs_free_block(inode->i_sb, + inode->u.affs_i.i_data[inode->u.affs_i.i_pa_next++]); + inode->u.affs_i.i_pa_next &= AFFS_MAX_PREALLOC - 1; + inode->u.affs_i.i_pa_cnt--; + } + if (inode->u.affs_i.i_zone) { + lock_super(inode->i_sb); + zone = &inode->i_sb->u.affs_sb.s_zones[inode->u.affs_i.i_zone]; + if (zone->z_ino == inode->i_ino) + zone->z_ino = 0; + unlock_super(inode->i_sb); + } + if (!bh) { + printk("AFFS: truncate(): Cannot extend file\n"); + inode->i_size = blocksize * (inode->u.affs_i.i_lastblock + 1); + } else if (inode->i_sb->u.affs_sb.s_flags & SF_OFS) { + rem = inode->i_size % blocksize; + DATA_FRONT(bh)->data_size = ntohl(rem ? rem : blocksize); + affs_fix_checksum(AFFS_I2BSIZE(inode),bh->b_data,5); + mark_buffer_dirty(bh,0); + } + affs_brelse(bh); + iput(ino); + return; + } + ekey = inode->i_ino; + ext = 0; + + while (ekey) { + if (!(bh = affs_bread(inode->i_dev,ekey,AFFS_I2BSIZE(inode)))) { + printk("AFFS: truncate(): Can't read block %d\n",ekey); + break; + } + ptype = htonl(((struct file_front *)bh->b_data)->primary_type); + stype = htonl(FILE_END(bh->b_data,inode)->secondary_type); + if (ekey == inode->i_ino && ptype == T_SHORT && stype == ST_LINKFILE && + LINK_END(bh->b_data,inode)->original == 0) { + pr_debug("AFFS: truncate(): dumping link\n"); + affs_brelse(bh); + break; + } + if (stype != ST_FILE || (ptype != T_SHORT && ptype != T_LIST)) { + printk("AFFS: truncate(): bad block (ptype=%d, stype=%d)\n", + ptype,stype); + affs_brelse(bh); + break; + } + /* Do not throw away file header */ + freethis = first == 0 && ekey != inode->i_ino; + for ( block = first; block < AFFS_I2HSIZE(inode); block++) { + keyp = &AFFS_BLOCK(bh->b_data,inode,block); + key = htonl(*keyp); + if (key) { + *keyp = 0; + affs_free_block(inode->i_sb,key); + } else { + block = AFFS_I2HSIZE(inode); + break; + } + } + keyp = &GET_END_PTR(struct file_end,bh->b_data,AFFS_I2BSIZE(inode))->extension; + key = htonl(*keyp); + if (first <= AFFS_I2HSIZE(inode)) { + ((struct file_front *)bh->b_data)->block_count = htonl(first); + first = 0; + *keyp = 0; + if ((inode->i_sb->u.affs_sb.s_flags & SF_OFS) && first > 0) { + block = htonl(AFFS_BLOCK(bh->b_data,inode,first - 1)); + if ((ebh = affs_bread(inode->i_dev,block,AFFS_I2BSIZE(inode)))) { + if(!(affs_checksum_block(AFFS_I2BSIZE(inode),ebh->b_data, + &ptype,NULL))) { + rem = inode->i_size % blocksize; + rem = ntohl(rem ? blocksize : rem); + ((struct data_front *)ebh->b_data)->data_size = rem; + ((struct data_front *)ebh->b_data)->next_data = 0; + affs_fix_checksum(AFFS_I2BSIZE(inode),ebh->b_data,5); + mark_buffer_dirty(ebh,1); + } + affs_brelse(ebh); + } + } + } else { + first -= AFFS_I2HSIZE(inode); + } + if (freethis) { /* Don't bother fixing checksum */ + affs_brelse(bh); + affs_free_block(inode->i_sb,ekey); + } else { + affs_fix_checksum(AFFS_I2BSIZE(inode),bh->b_data,5); + mark_buffer_dirty(bh,1); + affs_brelse(bh); + } + ekey = key; + } + inode->u.affs_i.i_lastblock = ((inode->i_size + blocksize - 1) / blocksize) - 1; + + /* Invalidate cache */ + if (inode->u.affs_i.i_ec) { + inode->u.affs_i.i_ec->max_ext = 0; + for (key = 0; key < 3; key++) { + inode->u.affs_i.i_ec->kc[key].kc_next_key = 0; + inode->u.affs_i.i_ec->kc[key].kc_last = -1; + } + } + + iput(ino); +} + +static int +affs_open_file(struct inode *inode, struct file *filp) +{ + int error; + int key; + int i; + + pr_debug("AFFS: open_file(ino=%lu)\n",inode->i_ino); + + error = 0; + inode->u.affs_i.i_cache_users++; + lock_super(inode->i_sb); + if (!inode->u.affs_i.i_ec) { + inode->u.affs_i.i_ec = (struct ext_cache *)get_free_page(GFP_KERNEL); + if (!inode->u.affs_i.i_ec) { + printk("AFFS: cache allocation failed\n"); + error = ENOMEM; + } else { + /* We only have to initialize non-zero values. + * get_free_page() zeroed the page already. + */ + key = inode->u.affs_i.i_original ? inode->u.affs_i.i_original : inode->i_ino; + inode->u.affs_i.i_ec->ec[0] = key; + for (i = 0; i < 4; i++) { + inode->u.affs_i.i_ec->kc[i].kc_this_key = key; + inode->u.affs_i.i_ec->kc[i].kc_last = -1; + } + } + } + unlock_super(inode->i_sb); + + return error; +} + +static void +affs_release_file(struct inode *inode, struct file *filp) +{ + struct affs_zone *zone; + + pr_debug("AFFS: release_file(ino=%lu)\n",inode->i_ino); + + if (filp->f_mode & 2) { /* Free preallocated blocks */ + while (inode->u.affs_i.i_pa_cnt) { + affs_free_block(inode->i_sb, + inode->u.affs_i.i_data[inode->u.affs_i.i_pa_next++]); + inode->u.affs_i.i_pa_next &= AFFS_MAX_PREALLOC - 1; + inode->u.affs_i.i_pa_cnt--; + } + if (inode->u.affs_i.i_zone) { + lock_super(inode->i_sb); + zone = &inode->i_sb->u.affs_sb.s_zones[inode->u.affs_i.i_zone]; + if (zone->z_ino == inode->i_ino) + zone->z_ino = 0; + unlock_super(inode->i_sb); + } + } + lock_super(inode->i_sb); + if (--inode->u.affs_i.i_cache_users == 0) { + if (inode->u.affs_i.i_ec) { + free_page((unsigned long)inode->u.affs_i.i_ec); + inode->u.affs_i.i_ec = NULL; + } + } + unlock_super(inode->i_sb); +} diff --git a/fs/affs/inode.c b/fs/affs/inode.c new file mode 100644 index 000000000..b44842705 --- /dev/null +++ b/fs/affs/inode.c @@ -0,0 +1,1013 @@ +/* + * linux/fs/affs/inode.c + * + * (c) 1996 Hans-Joachim Widmaier - Rewritten + * + * (C) 1993 Ray Burr - Modified for Amiga FFS filesystem. + * + * (C) 1992 Eric Youngdale Modified for ISO9660 filesystem. + * + * (C) 1991 Linus Torvalds - minix filesystem + */ + +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/malloc.h> +#include <linux/stat.h> +#include <linux/sched.h> +#include <linux/affs_fs.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/string.h> +#include <linux/locks.h> +#include <linux/errno.h> +#include <linux/genhd.h> +#include <linux/amigaffs.h> +#include <linux/major.h> +#include <linux/blkdev.h> +#include <asm/system.h> +#include <asm/uaccess.h> + +extern int *blk_size[]; +extern struct timezone sys_tz; + +#define MIN(a,b) (((a)<(b))?(a):(b)) + +void +affs_put_super(struct super_block *sb) +{ + int i; + + pr_debug("affs_put_super()\n"); + + lock_super(sb); + for (i = 0; i < sb->u.affs_sb.s_bm_count; i++) + affs_brelse(sb->u.affs_sb.s_bitmap[i].bm_bh); + if (!(sb->s_flags & MS_RDONLY)) { + ROOT_END_S(sb->u.affs_sb.s_root_bh->b_data,sb)->bm_flag = htonl(1); + secs_to_datestamp(CURRENT_TIME, + &ROOT_END_S(sb->u.affs_sb.s_root_bh->b_data,sb)->disk_altered); + affs_fix_checksum(sb->s_blocksize,sb->u.affs_sb.s_root_bh->b_data,5); + mark_buffer_dirty(sb->u.affs_sb.s_root_bh,1); + } + + if (sb->u.affs_sb.s_flags & SF_PREFIX) + kfree(sb->u.affs_sb.s_prefix); + kfree(sb->u.affs_sb.s_bitmap); + affs_brelse(sb->u.affs_sb.s_root_bh); + + /* I'm not happy with this. It would be better to save the previous + * value of this devices blksize_size[][] in the super block and + * restore it here, but with the affs superblock being quite large + * already ... + */ + set_blocksize(sb->s_dev,BLOCK_SIZE); + + sb->s_dev = 0; + unlock_super(sb); + MOD_DEC_USE_COUNT; + return; +} + +static void +affs_write_super(struct super_block *sb) +{ + int i, clean = 2; + + if (!(sb->s_flags & MS_RDONLY)) { + lock_super(sb); + for (i = 0, clean = 1; i < sb->u.affs_sb.s_bm_count; i++) { + if (sb->u.affs_sb.s_bitmap[i].bm_bh) { + if (buffer_dirty(sb->u.affs_sb.s_bitmap[i].bm_bh)) { + clean = 0; + break; + } + } + } + unlock_super(sb); + ROOT_END_S(sb->u.affs_sb.s_root_bh->b_data,sb)->bm_flag = htonl(clean); + secs_to_datestamp(CURRENT_TIME, + &ROOT_END_S(sb->u.affs_sb.s_root_bh->b_data,sb)->disk_altered); + affs_fix_checksum(sb->s_blocksize,sb->u.affs_sb.s_root_bh->b_data,5); + mark_buffer_dirty(sb->u.affs_sb.s_root_bh,1); + sb->s_dirt = !clean; /* redo until bitmap synced */ + } else + sb->s_dirt = 0; + + pr_debug("AFFS: write_super() at %d, clean=%d\n",CURRENT_TIME,clean); +} + +static struct super_operations affs_sops = { + affs_read_inode, + affs_notify_change, + affs_write_inode, + affs_put_inode, + affs_put_super, + affs_write_super, + affs_statfs, + NULL /* remount */ +}; + +int +affs_parent_ino(struct inode *dir) +{ + int root_ino = (dir->i_sb->u.affs_sb.s_root_block); + + if (!S_ISDIR (dir->i_mode)) { + printk ("affs_parent_ino: argument is not a directory\n"); + return root_ino; + } + if (dir->i_ino == root_ino) + return root_ino; + return dir->u.affs_i.i_parent; +} + +static int +parse_options(char *options, uid_t *uid, gid_t *gid, int *mode, int *reserved, int *root, + int *blocksize, char **prefix, char *volume, unsigned long *mount_opts) +{ + char *this_char, *value; + int f; + + /* Fill in defaults */ + + *uid = 0; + *gid = 0; + *reserved = 2; + *root = -1; + *blocksize = -1; + *prefix = "/"; + volume[0] = ':'; + volume[1] = 0; + *mount_opts = 0; + if (!options) + return 1; + for (this_char = strtok(options,","); this_char; this_char = strtok(NULL,",")) { + f = 0; + if ((value = strchr(this_char,'=')) != NULL) + *value++ = 0; + if (!strcmp(this_char,"protect")) { + if (value) { + printk("AFFS: option protect does not take an argument\n"); + return 0; + } + *mount_opts |= SF_IMMUTABLE; + } + else if (!strcmp(this_char,"verbose")) { + if (value) { + printk("AFFS: option verbose does not take an argument\n"); + return 0; + } + *mount_opts |= SF_VERBOSE; + } + else if ((f = !strcmp(this_char,"uid")) || !strcmp(this_char,"setuid")) { + if (!value) + *uid = current->uid; + else if (!*value) { + printk("AFFS: argument for uid option missing\n"); + return 0; + } else { + *uid = simple_strtoul(value,&value,0); + if (*value) + return 0; + if (!f) + *mount_opts |= SF_SETUID; + } + } + else if ((f = !strcmp(this_char,"gid")) || !strcmp(this_char,"setgid")) { + if (!value) + *gid = current->gid; + else if (!*value) { + printk("AFFS: argument for gid option missing\n"); + return 0; + } else { + *gid = simple_strtoul(value,&value,0); + if (*value) + return 0; + if (!f) + *mount_opts |= SF_SETGID; + } + } + else if (!strcmp(this_char,"prefix")) { + if (!value) { + printk("AFFS: The prefix option requires an argument\n"); + return 0; + } + *prefix = kmalloc(strlen(value) + 1,GFP_KERNEL); + if (!*prefix) + return 0; + strcpy(*prefix,value); + *mount_opts |= SF_PREFIX; + } + else if (!strcmp(this_char,"volume")) { + if (!value) { + printk("AFFS: The volume option requires an argument\n"); + return 0; + } + if (strlen(value) > 30) + value[30] = 0; + strcpy(volume,value); + } + else if (!strcmp(this_char,"mode")) { + if (!value || !*value) { + printk("AFFS: The mode option requires an argument\n"); + return 0; + } + *mode = simple_strtoul(value,&value,8) & 0777; + if (*value) + return 0; + *mount_opts |= SF_SETMODE; + } + else if (!strcmp(this_char,"reserved")) { + if (!value || !*value) { + printk("AFFS: The reserved option requires an argument\n"); + return 0; + } + *reserved = simple_strtoul(value,&value,0); + if (*value) + return 0; + } + else if (!strcmp(this_char,"root")) { + if (!value || !*value) { + printk("AFFS: The root option requires an argument\n"); + return 0; + } + *root = simple_strtoul(value,&value,0); + if (*value) + return 0; + } + else if (!strcmp(this_char,"bs")) { + if (!value || !*value) { + printk("AFFS: The bs option requires an argument\n"); + return 0; + } + *blocksize = simple_strtoul(value,&value,0); + if (*value) + return 0; + if (*blocksize != 512 && *blocksize != 1024 && *blocksize != 2048 + && *blocksize != 4096) { + printk ("AFFS: Invalid blocksize (512, 1024, 2048, 4096 allowed).\n"); + return 0; + } + } + /* Silently ignore the quota options */ + else if (!strcmp (this_char, "grpquota") + || !strcmp (this_char, "noquota") + || !strcmp (this_char, "quota") + || !strcmp (this_char, "usrquota")) + ; + else { + printk("AFFS: Unrecognized mount option %s\n", this_char); + return 0; + } + } + return 1; +} + +/* This function definitely needs to be split up. Some fine day I'll + * hopefully have the guts to do so. Until then: sorry for the mess. + */ + +struct super_block * +affs_read_super(struct super_block *s,void *data, int silent) +{ + struct buffer_head *bh = NULL; + struct buffer_head *bb; + kdev_t dev = s->s_dev; + int root_block; + int size; + __u32 chksum; + __u32 *bm; + int ptype, stype; + int mapidx; + int num_bm; + int i, j; + int key; + int blocksize; + uid_t uid; + gid_t gid; + int reserved; + int az_no; + unsigned long mount_flags; + unsigned long offset; + + pr_debug("affs_read_super(%s)\n",data ? (const char *)data : "no options"); + + MOD_INC_USE_COUNT; + + if (!parse_options(data,&uid,&gid,&i,&reserved,&root_block, + &blocksize,&s->u.affs_sb.s_prefix,s->u.affs_sb.s_volume,&mount_flags)) { + s->s_dev = 0; + printk("AFFS: error parsing options.\n"); + MOD_DEC_USE_COUNT; + return NULL; + } + lock_super(s); + + /* Get the size of the device in 512-byte blocks. + * If we later see that the partition uses bigger + * blocks, we will have to change it. + */ + + size = blksize_size[MAJOR(dev)][MINOR(dev)]; + size = (size ? size : BLOCK_SIZE) / 512 * blk_size[MAJOR(dev)][MINOR(dev)]; + + s->u.affs_sb.s_bitmap = NULL; + s->u.affs_sb.s_root_bh = NULL; + s->u.affs_sb.s_flags = mount_flags; + s->u.affs_sb.s_mode = i; + s->u.affs_sb.s_uid = uid; + s->u.affs_sb.s_gid = gid; + + if (size == 0) { + s->s_dev = 0; + unlock_super(s); + printk("affs_read_super: could not determine device size\n"); + goto out; + } + s->u.affs_sb.s_partition_size = size; + s->u.affs_sb.s_reserved = reserved; + + /* Try to find root block. Its location may depend on the block size. */ + + s->u.affs_sb.s_hashsize = 0; + if (blocksize > 0) { + i = blocksize; + j = blocksize; + } else { + i = 512; + j = 4096; + } + for (blocksize = i, key = 0; blocksize <= j; blocksize <<= 1, size >>= 1) { + if (root_block < 0) + s->u.affs_sb.s_root_block = (reserved + size - 1) / 2; + else + s->u.affs_sb.s_root_block = root_block; + set_blocksize(dev,blocksize); + + /* The root block location that was calculated above is not + * correct if the partition size is an odd number of 512- + * byte blocks, which will be rounded down to a number of + * 1024-byte blocks, and if there were an even number of + * reserved blocks. Ideally, all partition checkers should + * report the real number of blocks of the real blocksize, + * but since this just cannot be done, we have to try to + * find the root block anyways. In the above case, it is one + * block behind the calculated one. So we check this one, too. + */ + for (num_bm = 0; num_bm < 2; num_bm++) { + pr_debug("AFFS: Dev %s - trying bs=%d bytes, root at %d, " + "size=%d blocks, %d reserved\n",kdevname(dev),blocksize, + s->u.affs_sb.s_root_block + num_bm,size,reserved); + bh = affs_bread(dev,s->u.affs_sb.s_root_block + num_bm,blocksize); + if (!bh) { + printk("AFFS: unable to read root block\n"); + goto out; + } + if (!affs_checksum_block(blocksize,bh->b_data,&ptype,&stype) && + ptype == T_SHORT && stype == ST_ROOT) { + s->s_blocksize = blocksize; + s->u.affs_sb.s_hashsize = blocksize / 4 - 56; + s->u.affs_sb.s_root_block += num_bm; + key = 1; + break; + } + } + if (key) + break; + affs_brelse(bh); + bh = NULL; + } + if (!key) { + affs_brelse(bh); + if (!silent) + printk("AFFS: Can't find a valid root block on device %s\n",kdevname(dev)); + goto out; + } + root_block = s->u.affs_sb.s_root_block; + + s->u.affs_sb.s_partition_size = size; + s->s_blocksize_bits = blocksize == 512 ? 9 : + blocksize == 1024 ? 10 : + blocksize == 2048 ? 11 : 12; + + /* Find out which kind of FS we have */ + bb = affs_bread(dev,0,s->s_blocksize); + if (bb) { + chksum = htonl(*(__u32 *)bb->b_data); + + /* Dircache filesystems are compatible with non-dircache ones + * when reading. As long as they aren't supported, writing is + * not recommended. + */ + if ((chksum == FS_DCFFS || chksum == MUFS_DCFFS || chksum == FS_DCOFS + || chksum == MUFS_DCOFS) && !(s->s_flags & MS_RDONLY)) { + printk("AFFS: Dircache FS - mounting %s read only.\n",kdevname(dev)); + s->s_flags |= MS_RDONLY; + } + switch (chksum) { + case MUFS_FS: + case MUFS_INTLFFS: + s->u.affs_sb.s_flags |= SF_MUFS; + /* fall thru */ + case FS_INTLFFS: + s->u.affs_sb.s_flags |= SF_INTL; + break; + case MUFS_DCFFS: + case MUFS_FFS: + s->u.affs_sb.s_flags |= SF_MUFS; + break; + case FS_DCFFS: + case FS_FFS: + break; + case MUFS_OFS: + s->u.affs_sb.s_flags |= SF_MUFS; + /* fall thru */ + case FS_OFS: + s->u.affs_sb.s_flags |= SF_OFS; + break; + case MUFS_DCOFS: + case MUFS_INTLOFS: + s->u.affs_sb.s_flags |= SF_MUFS; + case FS_DCOFS: + case FS_INTLOFS: + s->u.affs_sb.s_flags |= SF_INTL | SF_OFS; + break; + default: + printk("AFFS: Unknown filesystem on device %s: %08X\n", + kdevname(dev),chksum); + affs_brelse(bb); + goto out; + } + affs_brelse(bb); + } else { + printk("AFFS: Can't get boot block.\n"); + goto out; + } + if (mount_flags & SF_VERBOSE) { + chksum = ntohl(chksum); + printk("AFFS: Mounting volume \"%*s\": Type=%.3s\\%c, Blocksize=%d\n", + GET_END_PTR(struct root_end,bh->b_data,blocksize)->disk_name[0], + &GET_END_PTR(struct root_end,bh->b_data,blocksize)->disk_name[1], + (char *)&chksum,((char *)&chksum)[3] + '0',blocksize); + } + + s->s_magic = AFFS_SUPER_MAGIC; + s->s_flags |= MS_NODEV | MS_NOSUID; + + /* Keep super block in cache */ + if (!(s->u.affs_sb.s_root_bh = affs_bread(dev,root_block,s->s_blocksize))) { + printk("AFFS: Can't read root block a second time\n"); + goto out; + } + + /* Allocate space for bitmaps, zones and others */ + + size = s->u.affs_sb.s_partition_size - reserved; + num_bm = (size + s->s_blocksize * 8 - 32 - 1) / (s->s_blocksize * 8 - 32); + az_no = (size + AFFS_ZONE_SIZE - 1) / (AFFS_ZONE_SIZE - 32); + ptype = num_bm * sizeof(struct affs_bm_info) + + az_no * sizeof(struct affs_alloc_zone) + + MAX_ZONES * sizeof(struct affs_zone); + pr_debug("num_bm=%d, az_no=%d, sum=%d\n",num_bm,az_no,ptype); + if (!(s->u.affs_sb.s_bitmap = kmalloc(ptype,GFP_KERNEL))) { + printk("AFFS: Not enough memory.\n"); + goto out; + } + memset(s->u.affs_sb.s_bitmap,0,ptype); + + s->u.affs_sb.s_zones = (struct affs_zone *)&s->u.affs_sb.s_bitmap[num_bm]; + s->u.affs_sb.s_alloc = (struct affs_alloc_zone *)&s->u.affs_sb.s_zones[MAX_ZONES]; + s->u.affs_sb.s_num_az = az_no; + + mapidx = 0; + + if (ROOT_END_S(bh->b_data,s)->bm_flag == 0) { + if (!(s->s_flags & MS_RDONLY)) { + printk("AFFS: Bitmap invalid - mounting %s read only.\n",kdevname(dev)); + s->s_flags |= MS_RDONLY; + } + affs_brelse(bh); + bh = NULL; + goto nobitmap; + } + + /* The following section is ugly, I know. Especially because of the + * reuse of some variables that are not named properly. + */ + + key = root_block; + ptype = s->s_blocksize / 4 - 49; + stype = ptype + 25; + offset = s->u.affs_sb.s_reserved; + az_no = 0; + while (bh) { + bm = (__u32 *)bh->b_data; + for (i = ptype; i < stype && bm[i]; i++, mapidx++) { + if (mapidx >= num_bm) { + printk("AFFS: Not enough bitmap space!?\n"); + goto out; + } + bb = affs_bread(s->s_dev,htonl(bm[i]),s->s_blocksize); + if (bb) { + if (affs_checksum_block(s->s_blocksize,bb->b_data,NULL,NULL) && + !(s->s_flags & MS_RDONLY)) { + printk("AFFS: Bitmap (%d,key=%lu) invalid - " + "mounting %s read only.\n",mapidx,htonl(bm[i]), + kdevname(dev)); + s->s_flags |= MS_RDONLY; + } + /* Mark unused bits in the last word as allocated */ + if (size <= s->s_blocksize * 8 - 32) { /* last bitmap */ + ptype = size / 32 + 1; /* word number */ + key = size & 0x1F; /* used bits */ + if (key) { + chksum = ntohl(0x7FFFFFFF >> (31 - key)); + ((__u32 *)bb->b_data)[ptype] &= chksum; + affs_fix_checksum(s->s_blocksize,bb->b_data,0); + mark_buffer_dirty(bb,1); + } + ptype = (size + 31) & ~0x1F; + size = 0; + s->u.affs_sb.s_flags |= SF_BM_VALID; + } else { + ptype = s->s_blocksize * 8 - 32; + size -= ptype; + } + s->u.affs_sb.s_bitmap[mapidx].bm_firstblk = offset; + s->u.affs_sb.s_bitmap[mapidx].bm_bh = NULL; + s->u.affs_sb.s_bitmap[mapidx].bm_key = htonl(bm[i]); + s->u.affs_sb.s_bitmap[mapidx].bm_count = 0; + offset += ptype; + + for (j = 0; ptype > 0; j++, az_no++, ptype -= key) { + key = MIN(ptype,AFFS_ZONE_SIZE); /* size in bits */ + s->u.affs_sb.s_alloc[az_no].az_size = key / 32; + s->u.affs_sb.s_alloc[az_no].az_free = + affs_count_free_bits(key / 8,bb->b_data + + j * (AFFS_ZONE_SIZE / 8) + 4); + } + affs_brelse(bb); + } else { + printk("AFFS: Can't read bitmap.\n"); + goto out; + } + } + key = htonl(bm[stype]); /* Next block of bitmap pointers */ + ptype = 0; + stype = s->s_blocksize / 4 - 1; + affs_brelse(bh); + if (key) { + if (!(bh = affs_bread(s->s_dev,key,s->s_blocksize))) { + printk("AFFS: Can't read bitmap extension.\n"); + goto out; + } + } else + bh = NULL; + } + if (mapidx != num_bm) { + printk("AFFS: Got only %d bitmap blocks, expected %d\n",mapidx,num_bm); + goto out; + } +nobitmap: + s->u.affs_sb.s_bm_count = mapidx; + + /* set up enough so that it can read an inode */ + + s->s_dev = dev; + s->s_op = &affs_sops; + s->s_mounted = iget(s,root_block); + s->s_dirt = 1; + unlock_super(s); + + if (!(s->s_mounted)) { + s->s_dev = 0; + printk("AFFS: get root inode failed\n"); + MOD_DEC_USE_COUNT; + return NULL; + } + + /* create data zones if the fs is mounted r/w */ + + if (!(s->s_flags & MS_RDONLY)) { + ROOT_END(s->u.affs_sb.s_root_bh->b_data,s->s_mounted)->bm_flag = 0; + secs_to_datestamp(CURRENT_TIME,&ROOT_END(s->u.affs_sb.s_root_bh->b_data, + s->s_mounted)->disk_altered); + affs_fix_checksum(s->s_blocksize,s->u.affs_sb.s_root_bh->b_data,5); + mark_buffer_dirty(s->u.affs_sb.s_root_bh,1); + affs_make_zones(s); + } + + pr_debug("AFFS: s_flags=%lX\n",s->s_flags); + return s; + + out: /* Kick out for various error conditions */ + affs_brelse (bh); + affs_brelse(s->u.affs_sb.s_root_bh); + if (s->u.affs_sb.s_bitmap) + kfree(s->u.affs_sb.s_bitmap); + set_blocksize(dev,BLOCK_SIZE); + s->s_dev = 0; + unlock_super(s); + MOD_DEC_USE_COUNT; + return NULL; +} + +void +affs_statfs(struct super_block *sb, struct statfs *buf, int bufsiz) +{ + int free; + struct statfs tmp; + + pr_debug("AFFS: statfs() partsize=%d, reserved=%d\n",sb->u.affs_sb.s_partition_size, + sb->u.affs_sb.s_reserved); + + free = affs_count_free_blocks(sb); + tmp.f_type = AFFS_SUPER_MAGIC; + tmp.f_bsize = sb->s_blocksize; + tmp.f_blocks = sb->u.affs_sb.s_partition_size - sb->u.affs_sb.s_reserved; + tmp.f_bfree = free; + tmp.f_bavail = free; + tmp.f_files = 0; + tmp.f_ffree = 0; + copy_to_user(buf,&tmp,bufsiz); +} + +void +affs_read_inode(struct inode *inode) +{ + struct buffer_head *bh, *lbh; + struct file_front *file_front; + struct file_end *file_end; + int block; + unsigned long prot; + int ptype, stype; + unsigned short id; + + pr_debug("AFFS: read_inode(%lu)\n",inode->i_ino); + + lbh = NULL; + block = inode->i_ino; + if (!(bh = affs_bread(inode->i_dev,block,AFFS_I2BSIZE(inode)))) { + printk("AFFS: unable to read i-node block %d\n",block); + return; + } + if (affs_checksum_block(AFFS_I2BSIZE(inode),bh->b_data,&ptype,&stype) || ptype != T_SHORT) { + printk("AFFS: read_inode(): checksum or type (ptype=%d) error on inode %d\n", + ptype,block); + affs_brelse(bh); + return; + } + + file_front = (struct file_front *)bh->b_data; + file_end = GET_END_PTR(struct file_end, bh->b_data,AFFS_I2BSIZE(inode)); + prot = (htonl(file_end->protect) & ~0x10) ^ FIBF_OWNER; + + inode->u.affs_i.i_protect = prot; + inode->u.affs_i.i_parent = htonl(file_end->parent); + inode->u.affs_i.i_original = 0; + inode->u.affs_i.i_zone = 0; + inode->u.affs_i.i_hlink = 0; + inode->u.affs_i.i_pa_cnt = 0; + inode->u.affs_i.i_pa_next = 0; + inode->u.affs_i.i_pa_last = 0; + inode->u.affs_i.i_ec = NULL; + inode->u.affs_i.i_cache_users = 0; + inode->u.affs_i.i_lastblock = -1; + inode->i_nlink = 1; + inode->i_mode = 0; + + if (inode->i_sb->u.affs_sb.s_flags & SF_SETMODE) + inode->i_mode = inode->i_sb->u.affs_sb.s_mode; + else + inode->i_mode = prot_to_mode(prot); + + if (inode->i_sb->u.affs_sb.s_flags & SF_SETUID) + inode->i_uid = inode->i_sb->u.affs_sb.s_uid; + else { + id = htons(file_end->owner_uid); + if (inode->i_sb->u.affs_sb.s_flags & SF_MUFS) { + if (id == 0 || id == 0xFFFF) + id ^= ~0; + } + inode->i_uid = id; + } + if (inode->i_sb->u.affs_sb.s_flags & SF_SETGID) + inode->i_gid = inode->i_sb->u.affs_sb.s_gid; + else { + id = htons(file_end->owner_gid); + if (inode->i_sb->u.affs_sb.s_flags & SF_MUFS) { + if (id == 0 || id == 0xFFFF) + id ^= ~0; + } + inode->i_gid = id; + } + + switch (htonl(file_end->secondary_type)) { + case ST_ROOT: + inode->i_uid = inode->i_sb->u.affs_sb.s_uid; + inode->i_gid = inode->i_sb->u.affs_sb.s_gid; + case ST_USERDIR: + if (htonl(file_end->secondary_type) == ST_USERDIR || + inode->i_sb->u.affs_sb.s_flags & SF_SETMODE) { + if (inode->i_mode & S_IRUSR) + inode->i_mode |= S_IXUSR; + if (inode->i_mode & S_IRGRP) + inode->i_mode |= S_IXGRP; + if (inode->i_mode & S_IROTH) + inode->i_mode |= S_IXOTH; + inode->i_mode |= S_IFDIR; + } else + inode->i_mode = S_IRUGO | S_IXUGO | S_IWUSR | S_IFDIR; + inode->i_size = 0; + break; + case ST_LINKDIR: + inode->u.affs_i.i_original = htonl(file_end->original); + inode->u.affs_i.i_hlink = 1; + inode->i_mode |= S_IFDIR; + inode->i_size = 0; + break; + case ST_LINKFILE: + inode->u.affs_i.i_original = htonl(file_end->original); + inode->u.affs_i.i_hlink = 1; + if (!(lbh = affs_bread(inode->i_dev,inode->u.affs_i.i_original, + AFFS_I2BSIZE(inode)))) { + affs_brelse(bh); + printk("AFFS: unable to read i-node block %ld\n",inode->i_ino); + return; + } + file_end = GET_END_PTR(struct file_end,lbh->b_data,AFFS_I2BSIZE(inode)); + case ST_FILE: + inode->i_mode |= S_IFREG; + inode->i_size = htonl(file_end->byte_size); + if (inode->i_sb->u.affs_sb.s_flags & SF_OFS) + block = AFFS_I2BSIZE(inode) - 24; + else + block = AFFS_I2BSIZE(inode); + inode->u.affs_i.i_lastblock = ((inode->i_size + block - 1) / block) - 1; + break; + case ST_SOFTLINK: + inode->i_mode |= S_IFLNK; + inode->i_size = 0; + break; + } + + inode->i_mtime = inode->i_atime = inode->i_ctime + = (htonl(file_end->created.ds_Days) * (24 * 60 * 60) + + htonl(file_end->created.ds_Minute) * 60 + + htonl(file_end->created.ds_Tick) / 50 + + ((8 * 365 + 2) * 24 * 60 * 60)) + + sys_tz.tz_minuteswest * 60; + affs_brelse(bh); + affs_brelse(lbh); + + inode->i_op = NULL; + if (S_ISREG(inode->i_mode)) { + if (inode->i_sb->u.affs_sb.s_flags & SF_OFS) { + inode->i_op = &affs_file_inode_operations_ofs; + } else { + inode->i_op = &affs_file_inode_operations; + } + } else if (S_ISDIR(inode->i_mode)) + inode->i_op = &affs_dir_inode_operations; + else if (S_ISLNK(inode->i_mode)) + inode->i_op = &affs_symlink_inode_operations; +} + +void +affs_write_inode(struct inode *inode) +{ + struct buffer_head *bh; + struct file_end *file_end; + short uid, gid; + + pr_debug("AFFS: write_inode(%lu)\n",inode->i_ino); + + inode->i_dirt = 0; + if (!inode->i_nlink) + return; + if (!(bh = bread(inode->i_dev,inode->i_ino,AFFS_I2BSIZE(inode)))) { + printk("AFFS: Unable to read block of inode %ld on %s\n", + inode->i_ino,kdevname(inode->i_dev)); + return; + } + file_end = GET_END_PTR(struct file_end, bh->b_data,AFFS_I2BSIZE(inode)); + if (file_end->secondary_type == htonl(ST_ROOT)) { + secs_to_datestamp(inode->i_mtime,&ROOT_END(bh->b_data,inode)->disk_altered); + } else { + file_end->protect = ntohl(inode->u.affs_i.i_protect ^ FIBF_OWNER); + file_end->byte_size = ntohl(inode->i_size); + secs_to_datestamp(inode->i_mtime,&file_end->created); + if (!(inode->i_ino == inode->i_sb->u.affs_sb.s_root_block)) { + uid = inode->i_uid; + gid = inode->i_gid; + if (inode->i_sb->u.affs_sb.s_flags & SF_MUFS) { + if (inode->i_uid == 0 || inode->i_uid == 0xFFFF) + uid = inode->i_uid ^ ~0; + if (inode->i_gid == 0 || inode->i_gid == 0xFFFF) + gid = inode->i_gid ^ ~0; + } + if (!(inode->i_sb->u.affs_sb.s_flags & SF_SETUID)) + file_end->owner_uid = ntohs(uid); + if (!(inode->i_sb->u.affs_sb.s_flags & SF_SETGID)) + file_end->owner_gid = ntohs(gid); + } + } + affs_fix_checksum(AFFS_I2BSIZE(inode),bh->b_data,5); + mark_buffer_dirty(bh,1); + brelse(bh); +} + +int +affs_notify_change(struct inode *inode, struct iattr *attr) +{ + int error; + + pr_debug("AFFS: notify_change(%lu,0x%x)\n",inode->i_ino,attr->ia_valid); + + error = inode_change_ok(inode,attr); + if (error) + return error; + + if (((attr->ia_valid & ATTR_UID) && (inode->i_sb->u.affs_sb.s_flags & SF_SETUID)) || + ((attr->ia_valid & ATTR_GID) && (inode->i_sb->u.affs_sb.s_flags & SF_SETGID)) || + ((attr->ia_valid & ATTR_MODE) && + (inode->i_sb->u.affs_sb.s_flags & (SF_SETMODE | SF_IMMUTABLE)))) + error = -EPERM; + + if (error) + return (inode->i_sb->u.affs_sb.s_flags & SF_QUIET) ? 0 : error; + + if (attr->ia_valid & ATTR_MODE) + inode->u.affs_i.i_protect = mode_to_prot(attr->ia_mode); + + inode_setattr(inode,attr); + + return 0; +} + +void +affs_put_inode(struct inode *inode) +{ + pr_debug("AFFS: put_inode(ino=%lu, nlink=%u)\n",inode->i_ino,inode->i_nlink); + if (inode->i_nlink) { + return; + } + inode->i_size = 0; + if (S_ISREG(inode->i_mode) && !inode->u.affs_i.i_hlink) + affs_truncate(inode); + affs_free_block(inode->i_sb,inode->i_ino); + clear_inode(inode); +} + +struct inode * +affs_new_inode(const struct inode *dir) +{ + struct inode *inode; + struct super_block *sb; + int block; + + if (!dir || !(inode = get_empty_inode())) + return NULL; + + sb = dir->i_sb; + inode->i_sb = sb; + inode->i_flags = sb->s_flags; + + if (!(block = affs_new_header((struct inode *)dir))) { + iput(inode); + return NULL; + } + + inode->i_count = 1; + inode->i_nlink = 1; + inode->i_dev = sb->s_dev; + inode->i_uid = current->fsuid; + inode->i_gid = current->fsgid; + inode->i_dirt = 1; + inode->i_ino = block; + inode->i_op = NULL; + inode->i_blocks = 0; + inode->i_size = 0; + inode->i_mode = 0; + inode->i_blksize = 0; + inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME; + + inode->u.affs_i.i_original = 0; + inode->u.affs_i.i_parent = dir->i_ino; + inode->u.affs_i.i_zone = 0; + inode->u.affs_i.i_hlink = 0; + inode->u.affs_i.i_pa_cnt = 0; + inode->u.affs_i.i_pa_next = 0; + inode->u.affs_i.i_pa_last = 0; + inode->u.affs_i.i_ec = NULL; + inode->u.affs_i.i_cache_users = 0; + inode->u.affs_i.i_lastblock = -1; + + insert_inode_hash(inode); + + return inode; +} + +int +affs_add_entry(struct inode *dir, struct inode *link, struct inode *inode, + const char *name, int len, int type) +{ + struct buffer_head *dir_bh; + struct buffer_head *inode_bh; + struct buffer_head *link_bh; + int hash; + + pr_debug("AFFS: add_entry(dir=%lu,inode=%lu,\"%*s\",type=%d\n",dir->i_ino,inode->i_ino, + len,name,type); + + dir_bh = affs_bread(dir->i_dev,dir->i_ino,AFFS_I2BSIZE(dir)); + inode_bh = affs_bread(inode->i_dev,inode->i_ino,AFFS_I2BSIZE(inode)); + link_bh = NULL; + if (!dir_bh || !inode_bh) { + affs_brelse(dir_bh); + affs_brelse(inode_bh); + return -ENOSPC; + } + if (link) { + link_bh = affs_bread(link->i_dev,link->i_ino,AFFS_I2BSIZE(link)); + if (!link_bh) { + affs_brelse(dir_bh); + affs_brelse(inode_bh); + return -EINVAL; + } + } + ((struct dir_front *)inode_bh->b_data)->primary_type = ntohl(T_SHORT); + ((struct dir_front *)inode_bh->b_data)->own_key = ntohl(inode->i_ino); + + if (len > 30) /* truncate name quietly */ + len = 30; + DIR_END(inode_bh->b_data,inode)->dir_name[0] = len; + strncpy(DIR_END(inode_bh->b_data,inode)->dir_name + 1,name,len); + DIR_END(inode_bh->b_data,inode)->secondary_type = ntohl(type); + DIR_END(inode_bh->b_data,inode)->parent = ntohl(dir->i_ino); + hash = affs_hash_name(name,len,AFFS_I2FSTYPE(dir),AFFS_I2HSIZE(dir)); + + lock_super(inode->i_sb); + DIR_END(inode_bh->b_data,inode)->hash_chain = + ((struct dir_front *)dir_bh->b_data)->hashtable[hash]; + ((struct dir_front *)dir_bh->b_data)->hashtable[hash] = ntohl(inode->i_ino); + if (link_bh) { + LINK_END(inode_bh->b_data,inode)->original = ntohl(link->i_ino); + LINK_END(inode_bh->b_data,inode)->link_chain = + FILE_END(link_bh->b_data,link)->link_chain; + FILE_END(link_bh->b_data,link)->link_chain = ntohl(inode->i_ino); + affs_fix_checksum(AFFS_I2BSIZE(link),link_bh->b_data,5); + link->i_version = ++event; + link->i_dirt = 1; + mark_buffer_dirty(link_bh,1); + } + affs_fix_checksum(AFFS_I2BSIZE(inode),inode_bh->b_data,5); + affs_fix_checksum(AFFS_I2BSIZE(dir),dir_bh->b_data,5); + dir->i_version = ++event; + dir->i_mtime = dir->i_atime = dir->i_ctime = CURRENT_TIME; + unlock_super(inode->i_sb); + + dir->i_dirt = 1; + inode->i_dirt = 1; + mark_buffer_dirty(dir_bh,1); + mark_buffer_dirty(inode_bh,1); + affs_brelse(dir_bh); + affs_brelse(inode_bh); + affs_brelse(link_bh); + + return 0; +} + +static struct file_system_type affs_fs_type = { + affs_read_super, + "affs", + 1, + NULL +}; + +int +init_affs_fs(void) +{ + return register_filesystem(&affs_fs_type); +} + +#ifdef MODULE + +int +init_module(void) +{ + int status; + if ((status = init_affs_fs()) == 0) + register_symtab(0); + return status; +} + +void +cleanup_module(void) +{ + unregister_filesystem(&affs_fs_type); +} + +#endif diff --git a/fs/affs/namei.c b/fs/affs/namei.c new file mode 100644 index 000000000..0f4bced43 --- /dev/null +++ b/fs/affs/namei.c @@ -0,0 +1,741 @@ +/* + * linux/fs/affs/namei.c + * + * (c) 1996 Hans-Joachim Widmaier - Rewritten + * + * (C) 1993 Ray Burr - Modified for Amiga FFS filesystem. + * + * (C) 1991 Linus Torvalds - minix filesystem + */ + +#include <linux/sched.h> +#include <linux/affs_fs.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/stat.h> +#include <linux/fcntl.h> +#include <linux/locks.h> +#include <linux/amigaffs.h> +#include <asm/uaccess.h> + +#include <linux/errno.h> + +/* Simple toupper() for DOS\1 */ + +static inline unsigned int +affs_toupper(unsigned int ch) +{ + return ch >= 'a' && ch <= 'z' ? ch -= ('a' - 'A') : ch; +} + +/* International toupper() for DOS\3 */ + +static inline unsigned int +affs_intl_toupper(unsigned int ch) +{ + return (ch >= 'a' && ch <= 'z') || (ch >= 0xE0 + && ch <= 0xFE && ch != 0xF7) ? + ch - ('a' - 'A') : ch; +} + +/* + * NOTE! unlike strncmp, affs_match returns 1 for success, 0 for failure. + */ + +static int +affs_match(const char *name, int len, const char *compare, int dlen, int intl) +{ + if (!compare) + return 0; + + if (len > 30) + len = 30; + if (dlen > 30) + dlen = 30; + + /* "" means "." ---> so paths like "/usr/lib//libc.a" work */ + if (!len && dlen == 1 && compare[0] == '.') + return 1; + if (dlen != len) + return 0; + if (intl) { + while (dlen--) { + if (affs_intl_toupper(*name & 0xFF) != affs_intl_toupper(*compare & 0xFF)) + return 0; + name++; + compare++; + } + } else { + while (dlen--) { + if (affs_toupper(*name & 0xFF) != affs_toupper(*compare & 0xFF)) + return 0; + name++; + compare++; + } + } + return 1; +} + +int +affs_hash_name(const char *name, int len, int intl, int hashsize) +{ + unsigned int i, x; + + if (len > 30) + len = 30; + + x = len; + for (i = 0; i < len; i++) + if (intl) + x = (x * 13 + affs_intl_toupper(name[i] & 0xFF)) & 0x7ff; + else + x = (x * 13 + affs_toupper(name[i] & 0xFF)) & 0x7ff; + + return x % hashsize; +} + +static struct buffer_head * +affs_find_entry(struct inode *dir, const char *name, int namelen, + unsigned long *ino) +{ + struct buffer_head *bh; + int intl; + int key; + + pr_debug("AFFS: find_entry(%.*s)=\n",namelen,name); + + intl = AFFS_I2FSTYPE(dir); + bh = affs_bread(dir->i_dev,dir->i_ino,AFFS_I2BSIZE(dir)); + if (!bh) + return NULL; + + if (affs_match(name,namelen,".",1,intl)) { + *ino = dir->i_ino; + return bh; + } + if (affs_match(name,namelen,"..",2,intl)) { + *ino = affs_parent_ino(dir); + return bh; + } + + key = AFFS_GET_HASHENTRY(bh->b_data,affs_hash_name(name,namelen,intl,AFFS_I2HSIZE(dir))); + + for (;;) { + char *cname; + int cnamelen; + + affs_brelse(bh); + if (key == 0) { + bh = NULL; + break; + } + bh = affs_bread(dir->i_dev,key,AFFS_I2BSIZE(dir)); + if (!bh) + break; + cnamelen = affs_get_file_name(AFFS_I2BSIZE(dir),bh->b_data,&cname); + if (affs_match(name,namelen,cname,cnamelen,intl)) + break; + key = htonl(FILE_END(bh->b_data,dir)->hash_chain); + } + *ino = key; + return bh; +} + +int +affs_lookup(struct inode *dir, const char *name, int len, struct inode **result) +{ + int res; + unsigned long ino; + struct buffer_head *bh; + + pr_debug("AFFS: lookup(%.*s)\n",len,name); + + *result = NULL; + if (!dir) + return -ENOENT; + + res = -ENOENT; + if (S_ISDIR(dir->i_mode)) { + if ((bh = affs_find_entry(dir,name,len,&ino))) { + if (FILE_END(bh->b_data,dir)->original) + ino = htonl(FILE_END(bh->b_data,dir)->original); + affs_brelse(bh); + if ((*result = iget(dir->i_sb,ino))) + res = 0; + else + res = -EACCES; + } + } + iput(dir); + return res; +} + +int +affs_unlink(struct inode *dir, const char *name, int len) +{ + int retval; + struct buffer_head *bh; + unsigned long ino; + struct inode *inode; + + pr_debug("AFFS: unlink(dir=%ld,\"%.*s\")\n",dir->i_ino,len,name); + + bh = NULL; + inode = NULL; + retval = -ENOENT; + if (!(bh = affs_find_entry(dir,name,len,&ino))) { + goto unlink_done; + } + if (!(inode = iget(dir->i_sb,ino))) { + goto unlink_done; + } + if (S_ISDIR(inode->i_mode)) { + retval = -EPERM; + goto unlink_done; + } + + if ((retval = affs_fix_hash_pred(dir,affs_hash_name(name,len,AFFS_I2FSTYPE(dir), + AFFS_I2HSIZE(dir)) + 6,ino, + FILE_END(bh->b_data,dir)->hash_chain))) + goto unlink_done; + + if ((retval = affs_fixup(bh,inode))) + goto unlink_done; + + inode->i_nlink=0; + inode->i_dirt=1; + inode->i_ctime = dir->i_ctime = dir->i_mtime = CURRENT_TIME; + dir->i_version = ++event; + dir->i_dirt=1; +unlink_done: + affs_brelse(bh); + iput(inode); + iput(dir); + return retval; +} + +int +affs_create(struct inode *dir, const char *name, int len, int mode, struct inode **result) +{ + struct inode *inode; + int error; + + pr_debug("AFFS: create(%lu,\"%.*s\",0%o)\n",dir->i_ino,len,name,mode); + + + *result = NULL; + + if (!dir || !dir->i_sb) { + iput(dir); + return -EINVAL; + } + inode = affs_new_inode(dir); + if (!inode) { + iput (dir); + return -ENOSPC; + } + inode->i_mode = mode; + if (dir->i_sb->u.affs_sb.s_flags & SF_OFS) + inode->i_op = &affs_file_inode_operations_ofs; + else + inode->i_op = &affs_file_inode_operations; + + error = affs_add_entry(dir,NULL,inode,name,len,ST_FILE); + if (error) { + iput(dir); + inode->i_nlink = 0; + inode->i_dirt = 1; + iput(inode); + return -ENOSPC; + } + inode->u.affs_i.i_protect = mode_to_prot(inode->i_mode); + + iput(dir); + *result = inode; + + return 0; +} + +int +affs_mkdir(struct inode *dir, const char *name, int len, int mode) +{ + struct inode *inode; + struct buffer_head *bh; + unsigned long i; + int error; + + pr_debug("AFFS: mkdir(%lu,\"%.*s\",0%o)\n",dir->i_ino,len,name,mode); + + if (!dir || !dir->i_sb) { + iput(dir); + return -EINVAL; + } + bh = affs_find_entry(dir,name,len,&i); + if (bh) { + affs_brelse(bh); + iput(dir); + return -EEXIST; + } + inode = affs_new_inode(dir); + if (!inode) { + iput (dir); + return -ENOSPC; + } + inode->i_op = &affs_dir_inode_operations; + error = affs_add_entry(dir,NULL,inode,name,len,ST_USERDIR); + if (error) { + iput(dir); + inode->i_nlink = 0; + inode->i_dirt = 1; + iput(inode); + return error; + } + inode->i_mode = S_IFDIR | (mode & 0777 & ~current->fs->umask); + inode->u.affs_i.i_protect = mode_to_prot(inode->i_mode); + + iput(dir); + iput(inode); + + return 0; +} + +static int +empty_dir(struct buffer_head *bh, int hashsize) +{ + while (--hashsize >= 0) { + if (((struct dir_front *)bh->b_data)->hashtable[hashsize]) + return 0; + } + return 1; +} + +int +affs_rmdir(struct inode *dir, const char *name, int len) +{ + int retval; + unsigned long ino; + struct inode *inode; + struct buffer_head *bh; + + pr_debug("AFFS: rmdir(dir=%lu,\"%.*s\")\n",dir->i_ino,len,name); + + inode = NULL; + retval = -ENOENT; + if (!(bh = affs_find_entry(dir,name,len,&ino))) { + goto rmdir_done; + } + if (!(inode = iget(dir->i_sb,ino))) { + goto rmdir_done; + } + retval = -EPERM; + if (!fsuser() && current->fsuid != inode->i_uid && + current->fsuid != dir->i_uid) + goto rmdir_done; + if (inode->i_dev != dir->i_dev) + goto rmdir_done; + if (inode == dir) /* we may not delete ".", but "../dir" is ok */ + goto rmdir_done; + if (!S_ISDIR(inode->i_mode)) { + retval = -ENOTDIR; + goto rmdir_done; + } + if (!empty_dir(bh,AFFS_I2HSIZE(inode))) { + retval = -ENOTEMPTY; + goto rmdir_done; + } + if (inode->i_count > 1) { + retval = -EBUSY; + goto rmdir_done; + } + if ((retval = affs_fix_hash_pred(dir,affs_hash_name(name,len,AFFS_I2FSTYPE(dir), + AFFS_I2HSIZE(dir)) + 6,ino, + FILE_END(bh->b_data,dir)->hash_chain))) + goto rmdir_done; + + if ((retval = affs_fixup(bh,inode))) + goto rmdir_done; + + inode->i_nlink=0; + inode->i_dirt=1; + inode->i_ctime = dir->i_ctime = dir->i_mtime = CURRENT_TIME; + dir->i_version = ++event; + dir->i_dirt=1; +rmdir_done: + iput(dir); + iput(inode); + affs_brelse(bh); + return retval; +} + +int +affs_symlink(struct inode *dir, const char *name, int len, const char *symname) +{ + struct buffer_head *bh; + struct inode *inode; + char *p; + unsigned long tmp; + int i, maxlen; + char c, lc; + + pr_debug("AFFS: symlink(%lu,\"%.*s\" -> \"%s\")\n",dir->i_ino,len,name,symname); + + maxlen = 4 * AFFS_I2HSIZE(dir) - 1; + inode = affs_new_inode(dir); + if (!inode) { + iput(dir); + return -ENOSPC; + } + inode->i_op = &affs_symlink_inode_operations; + inode->i_mode = S_IFLNK | 0777; + inode->u.affs_i.i_protect = mode_to_prot(inode->i_mode); + bh = affs_bread(inode->i_dev,inode->i_ino,AFFS_I2BSIZE(inode)); + if (!bh) { + iput(dir); + inode->i_nlink = 0; + inode->i_dirt = 1; + iput(inode); + return -EIO; + } + i = 0; + p = ((struct slink_front *)bh->b_data)->symname; + lc = '/'; + if (*symname == '/') { + while (*symname == '/') + symname++; + while (inode->i_sb->u.affs_sb.s_volume[i]) /* Cannot overflow */ + *p++ = inode->i_sb->u.affs_sb.s_volume[i++]; + } + while (i < maxlen && (c = *symname++)) { + if (c == '.' && lc == '/' && *symname == '.' && symname[1] == '/') { + *p++ = '/'; + i++; + symname += 2; + lc = '/'; + } else if (c == '.' && lc == '/' && *symname == '/') { + symname++; + lc = '/'; + } else { + *p++ = c; + lc = c; + i++; + } + if (lc == '/') + while (*symname == '/') + symname++; + } + *p = 0; + mark_buffer_dirty(bh,1); + affs_brelse(bh); + inode->i_dirt = 1; + bh = affs_find_entry(dir,name,len,&tmp); + if (bh) { + inode->i_nlink = 0; + iput(inode); + affs_brelse(bh); + iput(dir); + return -EEXIST; + } + i = affs_add_entry(dir,NULL,inode,name,len,ST_SOFTLINK); + if (i) { + inode->i_nlink = 0; + inode->i_dirt = 1; + iput(inode); + affs_brelse(bh); + iput(dir); + return i; + } + iput(dir); + iput(inode); + + return 0; +} + +int +affs_link(struct inode *oldinode, struct inode *dir, const char *name, int len) +{ + struct inode *inode; + struct buffer_head *bh; + unsigned long i; + int error; + + pr_debug("AFFS: link(%lu,%lu,\"%.*s\")\n",oldinode->i_ino,dir->i_ino,len,name); + + bh = affs_find_entry(dir,name,len,&i); + if (bh) { + affs_brelse(bh); + iput(oldinode); + iput(dir); + return -EEXIST; + } + if (oldinode->u.affs_i.i_hlink) { + i = oldinode->u.affs_i.i_original; + iput(oldinode); + oldinode = iget(dir->i_sb,i); + if (!oldinode) { + printk("AFFS: link(): original does not exist.\n"); + iput(dir); + return -ENOENT; + } + } + inode = affs_new_inode(dir); + if (!inode) { + iput(oldinode); + iput(dir); + return -ENOSPC; + } + inode->i_op = oldinode->i_op; + inode->i_mode = oldinode->i_mode; + inode->i_uid = oldinode->i_uid; + inode->i_gid = oldinode->i_gid; + inode->u.affs_i.i_protect = mode_to_prot(inode->i_mode); + inode->u.affs_i.i_original = oldinode->i_ino; + inode->u.affs_i.i_hlink = 1; + + if (S_ISDIR(oldinode->i_mode)) + error = affs_add_entry(dir,oldinode,inode,name,len,ST_LINKDIR); + else + error = affs_add_entry(dir,oldinode,inode,name,len,ST_LINKFILE); + if (error) { + inode->i_nlink = 0; + inode->i_dirt = 1; + } + iput(dir); + iput(inode); + iput(oldinode); + + return error; +} + +static int +subdir(struct inode * new_inode, struct inode * old_inode) +{ + int ino; + int result; + + new_inode->i_count++; + result = 0; + for (;;) { + if (new_inode == old_inode) { + result = 1; + break; + } + if (new_inode->i_dev != old_inode->i_dev) + break; + ino = new_inode->i_ino; + if (affs_lookup(new_inode,"..",2,&new_inode)) + break; + if (new_inode->i_ino == ino) + break; + } + iput(new_inode); + return result; +} + +/* I'm afraid this might not be race proof. Maybe next time. */ + +int +affs_rename(struct inode *old_dir, const char *old_name, int old_len, + struct inode *new_dir, const char *new_name, int new_len, + int must_be_dir) +{ + struct inode *old_inode; + struct inode *new_inode; + struct buffer_head *old_bh; + struct buffer_head *new_bh; + unsigned long old_ino; + unsigned long new_ino; + int retval; + + pr_debug("AFFS: rename(old=%lu,\"%*s\" to new=%lu,\"%*s\")\n",old_dir->i_ino,old_len,old_name, + new_dir->i_ino,new_len,new_name); + + if (new_len > 30) + new_len = 30; + goto start_up; +retry: + affs_brelse(old_bh); + affs_brelse(new_bh); + iput(new_inode); + iput(old_inode); + current->counter = 0; + schedule(); +start_up: + old_inode = new_inode = NULL; + old_bh = new_bh = NULL; + retval = -ENOENT; + + old_bh = affs_find_entry(old_dir,old_name,old_len,&old_ino); + if (!old_bh) + goto end_rename; + old_inode = __iget(old_dir->i_sb,old_ino,0); + if (!old_inode) + goto end_rename; + if (must_be_dir && !S_ISDIR(old_inode->i_mode)) + goto end_rename; + new_bh = affs_find_entry(new_dir,new_name,new_len,&new_ino); + if (new_bh) { + new_inode = __iget(new_dir->i_sb,new_ino,0); + if (!new_inode) { /* What does this mean? */ + affs_brelse(new_bh); + new_bh = NULL; + } + } + if (new_inode == old_inode) { /* Won't happen */ + retval = 0; + goto end_rename; + } + if (new_inode && S_ISDIR(new_inode->i_mode)) { + retval = -EISDIR; + if (!S_ISDIR(old_inode->i_mode)) + goto end_rename; + retval = -EINVAL; + if (subdir(new_dir,old_inode)) + goto end_rename; + retval = -ENOTEMPTY; + if (!empty_dir(new_bh,AFFS_I2HSIZE(new_inode))) + goto end_rename; + retval = -EBUSY; + if (new_inode->i_count > 1) + goto end_rename; + } + if (S_ISDIR(old_inode->i_mode)) { + retval = -ENOTDIR; + if (new_inode && !S_ISDIR(new_inode->i_mode)) + goto end_rename; + retval = -EINVAL; + if (subdir(new_dir,old_inode)) + goto end_rename; + if (affs_parent_ino(old_inode) != old_dir->i_ino) + goto end_rename; + } + /* Unlink destination if existent */ + if (new_inode) { + if ((retval = affs_fix_hash_pred(new_dir,affs_hash_name(new_name,new_len, + AFFS_I2FSTYPE(new_dir),AFFS_I2HSIZE(new_dir)) + 6, + new_ino, + FILE_END(new_bh->b_data,new_dir)->hash_chain))) + goto retry; + if ((retval = affs_fixup(new_bh,new_inode))) + goto retry; + mark_buffer_dirty(new_bh,1); + new_dir->i_version = ++event; + new_dir->i_dirt = 1; + new_inode->i_nlink = 0; + new_inode->i_dirt = 1; + } + retval = affs_fix_hash_pred(old_dir,affs_hash_name(old_name,old_len,AFFS_I2FSTYPE(old_dir), + AFFS_I2HSIZE(old_dir)) + 6,old_ino, + FILE_END(old_bh->b_data,old_dir)->hash_chain); + if (retval) + goto retry; + + retval = affs_add_entry(new_dir,NULL,old_inode,new_name,new_len, + htonl(FILE_END(old_bh->b_data,old_dir)->secondary_type)); + + new_dir->i_ctime = new_dir->i_mtime = old_dir->i_ctime = old_dir->i_mtime = CURRENT_TIME; + new_dir->i_version = ++event; + old_dir->i_version = ++event; + new_dir->i_dirt = 1; + old_dir->i_dirt = 1; + mark_buffer_dirty(old_bh,1); + +end_rename: + affs_brelse(old_bh); + affs_brelse(new_bh); + iput(new_inode); + iput(old_inode); + iput(old_dir); + iput(new_dir); + + return retval; +} + +int +affs_fixup(struct buffer_head *bh, struct inode *inode) +{ + int key, link_key; + int type; + struct buffer_head *nbh; + struct inode *ofinode; + + type = htonl(FILE_END(bh->b_data,inode)->secondary_type); + if (type == ST_LINKFILE || type == ST_LINKDIR) { + key = htonl(LINK_END(bh->b_data,inode)->original); + LINK_END(bh->b_data,inode)->original = 0; + if (!key) { + printk("AFFS: fixup(): hard link without original: ino=%lu\n",inode->i_ino); + return -ENOENT; + } + if (!(ofinode = iget(inode->i_sb,key))) + return -ENOENT; + type = affs_fix_link_pred(ofinode,inode->i_ino, + FILE_END(bh->b_data,inode)->link_chain); + iput(ofinode); + return type; + } else if (type == ST_FILE || type == ST_USERDIR) { + if ((key = htonl(FILE_END(bh->b_data,inode)->link_chain))) { + /* Get first link, turn it to a file */ + if (!(ofinode = iget(inode->i_sb,key))) { + printk("AFFS: fixup(): cannot read inode %u\n",key); + return -ENOENT; + } + if (!ofinode->u.affs_i.i_hlink) { + printk("AFFS: fixup(): first link to %lu (%u) is not a link?\n", + inode->i_ino,key); + iput(ofinode); + return -ENOENT; + } + if (!(nbh = affs_bread(inode->i_dev,key,AFFS_I2BSIZE(inode)))) { + printk("AFFS: fixup(): cannot read block %u\n",key); + iput(ofinode); + return -ENOENT; + } + lock_super(inode->i_sb); + memcpy(nbh->b_data + 8,bh->b_data + 8,AFFS_I2BSIZE(inode) - 208); + FILE_END(nbh->b_data,inode)->byte_size = FILE_END(bh->b_data,inode)-> + byte_size; + FILE_END(nbh->b_data,inode)->extension = FILE_END(bh->b_data,inode)-> + extension; + FILE_END(nbh->b_data,inode)->secondary_type = FILE_END(bh->b_data,inode)-> + secondary_type; + FILE_END(nbh->b_data,inode)->original = 0; + + ofinode->u.affs_i.i_original = 0; + ofinode->u.affs_i.i_hlink = 0; + ofinode->i_size = inode->i_size; + ofinode->i_uid = inode->i_uid; + ofinode->i_gid = inode->i_gid; + ofinode->i_dirt = 1; + link_key = ofinode->i_ino; + + /* Let all remaining links point to the new file */ + while (1) { + affs_fix_checksum(AFFS_I2BSIZE(inode),nbh->b_data,5); + mark_buffer_dirty(nbh,1); + key = htonl(FILE_END(nbh->b_data,inode)->link_chain); + affs_brelse(nbh); + iput(ofinode); + if (!key || !(nbh = affs_bread(inode->i_dev,key,AFFS_I2BSIZE(inode)))) + break; + if ((ofinode = iget(inode->i_sb,key))) { + if (!ofinode->u.affs_i.i_hlink) + printk("AFFS: fixup() inode %u in link chain is " + "not a link\n",key); + ofinode->u.affs_i.i_original = link_key; + ofinode->i_dirt = 1; + FILE_END(nbh->b_data,inode)->original = htonl(link_key); + } else + printk("AFFS: fixup(): cannot get inode %u\n",key); + } + /* Turn old inode to a link */ + inode->u.affs_i.i_hlink = 1; + unlock_super(inode->i_sb); + } + return 0; + } else if (type == ST_SOFTLINK) { + return 0; + } else { + printk("AFFS: fixup(): secondary type=%d\n",type); + return -EBADF; + } +} diff --git a/fs/affs/symlink.c b/fs/affs/symlink.c new file mode 100644 index 000000000..734df0780 --- /dev/null +++ b/fs/affs/symlink.c @@ -0,0 +1,176 @@ +/* + * linux/fs/affs/symlink.c + * + * 1995 Hans-Joachim Widmaier - Modified for affs. + * + * Copyright (C) 1991, 1992 Linus Torvalds + * + * affs symlink handling code + */ + +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/malloc.h> +#include <linux/fs.h> +#include <linux/stat.h> +#include <linux/affs_fs.h> +#include <linux/amigaffs.h> +#include <asm/uaccess.h> + +#define MIN(a,b) (((a) < (b)) ? (a) : (b)) + +static int affs_readlink(struct inode *, char *, int); +static int affs_follow_link(struct inode *, struct inode *, int, int, struct inode **); + +struct inode_operations affs_symlink_inode_operations = { + NULL, /* no file-operations */ + NULL, /* create */ + NULL, /* lookup */ + NULL, /* link */ + NULL, /* unlink */ + NULL, /* symlink */ + NULL, /* mkdir */ + NULL, /* rmdir */ + NULL, /* mknod */ + NULL, /* rename */ + affs_readlink, /* readlink */ + affs_follow_link, /* follow_link */ + NULL, /* bmap */ + NULL, /* truncate */ + NULL /* permission */ +}; + +static int +affs_follow_link(struct inode *dir, struct inode *inode, int flag, int mode, + struct inode **res_inode) +{ + struct buffer_head *bh; + struct slink_front *lf; + char *buffer; + int error; + int i, j; + char c; + char lc; + + pr_debug("AFFS: follow_link(ino=%lu)\n",inode->i_ino); + + *res_inode = NULL; + if (!dir) { + dir = current->fs->root; + dir->i_count++; + } + if (!inode) { + iput(dir); + return -ENOENT; + } + if (!S_ISLNK(inode->i_mode)) { + iput(dir); + *res_inode = inode; + return 0; + } + if (current->link_count > 5) { + iput(inode); + iput(dir); + return -ELOOP; + } + if (!(buffer = kmalloc(1024,GFP_KERNEL))) { + iput(inode); + iput(dir); + return -ENOSPC; + } + bh = affs_bread(inode->i_dev,inode->i_ino,AFFS_I2BSIZE(inode)); + i = 0; + j = 0; + if (!bh) { + printk("AFFS: unable to read i-node block %lu\n",inode->i_ino); + kfree(buffer); + iput(inode); + iput(dir); + return -EIO; + } + lf = (struct slink_front *)bh->b_data; + lc = 0; + if (strchr(lf->symname,':')) { /* Handle assign or volume name */ + while (i < 1023 && (c = inode->i_sb->u.affs_sb.s_prefix[i])) + buffer[i++] = c; + while (i < 1023 && lf->symname[j] != ':') + buffer[i++] = lf->symname[j++]; + if (i < 1023) + buffer[i++] = '/'; + j++; + lc = '/'; + } + while (i < 1023 && (c = lf->symname[j])) { + if (c == '/' && lc == '/' && i < 1020) { /* parent dir */ + buffer[i++] = '.'; + buffer[i++] = '.'; + } + buffer[i++] = c; + lc = c; + j++; + } + buffer[i] = '\0'; + affs_brelse(bh); + iput(inode); + current->link_count++; + error = open_namei(buffer,flag,mode,res_inode,dir); + current->link_count--; + kfree(buffer); + return error; +} + +static int +affs_readlink(struct inode *inode, char *buffer, int buflen) +{ + struct buffer_head *bh; + struct slink_front *lf; + int i, j; + char c; + char lc; + + pr_debug("AFFS: readlink(ino=%lu,buflen=%d)\n",inode->i_ino,buflen); + + if (!S_ISLNK(inode->i_mode)) { + iput(inode); + return -EINVAL; + } + bh = affs_bread(inode->i_dev,inode->i_ino,AFFS_I2BSIZE(inode)); + i = 0; + j = 0; + if (!bh) { + printk("AFFS: unable to read i-node block %lu\n",inode->i_ino); + goto symlink_end; + } + lf = (struct slink_front *)bh->b_data; + lc = 0; + + if (strchr(lf->symname,':')) { /* Handle assign or volume name */ + while (i < buflen && (c = inode->i_sb->u.affs_sb.s_prefix[i])) { + put_user(c,buffer++); + i++; + } + while (i < buflen && (c = lf->symname[j]) != ':') { + put_user(c,buffer++); + i++, j++; + } + if (i < buflen) { + put_user('/',buffer++); + i++, j++; + } + lc = '/'; + } + while (i < buflen && (c = lf->symname[j])) { + if (c == '/' && lc == '/' && (i + 3 < buflen)) { /* parent dir */ + put_user('.',buffer++); + put_user('.',buffer++); + i += 2; + } + put_user(c,buffer++); + lc = c; + i++, j++; + } +symlink_end: + iput(inode); + affs_brelse(bh); + return i; +} |