diff options
Diffstat (limited to 'fs/affs/super.c')
-rw-r--r-- | fs/affs/super.c | 691 |
1 files changed, 691 insertions, 0 deletions
diff --git a/fs/affs/super.c b/fs/affs/super.c new file mode 100644 index 000000000..7bd31dc5b --- /dev/null +++ b/fs/affs/super.c @@ -0,0 +1,691 @@ +/* + * 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 + */ + +#define DEBUG 0 +#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 <linux/init.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)) + +static int affs_statfs(struct super_block *sb, struct statfs *buf, int bufsiz); +static int affs_remount (struct super_block *sb, int *flags, char *data); + +static 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 = be32_to_cpu(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_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 = be32_to_cpu(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_write_inode, + affs_put_inode, + affs_delete_inode, + affs_notify_change, + affs_put_super, + affs_write_super, + affs_statfs, + affs_remount +}; + +static int +parse_options(char *options, uid_t *uid, gid_t *gid, int *mode, int *reserved, s32 *root, + int *blocksize, char **prefix, char *volume, unsigned long *mount_opts) +{ + char *this_char, *value; + int f; + + /* Fill in defaults */ + + *uid = current->uid; + *gid = current->gid; + *reserved = 2; + *root = -1; + *blocksize = -1; + 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,"setuid")) || !strcmp(this_char,"setgid")) { + if (value) { + if (!*value) { + printk("AFFS: Argument for set[ug]id option missing\n"); + return 0; + } else { + (f ? *uid : *gid) = simple_strtoul(value,&value,0); + if (*value) { + printk("AFFS: Bad set[ug]id argument\n"); + return 0; + } + *mount_opts |= f ? SF_SETUID : SF_SETGID; + } + } + } else if (!strcmp(this_char,"prefix")) { + if (!value || !*value) { + printk("AFFS: The prefix option requires an argument\n"); + return 0; + } + if (*prefix) /* Free any previous prefix */ + kfree(*prefix); + *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 || !*value) { + printk("AFFS: The volume option requires an argument\n"); + return 0; + } + if (strlen(value) > 30) + value[30] = 0; + strncpy(volume,value,30); + } 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; + } + } else if (!strcmp (this_char, "grpquota") + || !strcmp (this_char, "noquota") + || !strcmp (this_char, "quota") + || !strcmp (this_char, "usrquota")) + /* Silently ignore the quota options */ + ; + 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. + */ + +static struct super_block * +affs_read_super(struct super_block *s,void *data, int silent) +{ + struct buffer_head *bh = NULL; + struct buffer_head *bb; + struct inode *root_inode; + kdev_t dev = s->s_dev; + s32 root_block; + int size; + u32 chksum; + u32 *bm; + s32 ptype, stype; + int mapidx; + int num_bm; + int i, j; + s32 key; + int blocksize; + uid_t uid; + gid_t gid; + int reserved; + int az_no; + int bmalt = 0; + unsigned long mount_flags; + unsigned long offset; + + pr_debug("affs_read_super(%s)\n",data ? (const char *)data : "no options"); + + MOD_INC_USE_COUNT; + + s->u.affs_sb.s_prefix = NULL; + 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(KERN_ERR "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(KERN_ERR "AFFS: 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 depends 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 %u, " + "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(KERN_ERR "AFFS: Cannot 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(KERN_ERR "AFFS: Cannot 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 = be32_to_cpu(*(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(KERN_NOTICE "AFFS: Dircache FS - mounting %s read only\n", + kdevname(dev)); + s->s_flags |= MS_RDONLY; + s->u.affs_sb.s_flags |= SF_READONLY; + } + 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(KERN_ERR "AFFS: Unknown filesystem on device %s: %08X\n", + kdevname(dev),chksum); + affs_brelse(bb); + goto out; + } + affs_brelse(bb); + } else { + printk(KERN_ERR "AFFS: Cannot read boot block\n"); + goto out; + } + if (mount_flags & SF_VERBOSE) { + chksum = cpu_to_be32(chksum); + printk(KERN_NOTICE "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(KERN_ERR "AFFS: Cannot read root block\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(KERN_ERR "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(KERN_NOTICE "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(KERN_ERR "AFFS: Extraneous bitmap pointer - " + "mounting %s read only.\n",kdevname(dev)); + s->s_flags |= MS_RDONLY; + s->u.affs_sb.s_flags |= SF_READONLY; + continue; + } + bb = affs_bread(s->s_dev,be32_to_cpu(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(KERN_WARNING "AFFS: Bitmap (%d,key=%lu) invalid - " + "mounting %s read only.\n",mapidx,be32_to_cpu(bm[i]), + kdevname(dev)); + s->s_flags |= MS_RDONLY; + s->u.affs_sb.s_flags |= SF_READONLY; + } + /* 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 && !(s->s_flags & MS_RDONLY)) { + chksum = cpu_to_be32(0x7FFFFFFF >> (31 - key)); + ((u32 *)bb->b_data)[ptype] &= chksum; + affs_fix_checksum(s->s_blocksize,bb->b_data,0); + mark_buffer_dirty(bb,1); + bmalt = 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 = be32_to_cpu(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(KERN_ERR "AFFS: Cannot read bitmap\n"); + goto out; + } + } + key = be32_to_cpu(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(KERN_ERR "AFFS: Cannot read bitmap extension\n"); + goto out; + } + } else + bh = NULL; + } + if (mapidx < num_bm) { + printk(KERN_ERR "AFFS: Got only %d bitmap blocks, expected %d\n",mapidx,num_bm); + goto out; + } +nobitmap: + s->u.affs_sb.s_bm_count = num_bm; + + /* set up enough so that it can read an inode */ + + s->s_dev = dev; + s->s_op = &affs_sops; + s->s_dirt = 1; + root_inode = iget(s,root_block); + s->s_root = d_alloc_root(root_inode,NULL); + unlock_super(s); + + if (!(s->s_root)) { + s->s_dev = 0; + affs_brelse(s->u.affs_sb.s_root_bh); + printk(KERN_ERR "AFFS: get root inode failed\n"); + MOD_DEC_USE_COUNT; + return NULL; + } + + /* Record date of last change if the bitmap was truncated and + * create data zones if the volume is writable. + */ + + if (!(s->s_flags & MS_RDONLY)) { + if (bmalt) { + secs_to_datestamp(CURRENT_TIME,&ROOT_END( + s->u.affs_sb.s_root_bh->b_data,root_inode)->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; +} + +static int +affs_remount(struct super_block *sb, int *flags, char *data) +{ + int blocksize; + uid_t uid; + gid_t gid; + int mode; + int reserved; + int root_block; + unsigned long mount_flags; + unsigned long read_only = sb->u.affs_sb.s_flags & SF_READONLY; + + pr_debug("AFFS: remount(flags=0x%x,opts=\"%s\")\n",*flags,data); + + if (!parse_options(data,&uid,&gid,&mode,&reserved,&root_block, + &blocksize,&sb->u.affs_sb.s_prefix,sb->u.affs_sb.s_volume,&mount_flags)) + return -EINVAL; + sb->u.affs_sb.s_flags = mount_flags | read_only; + sb->u.affs_sb.s_mode = mode; + sb->u.affs_sb.s_uid = uid; + sb->u.affs_sb.s_gid = gid; + + if ((*flags & MS_RDONLY) == (sb->s_flags & MS_RDONLY)) + return 0; + if (*flags & MS_RDONLY) { + sb->s_dirt = 1; + while (sb->s_dirt) + affs_write_super(sb); + sb->s_flags |= MS_RDONLY; + } else if (!(sb->u.affs_sb.s_flags & SF_READONLY)) { + sb->s_flags &= ~MS_RDONLY; + affs_make_zones(sb); + } else { + affs_warning(sb,"remount","Cannot remount fs read/write because of errors"); + return -EINVAL; + } + return 0; +} + +static int +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; + return copy_to_user(buf,&tmp,bufsiz) ? -EFAULT : 0; +} + +static struct file_system_type affs_fs_type = { + "affs", + FS_REQUIRES_DEV, + affs_read_super, + NULL +}; + +__initfunc(int init_affs_fs(void)) +{ + return register_filesystem(&affs_fs_type); +} + +#ifdef MODULE +EXPORT_NO_SYMBOLS; + +int +init_module(void) +{ + return register_filesystem(&affs_fs_type); +} + +void +cleanup_module(void) +{ + unregister_filesystem(&affs_fs_type); +} + +#endif |