summaryrefslogtreecommitdiffstats
path: root/fs/affs
diff options
context:
space:
mode:
authorRalf Baechle <ralf@linux-mips.org>1997-01-07 02:33:00 +0000
committer <ralf@linux-mips.org>1997-01-07 02:33:00 +0000
commitbeb116954b9b7f3bb56412b2494b562f02b864b1 (patch)
tree120e997879884e1b9d93b265221b939d2ef1ade1 /fs/affs
parent908d4681a1dc3792ecafbe64265783a86c4cccb6 (diff)
Import of Linux/MIPS 2.1.14
Diffstat (limited to 'fs/affs')
-rw-r--r--fs/affs/Makefile14
-rw-r--r--fs/affs/amigaffs.c273
-rw-r--r--fs/affs/bitmap.c385
-rw-r--r--fs/affs/dir.c192
-rw-r--r--fs/affs/file.c916
-rw-r--r--fs/affs/inode.c1013
-rw-r--r--fs/affs/namei.c741
-rw-r--r--fs/affs/symlink.c176
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;
+}