summaryrefslogtreecommitdiffstats
path: root/fs/adfs/super.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/adfs/super.c')
-rw-r--r--fs/adfs/super.c341
1 files changed, 341 insertions, 0 deletions
diff --git a/fs/adfs/super.c b/fs/adfs/super.c
new file mode 100644
index 000000000..3ddd5d5cc
--- /dev/null
+++ b/fs/adfs/super.c
@@ -0,0 +1,341 @@
+/*
+ * linux/fs/adfs/super.c
+ *
+ * Copyright (C) 1997 Russell King
+ */
+
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/adfs_fs.h>
+#include <linux/malloc.h>
+#include <linux/sched.h>
+#include <linux/stat.h>
+#include <linux/string.h>
+#include <linux/locks.h>
+#include <linux/init.h>
+
+#include <asm/bitops.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+#include <stdarg.h>
+
+static void adfs_put_super (struct super_block *sb);
+static int adfs_statfs (struct super_block *sb, struct statfs *buf, int bufsiz);
+void adfs_read_inode (struct inode *inode);
+
+void adfs_error (struct super_block *sb, const char *function, const char *fmt, ...)
+{
+ char error_buf[128];
+ va_list args;
+
+ va_start (args, fmt);
+ vsprintf (error_buf, fmt, args);
+ va_end (args);
+
+ printk (KERN_CRIT "ADFS-fs error (device %s)%s%s: %s\n",
+ kdevname (sb->s_dev), function ? ": " : "",
+ function ? function : "", error_buf);
+}
+
+unsigned char adfs_calccrosscheck (struct super_block *sb, char *map)
+{
+ unsigned int v0, v1, v2, v3;
+ int i;
+
+ v0 = v1 = v2 = v3 = 0;
+ for (i = sb->s_blocksize - 4; i; i -= 4) {
+ v0 += map[i] + (v3 >> 8);
+ v3 &= 0xff;
+ v1 += map[i + 1] + (v0 >> 8);
+ v0 &= 0xff;
+ v2 += map[i + 2] + (v1 >> 8);
+ v1 &= 0xff;
+ v3 += map[i + 3] + (v2 >> 8);
+ v2 &= 0xff;
+ }
+ v0 += v3 >> 8;
+ v1 += map[1] + (v0 >> 8);
+ v2 += map[2] + (v1 >> 8);
+ v3 += map[3] + (v2 >> 8);
+
+ return v0 ^ v1 ^ v2 ^ v3;
+}
+
+static int adfs_checkmap (struct super_block *sb)
+{
+ unsigned char crosscheck = 0, zonecheck = 1;
+ int i;
+
+ for (i = 0; i < sb->u.adfs_sb.s_map_size; i++) {
+ char *map;
+
+ map = sb->u.adfs_sb.s_map[i]->b_data;
+ if (adfs_calccrosscheck (sb, map) != map[0]) {
+ adfs_error (sb, "adfs_checkmap", "zone %d fails zonecheck", i);
+ zonecheck = 0;
+ }
+ crosscheck ^= map[3];
+ }
+ if (crosscheck != 0xff)
+ adfs_error (sb, "adfs_checkmap", "crosscheck != 0xff");
+ return crosscheck == 0xff && zonecheck;
+}
+
+static struct super_operations adfs_sops = {
+ adfs_read_inode,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ adfs_put_super,
+ NULL,
+ adfs_statfs,
+ NULL
+};
+
+static void adfs_put_super (struct super_block *sb)
+{
+ int i;
+ lock_super (sb);
+ sb->s_dev = 0;
+ for (i = 0; i < sb->u.adfs_sb.s_map_size; i++)
+ brelse (sb->u.adfs_sb.s_map[i]);
+ kfree (sb->u.adfs_sb.s_map);
+ brelse (sb->u.adfs_sb.s_sbh);
+ unlock_super (sb);
+ MOD_DEC_USE_COUNT;
+}
+
+struct super_block *adfs_read_super (struct super_block *sb, void *data, int silent)
+{
+ struct adfs_discrecord *dr;
+ struct buffer_head *bh;
+ unsigned char *b_data;
+ kdev_t dev = sb->s_dev;
+ int i, j;
+
+ MOD_INC_USE_COUNT;
+ lock_super (sb);
+ set_blocksize (dev, BLOCK_SIZE);
+ if (!(bh = bread (dev, ADFS_DISCRECORD / BLOCK_SIZE, BLOCK_SIZE))) {
+ unlock_super (sb);
+ adfs_error (sb, NULL, "unable to read superblock");
+ MOD_DEC_USE_COUNT;
+ return NULL;
+ }
+
+ b_data = bh->b_data + (ADFS_DISCRECORD % BLOCK_SIZE);
+
+ if (adfs_checkbblk (b_data)) {
+ if (!silent)
+ printk ("VFS: Can't find an adfs filesystem on dev "
+ "%s.\n", kdevname(dev));
+failed_mount:
+ unlock_super (sb);
+ if (bh)
+ brelse (bh);
+ MOD_DEC_USE_COUNT;
+ return NULL;
+ }
+ dr = (struct adfs_discrecord *)(b_data + ADFS_DR_OFFSET);
+
+ sb->s_blocksize_bits = dr->log2secsize;
+ sb->s_blocksize = 1 << sb->s_blocksize_bits;
+ if (sb->s_blocksize != BLOCK_SIZE &&
+ (sb->s_blocksize == 512 || sb->s_blocksize == 1024 ||
+ sb->s_blocksize == 2048 || sb->s_blocksize == 4096)) {
+
+ brelse (bh);
+ set_blocksize (dev, sb->s_blocksize);
+ bh = bread (dev, ADFS_DISCRECORD / sb->s_blocksize, sb->s_blocksize);
+ if (!bh) {
+ adfs_error (sb, NULL, "couldn't read superblock on "
+ "2nd try.");
+ goto failed_mount;
+ }
+ b_data = bh->b_data + (ADFS_DISCRECORD % sb->s_blocksize);
+ if (adfs_checkbblk (b_data)) {
+ adfs_error (sb, NULL, "disc record mismatch, very weird!");
+ goto failed_mount;
+ }
+ dr = (struct adfs_discrecord *)(b_data + ADFS_DR_OFFSET);
+ }
+ if (sb->s_blocksize != bh->b_size) {
+ if (!silent)
+ printk (KERN_ERR "VFS: Unsupported blocksize on dev "
+ "%s.\n", kdevname (dev));
+ goto failed_mount;
+ }
+ /* blocksize on this device should now be set to the adfs log2secsize */
+
+ sb->u.adfs_sb.s_sbh = bh;
+ sb->u.adfs_sb.s_dr = dr;
+
+ /* s_zone_size = size of 1 zone (1 sector) * bits_in_byte - zone_spare =>
+ * number of map bits in a zone
+ */
+ sb->u.adfs_sb.s_zone_size = (8 << dr->log2secsize) - dr->zone_spare;
+
+ /* s_ids_per_zone = bit size of 1 zone / min. length of fragment block =>
+ * number of ids in one zone
+ */
+ sb->u.adfs_sb.s_ids_per_zone = sb->u.adfs_sb.s_zone_size / (dr->idlen + 1);
+
+ /* s_idlen = length of 1 id */
+ sb->u.adfs_sb.s_idlen = dr->idlen;
+
+ /* map size (in sectors) = number of zones */
+ sb->u.adfs_sb.s_map_size = dr->nzones;
+
+ /* zonesize = size of sector - zonespare */
+ sb->u.adfs_sb.s_zonesize = (sb->s_blocksize << 3) - dr->zone_spare;
+
+ /* map start (in sectors) = start of zone (number of zones) / 2 */
+ sb->u.adfs_sb.s_map_block = (dr->nzones >> 1) * sb->u.adfs_sb.s_zone_size -
+ ((dr->nzones > 1) ? 8 * ADFS_DR_SIZE : 0);
+
+ /* (signed) number of bits to shift left a map address to a sector address */
+ sb->u.adfs_sb.s_map2blk = dr->log2bpmb - dr->log2secsize;
+
+ if (sb->u.adfs_sb.s_map2blk >= 0)
+ sb->u.adfs_sb.s_map_block <<= sb->u.adfs_sb.s_map2blk;
+ else
+ sb->u.adfs_sb.s_map_block >>= -sb->u.adfs_sb.s_map2blk;
+
+ printk (KERN_DEBUG "ADFS: zone size %d, IDs per zone %d, map address %X size %d sectors\n",
+ sb->u.adfs_sb.s_zone_size, sb->u.adfs_sb.s_ids_per_zone,
+ sb->u.adfs_sb.s_map_block, sb->u.adfs_sb.s_map_size);
+ printk (KERN_DEBUG "ADFS: sector size %d, map bit size %d\n",
+ 1 << dr->log2secsize, 1 << dr->log2bpmb);
+
+ sb->s_magic = ADFS_SUPER_MAGIC;
+ sb->s_flags |= MS_RDONLY; /* we don't support writing yet */
+
+ sb->u.adfs_sb.s_map = kmalloc (sb->u.adfs_sb.s_map_size *
+ sizeof (struct buffer_head *), GFP_KERNEL);
+ if (sb->u.adfs_sb.s_map == NULL) {
+ adfs_error (sb, NULL, "not enough memory");
+ goto failed_mount;
+ }
+
+ for (i = 0; i < sb->u.adfs_sb.s_map_size; i++) {
+ sb->u.adfs_sb.s_map[i] = bread (dev,
+ sb->u.adfs_sb.s_map_block + i,
+ sb->s_blocksize);
+ if (!sb->u.adfs_sb.s_map[i]) {
+ for (j = 0; j < i; j++)
+ brelse (sb->u.adfs_sb.s_map[j]);
+ kfree (sb->u.adfs_sb.s_map);
+ adfs_error (sb, NULL, "unable to read map");
+ goto failed_mount;
+ }
+ }
+ if (!adfs_checkmap (sb)) {
+ for (i = 0; i < sb->u.adfs_sb.s_map_size; i++)
+ brelse (sb->u.adfs_sb.s_map[i]);
+ adfs_error (sb, NULL, "map corrupted");
+ goto failed_mount;
+ }
+
+ dr = (struct adfs_discrecord *)(sb->u.adfs_sb.s_map[0]->b_data + 4);
+ unlock_super (sb);
+
+ /*
+ * set up enough so that it can read an inode
+ */
+ sb->s_op = &adfs_sops;
+ sb->u.adfs_sb.s_root = adfs_inode_generate (dr->root, 0);
+ sb->s_root = d_alloc_root(iget(sb, sb->u.adfs_sb.s_root), NULL);
+
+ if (!sb->s_root) {
+ sb->s_dev = 0;
+ for (i = 0; i < sb->u.adfs_sb.s_map_size; i++)
+ brelse (sb->u.adfs_sb.s_map[i]);
+ brelse (bh);
+ adfs_error (sb, NULL, "get root inode failed\n");
+ MOD_DEC_USE_COUNT;
+ return NULL;
+ }
+ return sb;
+}
+
+static int adfs_statfs (struct super_block *sb, struct statfs *buf, int bufsiz)
+{
+ struct statfs tmp;
+ const unsigned int nidlen = sb->u.adfs_sb.s_idlen + 1;
+
+ tmp.f_type = ADFS_SUPER_MAGIC;
+ tmp.f_bsize = sb->s_blocksize;
+ tmp.f_blocks = (sb->u.adfs_sb.s_dr->disc_size) >> (sb->s_blocksize_bits);
+ tmp.f_files = tmp.f_blocks >> nidlen;
+ {
+ unsigned int i, j = 0;
+ const unsigned mask = (1 << (nidlen - 1)) - 1;
+ for (i = 0; i < sb->u.adfs_sb.s_map_size; i++) {
+ const char *map = sb->u.adfs_sb.s_map[i]->b_data;
+ unsigned freelink, mapindex = 24;
+ j -= nidlen;
+ do {
+ unsigned char k, l, m;
+ unsigned off = (mapindex - nidlen) >> 3;
+ unsigned rem;
+ const unsigned boff = mapindex & 7;
+
+ /* get next freelink */
+
+ k = map[off++];
+ l = map[off++];
+ m = map[off++];
+ freelink = (m << 16) | (l << 8) | k;
+ rem = freelink >> (boff + nidlen - 1);
+ freelink = (freelink >> boff) & mask;
+ mapindex += freelink;
+
+ /* find its length and add it to running total */
+
+ while (rem == 0) {
+ j += 8;
+ rem = map[off++];
+ }
+ if ((rem & 0xff) == 0) j+=8, rem>>=8;
+ if ((rem & 0xf) == 0) j+=4, rem>>=4;
+ if ((rem & 0x3) == 0) j+=2, rem>>=2;
+ if ((rem & 0x1) == 0) j+=1;
+ j += nidlen - boff;
+ if (freelink <= nidlen) break;
+ } while (mapindex < 8 * sb->s_blocksize);
+ if (mapindex > 8 * sb->s_blocksize)
+ adfs_error (sb, NULL, "oversized free fragment\n");
+ else if (freelink)
+ adfs_error (sb, NULL, "undersized free fragment\n");
+ }
+ tmp.f_bfree = tmp.f_bavail = j <<
+ (sb->u.adfs_sb.s_dr->log2bpmb - sb->s_blocksize_bits);
+ }
+ tmp.f_ffree = tmp.f_bfree >> nidlen;
+ tmp.f_namelen = ADFS_NAME_LEN;
+ return copy_to_user (buf, &tmp, bufsiz) ? -EFAULT : 0;
+}
+
+static struct file_system_type adfs_fs_type = {
+ "adfs", FS_REQUIRES_DEV, adfs_read_super, NULL
+};
+
+__initfunc(int init_adfs_fs (void))
+{
+ return register_filesystem (&adfs_fs_type);
+}
+
+#ifdef MODULE
+int init_module (void)
+{
+ return init_adfs_fs();
+}
+
+void cleanup_module (void)
+{
+ unregister_filesystem (&adfs_fs_type);
+}
+#endif