OpenCores
URL https://opencores.org/ocsvn/or1k/or1k/trunk

Subversion Repositories or1k

[/] [or1k/] [trunk/] [uclinux/] [uClinux-2.0.x/] [fs/] [hpfs/] [hpfs_fs.c] - Rev 1765

Compare with Previous | Blame | View Log

/*
 *  linux/fs/hpfs/hpfs_fs.c
 *  read-only HPFS
 *  version 1.0
 *
 *  Chris Smith 1993
 *
 *  Sources & references:
 *   Duncan, _Design ... of HPFS_, MSJ 4(5)   (C) 1989 Microsoft Corp
 *   linux/fs/minix  Copyright (C) 1991, 1992, 1993  Linus Torvalds
 *   linux/fs/msdos  Written 1992, 1993 by Werner Almesberger
 *   linux/fs/isofs  Copyright (C) 1991  Eric Youngdale
 */
 
#include <linux/module.h>
 
#include <linux/fs.h>
#include <linux/hpfs_fs.h>
#include <linux/errno.h>
#include <linux/malloc.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/locks.h>
#include <linux/stat.h>
#include <linux/string.h>
#include <asm/bitops.h>
#include <asm/segment.h>
 
#include "hpfs.h"
#include "hpfs_caps.h"
 
/* 
 * HPFS is a mixture of 512-byte blocks and 2048-byte blocks.  The 2k blocks
 * are used for directories and bitmaps.  For bmap to work, we must run the
 * file system with 512-byte blocks.  The 2k blocks are assembled in buffers
 * obtained from kmalloc.
 *
 * For a file's i-number we use the sector number of its fnode, coded.
 * (Directory ino's are even, file ino's are odd, and ino >> 1 is the
 * sector address of the fnode.  This is a hack to allow lookup() to
 * tell read_inode() whether it is necessary to read the fnode.)
 *
 * The map_xxx routines all read something into a buffer and return a
 * pointer somewhere in the buffer.  The caller must do the brelse.
 * The other routines are balanced.
 *
 * For details on the data structures see hpfs.h and the Duncan paper.
 *
 * Overview
 *
 * [ The names of these data structures, except fnode, are not Microsoft's
 * or IBM's.  I don't know what names they use.  The semantics described
 * here are those of this implementation, and any coincidence between it
 * and real HPFS is to be hoped for but not guaranteed by me, and
 * certainly not guaranteed by MS or IBM.  Who know nothing about this. ]
 *
 * [ Also, the following will make little sense if you haven't read the
 * Duncan paper, which is excellent. ]
 *
 * HPFS is a tree.  There are 3 kinds of nodes.  A directory is a tree
 * of dnodes, and a file's allocation info is a tree of sector runs
 * stored in fnodes and anodes.
 *
 * The top pointer is in the super block, it points to the fnode of the
 * root directory.
 *
 * The root directory -- all directories -- gives file names, dates &c,
 * and fnode addresses.  If the directory fits in one dnode, that's it,
 * otherwise the top dnode points to other dnodes, forming a tree.  A
 * dnode tree (one directory) might look like
 *
 *     ((a b c) d (e f g) h (i j) k l (m n o p))
 *
 * The subtrees appear between the files.  Each dir entry contains, along
 * with the name and fnode, a dnode pointer to the subtree that precedes it
 * (if there is one; a flag tells that).  The first entry in every directory
 * is ^A^A, the "." entry for the directory itself.  The last entry in every
 * dnode is \377, a fake entry whose only valid fields are the bit marking
 * it last and the down pointer to the subtree preceding it, if any.
 *
 * The "value" field of directory entries is an fnode address.  The fnode
 * tells where the sectors of the file are.  The fnode for a subdirectory
 * contains one pointer, to the root dnode of the subdirectory.  The fnode
 * for a data file contains, in effect, a tiny anode.  (Most of the space
 * in fnodes is for extended attributes.)
 *
 * anodes and the anode part of fnodes are trees of extents.  An extent
 * is a (length, disk address) pair, labeled with the file address being
 * mapped.  E.g.,
 *
 *     (0: 3@1000  3: 1@2000  4: 2@10)
 *
 * means the file:disk sector map (0:1000 1:1001 2:1002 3:2000 4:10 5:11).
 *
 * There is space for 8 file:len@disk triples in an fnode, or for 40 in an
 * anode.  If this is insufficient, subtrees are used, as in
 *
 *  (6: (0: 3@1000  3: 1@2000  4: 2@10)  12: (6: 3@8000  9: 1@9000  10: 2@20))
 *
 * The label on a subtree is the first address *after* that tree.  The
 * subtrees are always anodes.  The label:subtree pairs require only
 * two words each, so non-leaf subtrees have a different format; there
 * is room for 12 label:subtree pairs in an fnode, or 60 in an anode.
 *
 * Within a directory, each dnode contains a pointer up to its parent
 * dnode.  The root dnode points up to the directory's fnode.
 *
 * Each fnode contains a pointer to the directory that contains it
 * (to the fnode of the directory).  So this pointer in a directory
 * fnode is "..".
 *
 * On the disk, dnodes are all together in the center of the partition,
 * and HPFS even manages to put all the dnodes for a single directory
 * together, generally.  fnodes are out with the data.  anodes are seldom
 * seen -- in fact noncontiguous files are seldom seen.  I think this is
 * partly the open() call that lets programs specify the length of an
 * output file when they know it, and partly because HPFS.IFS really is
 * very good at resisting fragmentation. 
 */

/* notation */
 
#define little_ushort(x) (*(unsigned short *) &(x))
typedef void nonconst;
 
/* super block ops */
 
static void hpfs_read_inode(struct inode *);
static void hpfs_put_super(struct super_block *);
static void hpfs_statfs(struct super_block *, struct statfs *, int);
static int hpfs_remount_fs(struct super_block *, int *, char *);
 
static const struct super_operations hpfs_sops =
{
	hpfs_read_inode,		/* read_inode */
	NULL,				/* notify_change */
	NULL,				/* write_inode */
	NULL,				/* put_inode */
	hpfs_put_super,			/* put_super */
	NULL,				/* write_super */
	hpfs_statfs,			/* statfs */
	hpfs_remount_fs,		/* remount_fs */
};
 
/* file ops */
 
static int hpfs_file_read(struct inode *, struct file *, char *, int);
static secno hpfs_bmap(struct inode *, unsigned);
 
static const struct file_operations hpfs_file_ops =
{
	NULL,				/* lseek - default */
	hpfs_file_read,			/* read */
	NULL,				/* write */
	NULL,				/* readdir - bad */
	NULL,				/* select - default */
	NULL,				/* ioctl - default */
	generic_file_mmap,		/* mmap */
	NULL,				/* no special open is needed */
	NULL,				/* release */
	file_fsync,			/* fsync */
};
 
static const struct inode_operations hpfs_file_iops =
{
	(nonconst *) & hpfs_file_ops,	/* 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 */
	(int (*)(struct inode *, int))
	&hpfs_bmap,			/* bmap */
	NULL,				/* truncate */
	NULL,				/* permission */
};
 
/* directory ops */
 
static int hpfs_dir_read(struct inode *inode, struct file *filp,
			 char *buf, int count);
static int hpfs_readdir(struct inode *inode, struct file *filp,
			void *dirent, filldir_t filldir);
static int hpfs_lookup(struct inode *, const char *, int, struct inode **);
 
static const struct file_operations hpfs_dir_ops =
{
	NULL,				/* lseek - default */
	hpfs_dir_read,			/* read */
	NULL,				/* write - bad */
	hpfs_readdir,			/* readdir */
	NULL,				/* select - default */
	NULL,				/* ioctl - default */
	NULL,				/* mmap */
	NULL,				/* no special open code */
	NULL,				/* no special release code */
	file_fsync,			/* fsync */
};
 
static const struct inode_operations hpfs_dir_iops =
{
	(nonconst *) & hpfs_dir_ops,	/* default directory file ops */
	NULL,				/* create */
	hpfs_lookup,			/* 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 */
	NULL,				/* truncate */
	NULL,				/* permission */
};
 
/* Four 512-byte buffers and the 2k block obtained by concatenating them */
 
struct quad_buffer_head {
	struct buffer_head *bh[4];
	void *data;
};
 
/* forwards */
 
static int parse_opts(char *opts, uid_t *uid, gid_t *gid, umode_t *umask,
		      int *lowercase, int *conv, int *nocheck);
static int check_warn(int not_ok,
		      const char *p1, const char *p2, const char *p3);
static int zerop(void *addr, unsigned len);
static void count_dnodes(struct inode *inode, dnode_secno dno,
			 unsigned *n_dnodes, unsigned *n_subdirs);
static unsigned count_bitmap(struct super_block *s);
static unsigned count_one_bitmap(kdev_t dev, secno secno);
static secno bplus_lookup(struct inode *inode, struct bplus_header *b,
			  secno file_secno, struct buffer_head **bhp);
static struct hpfs_dirent *map_dirent(struct inode *inode, dnode_secno dno,
				    const unsigned char *name, unsigned len,
				      struct quad_buffer_head *qbh);
static struct hpfs_dirent *map_pos_dirent(struct inode *inode, loff_t *posp,
					  struct quad_buffer_head *qbh);
static dnode_secno dir_subdno(struct inode *inode, unsigned pos);
static struct hpfs_dirent *map_nth_dirent(kdev_t dev, dnode_secno dno,
					  int n,
					  struct quad_buffer_head *qbh);
static unsigned choose_conv(unsigned char *p, unsigned len);
static unsigned convcpy_tofs(unsigned char *out, unsigned char *in,
			     unsigned len);
static dnode_secno fnode_dno(kdev_t dev, ino_t ino);
static struct fnode *map_fnode(kdev_t dev, ino_t ino,
			       struct buffer_head **bhp);
static struct anode *map_anode(kdev_t dev, unsigned secno,
			       struct buffer_head **bhp);
static struct dnode *map_dnode(kdev_t dev, unsigned secno,
			       struct quad_buffer_head *qbh);
static void *map_sector(kdev_t dev, unsigned secno, struct buffer_head **bhp);
static void *map_4sectors(kdev_t dev, unsigned secno,
			  struct quad_buffer_head *qbh);
static void brelse4(struct quad_buffer_head *qbh);
 
/*
 * make inode number for a file
 */
 
static inline ino_t file_ino(fnode_secno secno)
{
	return secno << 1 | 1;
}
 
/*
 * make inode number for a directory
 */
 
static inline ino_t dir_ino(fnode_secno secno)
{
	return secno << 1;
}
 
/*
 * get fnode address from an inode number
 */
 
static inline fnode_secno ino_secno(ino_t ino)
{
	return ino >> 1;
}
 
/*
 * test for directory's inode number 
 */
 
static inline int ino_is_dir(ino_t ino)
{
	return (ino & 1) == 0;
}
 
/*
 * conv= options
 */
 
#define CONV_BINARY 0			/* no conversion */
#define CONV_TEXT 1			/* crlf->newline */
#define CONV_AUTO 2			/* decide based on file contents */
 
/*
 * local time (HPFS) to GMT (Unix)
 */
 
static inline time_t local_to_gmt(time_t t)
{
	extern struct timezone sys_tz;
	return t + sys_tz.tz_minuteswest * 60;
}

/* super block ops */
 
/*
 * mount.  This gets one thing, the root directory inode.  It does a
 * bunch of guessed-at consistency checks.
 */
 
struct super_block *hpfs_read_super(struct super_block *s,
				    void *options, int silent)
{
	struct hpfs_boot_block *bootblock;
	struct hpfs_super_block *superblock;
	struct hpfs_spare_block *spareblock;
	struct hpfs_dirent *de;
	struct buffer_head *bh0, *bh1, *bh2;
	struct quad_buffer_head qbh;
	dnode_secno root_dno;
	kdev_t dev;
	uid_t uid;
	gid_t gid;
	umode_t umask;
	int lowercase;
	int conv;
	int dubious;
	int nocheck;
 
	MOD_INC_USE_COUNT;
 
	/*
	 * Get the mount options
	 */
 
	if (!parse_opts(options, &uid, &gid, &umask, &lowercase, &conv,
				 &nocheck)) {
		printk("HPFS: syntax error in mount options.  Not mounted.\n");
		s->s_dev = 0;
		MOD_DEC_USE_COUNT;
		return 0;
	}
 
	/*
	 * Fill in the super block struct
	 */
 
	lock_super(s);
	dev = s->s_dev;
	set_blocksize(dev, 512);
 
	/*
	 * fetch sectors 0, 16, 17
	 */
 
	bootblock = map_sector(dev, 0, &bh0);
	if (!bootblock)
		goto bail;
 
	superblock = map_sector(dev, 16, &bh1);
	if (!superblock)
		goto bail0;
 
	spareblock = map_sector(dev, 17, &bh2);
	if (!spareblock)
		goto bail1;
 
	/*
	 * Check that this fs looks enough like a known one that we can find
	 * and read the root directory.
	 */
 
	if (bootblock->magic != 0xaa55
	    || superblock->magic != SB_MAGIC
	    || spareblock->magic != SP_MAGIC
	    || bootblock->sig_28h != 0x28
	    || memcmp(&bootblock->sig_hpfs, "HPFS    ", 8)
	    || little_ushort(bootblock->bytes_per_sector) != 512) {
		printk("HPFS: hpfs_read_super: Not HPFS\n");
		goto bail2;
	}
 
	/*
	 * Check for inconsistencies -- possibly wrong guesses here, possibly
	 * filesystem problems.
	 */
 
	dubious = 0;
 
	dubious |= check_warn(spareblock->dirty != 0,
		       "`Improperly stopped'", "flag is set", "run CHKDSK");
	dubious |= check_warn(spareblock->n_spares_used != 0,
			      "Spare blocks", "may be in use", "run CHKDSK");
 
	/*
	 * Above errors mean we could get wrong answers if we proceed,
	 * so don't
	 */
 
	if (dubious && !nocheck)
		goto bail2;
 
	dubious |= check_warn((spareblock->n_dnode_spares !=
			       spareblock->n_dnode_spares_free),
			      "Spare dnodes", "may be in use", "run CHKDSK");
	dubious |= check_warn(superblock->zero1 != 0,
			      "#1", "unknown word nonzero", "investigate");
	dubious |= check_warn(superblock->zero3 != 0,
			      "#3", "unknown word nonzero", "investigate");
	dubious |= check_warn(superblock->zero4 != 0,
			      "#4", "unknown word nonzero", "investigate");
	dubious |= check_warn(!zerop(superblock->zero5,
				     sizeof superblock->zero5),
			      "#5", "unknown word nonzero", "investigate");
	dubious |= check_warn(!zerop(superblock->zero6,
				     sizeof superblock->zero6),
			      "#6", "unknown word nonzero", "investigate");
 
	if (dubious)
		printk("HPFS: Proceeding, but operation may be unreliable\n");
 
	/*
	 * set fs read only
	 */
 
	s->s_flags |= MS_RDONLY;
 
	/*
	 * fill in standard stuff
	 */
 
	s->s_magic = HPFS_SUPER_MAGIC;
	s->s_blocksize = 512;
	s->s_blocksize_bits = 9;
	s->s_op = (struct super_operations *) &hpfs_sops;
 
	/*
	 * fill in hpfs stuff
	 */
 
	s->s_hpfs_root = dir_ino(superblock->root);
	s->s_hpfs_fs_size = superblock->n_sectors;
	s->s_hpfs_dirband_size = superblock->n_dir_band / 4;
	s->s_hpfs_dmap = superblock->dir_band_bitmap;
	s->s_hpfs_bitmaps = superblock->bitmaps;
	s->s_hpfs_uid = uid;
	s->s_hpfs_gid = gid;
	s->s_hpfs_mode = 0777 & ~umask;
	s->s_hpfs_n_free = -1;
	s->s_hpfs_n_free_dnodes = -1;
	s->s_hpfs_lowercase = lowercase;
	s->s_hpfs_conv = conv;
 
	/*
	 * done with the low blocks
	 */
 
	brelse(bh2);
	brelse(bh1);
	brelse(bh0);
 
	/*
	 * all set.  try it out.
	 */
 
	s->s_mounted = iget(s, s->s_hpfs_root);
	unlock_super(s);
 
	if (!s->s_mounted) {
		printk("HPFS: hpfs_read_super: inode get failed\n");
		s->s_dev = 0;
		MOD_DEC_USE_COUNT;
		return 0;
	}
 
	/*
	 * find the root directory's . pointer & finish filling in the inode
	 */
 
	root_dno = fnode_dno(dev, s->s_hpfs_root);
	if (root_dno)
		de = map_dirent(s->s_mounted, root_dno, "\001\001", 2, &qbh);
	if (!root_dno || !de) {
		printk("HPFS: "
		       "hpfs_read_super: root dir isn't in the root dir\n");
		s->s_dev = 0;
		MOD_DEC_USE_COUNT;
		return 0;
	}
 
	s->s_mounted->i_atime = local_to_gmt(de->read_date);
	s->s_mounted->i_mtime = local_to_gmt(de->write_date);
	s->s_mounted->i_ctime = local_to_gmt(de->creation_date);
 
	brelse4(&qbh);
	return s;
 
 bail2:
	brelse(bh2);
 bail1:
	brelse(bh1);
 bail0:
	brelse(bh0);
 bail:
	s->s_dev = 0;
	unlock_super(s);
	MOD_DEC_USE_COUNT;
	return 0;
}
 
static int check_warn(int not_ok,
		      const char *p1, const char *p2, const char *p3)
{
	if (not_ok)
		printk("HPFS: %s %s. Please %s\n", p1, p2, p3);
	return not_ok;
}
 
static int zerop(void *addr, unsigned len)
{
	unsigned char *p = addr;
	return p[0] == 0 && memcmp(p, p + 1, len - 1) == 0;
}
 
/*
 * A tiny parser for option strings, stolen from dosfs.
 */
 
static int parse_opts(char *opts, uid_t *uid, gid_t *gid, umode_t *umask,
		      int *lowercase, int *conv, int *nocheck)
{
	char *p, *rhs;
 
	*uid = current->uid;
	*gid = current->gid;
	*umask = current->fs->umask;
	*lowercase = 1;
	*conv = CONV_BINARY;
        *nocheck = 0;
 
	if (!opts)
		return 1;
 
	for (p = strtok(opts, ","); p != 0; p = strtok(0, ",")) {
		if ((rhs = strchr(p, '=')) != 0)
			*rhs++ = '\0';
		if (!strcmp(p, "uid")) {
			if (!rhs || !*rhs)
				return 0;
			*uid = simple_strtoul(rhs, &rhs, 0);
			if (*rhs)
				return 0;
		}
		else if (!strcmp(p, "gid")) {
			if (!rhs || !*rhs)
				return 0;
			*gid = simple_strtoul(rhs, &rhs, 0);
			if (*rhs)
				return 0;
		}
		else if (!strcmp(p, "umask")) {
			if (!rhs || !*rhs)
				return 0;
			*umask = simple_strtoul(rhs, &rhs, 8);
			if (*rhs)
				return 0;
		}
		else if (!strcmp(p, "case")) {
			if (!strcmp(rhs, "lower"))
				*lowercase = 1;
			else if (!strcmp(rhs, "asis"))
				*lowercase = 0;
			else
				return 0;
		}
		else if (!strcmp(p, "conv")) {
			if (!strcmp(rhs, "binary"))
				*conv = CONV_BINARY;
			else if (!strcmp(rhs, "text"))
				*conv = CONV_TEXT;
			else if (!strcmp(rhs, "auto"))
				*conv = CONV_AUTO;
			else
				return 0;
		}
		else if (!strcmp(p,"nocheck")) 
			*nocheck=1;
		else
			return 1;
	}
 
	return 1;
}
 
/*
 * read_inode.  This is called with exclusive access to a new inode that
 * has only (i_dev,i_ino) set.  It is responsible for filling in the rest.
 * We leave the dates blank, to be filled in from the dir entry.
 *
 * NOTE that there must be no sleeping from the return in this routine
 * until lookup() finishes filling in the inode, otherwise the partly
 * completed inode would be visible during the sleep.
 *
 * It is done in this strange and sinful way because the alternative
 * is to read the fnode, find the dir pointer in it, read that fnode
 * to get the dnode pointer, search through that whole directory for
 * the ino we're reading, and get the dates.  It works that way, but
 * ls sounds like fsck.
 */
 
static void hpfs_read_inode(struct inode *inode)
{
	struct super_block *s = inode->i_sb;
 
	/* be ready to bail out */
 
	inode->i_op = 0;
	inode->i_mode = 0;
 
	if (inode->i_ino == 0
	    || ino_secno(inode->i_ino) >= inode->i_sb->s_hpfs_fs_size) {
		printk("HPFS: read_inode: bad ino\n");
		return;
	}
 
	/*
	 * canned stuff
	 */
 
	inode->i_uid = s->s_hpfs_uid;
	inode->i_gid = s->s_hpfs_gid;
	inode->i_mode = s->s_hpfs_mode;
	inode->i_hpfs_conv = s->s_hpfs_conv;
 
	inode->i_hpfs_dno = 0;
	inode->i_hpfs_n_secs = 0;
	inode->i_hpfs_file_sec = 0;
	inode->i_hpfs_disk_sec = 0;
	inode->i_hpfs_dpos = 0;
	inode->i_hpfs_dsubdno = 0;
 
	/*
	 * figure out whether we are looking at a directory or a file
	 */
 
	if (ino_is_dir(inode->i_ino))
		inode->i_mode |= S_IFDIR;
	else {
		inode->i_mode |= S_IFREG;
		inode->i_mode &= ~0111;
	}
 
	/*
	 * these fields must be filled in from the dir entry, which we don't
	 * have but lookup does.  It will fill them in before letting the
	 * inode out of its grasp.
	 */
 
	inode->i_atime = 0;
	inode->i_mtime = 0;
	inode->i_ctime = 0;
	inode->i_size = 0;
 
	/*
	 * fill in the rest
	 */
 
	if (S_ISREG(inode->i_mode)) {
 
		inode->i_op = (struct inode_operations *) &hpfs_file_iops;
		inode->i_nlink = 1;
		inode->i_blksize = 512;
 
	}
	else {
		unsigned n_dnodes, n_subdirs;
		struct buffer_head *bh0;
		struct fnode *fnode = map_fnode(inode->i_dev,
						inode->i_ino, &bh0);
 
		if (!fnode) {
			printk("HPFS: read_inode: no fnode\n");
			inode->i_mode = 0;
			return;
		}
 
		inode->i_hpfs_parent_dir = dir_ino(fnode->up);
		inode->i_hpfs_dno = fnode->u.external[0].disk_secno;
 
		brelse(bh0);
 
		n_dnodes = n_subdirs = 0;
		count_dnodes(inode, inode->i_hpfs_dno, &n_dnodes, &n_subdirs);
 
		inode->i_op = (struct inode_operations *) &hpfs_dir_iops;
		inode->i_blksize = 512;	/* 2048 here confuses ls & du & ... */
		inode->i_blocks = 4 * n_dnodes;
		inode->i_size = 512 * inode->i_blocks;
		inode->i_nlink = 2 + n_subdirs;
	}
}
 
/*
 * unmount.
 */
 
static void hpfs_put_super(struct super_block *s)
{
	lock_super(s);
	s->s_dev = 0;
	unlock_super(s);
	MOD_DEC_USE_COUNT;
}
 
/*
 * statfs.  For free inode counts we report the count of dnodes in the
 * directory band -- not exactly right but pretty analogous.
 */
 
static void hpfs_statfs(struct super_block *s, struct statfs *buf, int bufsiz)
{
	struct statfs tmp;
 
	/*
	 * count the bits in the bitmaps, unless we already have
	 */
	if (s->s_hpfs_n_free == -1) {
		s->s_hpfs_n_free = count_bitmap(s);
		s->s_hpfs_n_free_dnodes =
		    count_one_bitmap(s->s_dev, s->s_hpfs_dmap);
	}
 
	/*
	 * fill in the user statfs struct
	 */
	tmp.f_type = s->s_magic;
	tmp.f_bsize = 512;
	tmp.f_blocks = s->s_hpfs_fs_size;
	tmp.f_bfree = s->s_hpfs_n_free;
	tmp.f_bavail = s->s_hpfs_n_free;
	tmp.f_files = s->s_hpfs_dirband_size;
	tmp.f_ffree = s->s_hpfs_n_free_dnodes;
	tmp.f_namelen = 254;
	memcpy_tofs(buf, &tmp, bufsiz);
}
 
/*
 * remount.  Don't let read only be turned off.
 */
 
static int hpfs_remount_fs(struct super_block *s, int *flags, char *data)
{
	if (!(*flags & MS_RDONLY))
		return -EINVAL;
	return 0;
}
 
/*
 * count the dnodes in a directory, and the subdirs.
 */
 
static void count_dnodes(struct inode *inode, dnode_secno dno,
			 unsigned *n_dnodes, unsigned *n_subdirs)
{
	struct quad_buffer_head qbh;
	struct dnode *dnode;
	struct hpfs_dirent *de;
	struct hpfs_dirent *de_end;
 
	dnode = map_dnode(inode->i_dev, dno, &qbh);
	if (!dnode)
		return;
	de = dnode_first_de(dnode);
	de_end = dnode_end_de(dnode);
 
	(*n_dnodes)++;
 
	for (; de < de_end; de = de_next_de(de)) {
		if (de->down)
			count_dnodes(inode, de_down_pointer(de),
				     n_dnodes, n_subdirs);
		if (de->directory && !de->first)
			(*n_subdirs)++;
		if (de->last || de->length == 0)
			break;
	}
 
	brelse4(&qbh);
}
 
/*
 * count the bits in the free space bit maps
 */
 
static unsigned count_bitmap(struct super_block *s)
{
	unsigned n, count, n_bands;
	secno *bitmaps;
	struct quad_buffer_head qbh;
 
	/*
	 * there is one bit map for each 16384 sectors
	 */
	n_bands = (s->s_hpfs_fs_size + 0x3fff) >> 14;
 
	/*
	 * their locations are given in an array pointed to by the super
	 * block
	 */
	bitmaps = map_4sectors(s->s_dev, s->s_hpfs_bitmaps, &qbh);
	if (!bitmaps)
		return 0;
 
	count = 0;
 
	/*
	 * map each one and count the free sectors
	 */
	for (n = 0; n < n_bands; n++)
		if (bitmaps[n] == 0)
			printk("HPFS: bit map pointer missing\n");
		else
			count += count_one_bitmap(s->s_dev, bitmaps[n]);
 
	brelse4(&qbh);
	return count;
}
 
/*
 * Read in one bit map, count the bits, return the count.
 */
 
static unsigned count_one_bitmap(kdev_t dev, secno secno)
{
	struct quad_buffer_head qbh;
	char *bits;
	unsigned i, count;
 
	bits = map_4sectors(dev, secno, &qbh);
	if (!bits)
		return 0;
 
	count = 0;
 
	for (i = 0; i < 8 * 2048; i++)
		count += (test_bit(i, bits) != 0);
	brelse4(&qbh);
 
	return count;
}

/* file ops */
 
/*
 * read.  Read the bytes, put them in buf, return the count.
 */
 
static int hpfs_file_read(struct inode *inode, struct file *filp,
			  char *buf, int count)
{
	unsigned q, r, n, n0;
	struct buffer_head *bh;
	char *block;
	char *start;
 
	if (inode == 0 || !S_ISREG(inode->i_mode))
		return -EINVAL;
 
	/*
	 * truncate count at EOF
	 */
	if (count > inode->i_size - (off_t) filp->f_pos)
		count = inode->i_size - filp->f_pos;
 
	start = buf;
	while (count > 0) {
		/*
		 * get file sector number, offset in sector, length to end of
		 * sector
		 */
		q = filp->f_pos >> 9;
		r = filp->f_pos & 511;
		n = 512 - r;
 
		/*
		 * get length to copy to user buffer
		 */
		if (n > count)
			n = count;
 
		/*
		 * read the sector, copy to user
		 */
		block = map_sector(inode->i_dev, hpfs_bmap(inode, q), &bh);
		if (!block)
			return -EIO;
 
		/*
		 * but first decide if it has \r\n, if the mount option said
		 * to do that
		 */
		if (inode->i_hpfs_conv == CONV_AUTO)
			inode->i_hpfs_conv = choose_conv(block + r, n);
 
		if (inode->i_hpfs_conv == CONV_BINARY) {
			/*
			 * regular copy, output length is same as input
			 * length
			 */
			memcpy_tofs(buf, block + r, n);
			n0 = n;
		}
		else {
			/*
			 * squeeze out \r, output length varies
			 */
			n0 = convcpy_tofs(buf, block + r, n);
			if (count > inode->i_size - (off_t) filp->f_pos - n + n0)
				count = inode->i_size - filp->f_pos - n + n0;
		}
 
		brelse(bh);
 
		/*
		 * advance input n bytes, output n0 bytes
		 */
		filp->f_pos += n;
		buf += n0;
		count -= n0;
	}
 
	return buf - start;
}
 
/*
 * This routine implements conv=auto.  Return CONV_BINARY or CONV_TEXT.
 */
 
static unsigned choose_conv(unsigned char *p, unsigned len)
{
	unsigned tvote, bvote;
	unsigned c;
 
	tvote = bvote = 0;
 
	while (len--) {
		c = *p++;
		if (c < ' ')
			if (c == '\r' && len && *p == '\n')
				tvote += 10;
			else if (c == '\t' || c == '\n');
			else
				bvote += 5;
		else if (c < '\177')
			tvote++;
		else
			bvote += 5;
	}
 
	if (tvote > bvote)
		return CONV_TEXT;
	else
		return CONV_BINARY;
}
 
/*
 * This routine implements conv=text.  :s/crlf/nl/
 */
 
static unsigned convcpy_tofs(unsigned char *out, unsigned char *in,
			     unsigned len)
{
	unsigned char *start = out;
 
	while (len--) {
		unsigned c = *in++;
		if (c == '\r' && (len == 0 || *in == '\n'));
		else
			put_user(c, out++);
	}
 
	return out - start;
}
 
/*
 * Return the disk sector number containing a file sector.
 */
 
static secno hpfs_bmap(struct inode *inode, unsigned file_secno)
{
	unsigned n, disk_secno;
	struct fnode *fnode;
	struct buffer_head *bh;
 
	/*
	 * There is one sector run cached in the inode. See if the sector is
	 * in it.
	 */
 
	n = file_secno - inode->i_hpfs_file_sec;
	if (n < inode->i_hpfs_n_secs)
		return inode->i_hpfs_disk_sec + n;
 
	/*
	 * No, read the fnode and go find the sector.
	 */
 
	else {
		fnode = map_fnode(inode->i_dev, inode->i_ino, &bh);
		if (!fnode)
			return 0;
		disk_secno = bplus_lookup(inode, &fnode->btree,
					  file_secno, &bh);
		brelse(bh);
		return disk_secno;
	}
}
 
/*
 * Search allocation tree *b for the given file sector number and return
 * the disk sector number.  Buffer *bhp has the tree in it, and can be
 * reused for subtrees when access to *b is no longer needed.
 * *bhp is busy on entry and exit. 
 */
 
static secno bplus_lookup(struct inode *inode, struct bplus_header *b,
			  secno file_secno, struct buffer_head **bhp)
{
	int i;
 
	/*
	 * A leaf-level tree gives a list of sector runs.  Find the one
	 * containing the file sector we want, cache the map info in the
	 * inode for later, and return the corresponding disk sector.
	 */
 
	if (!b->internal) {
		struct bplus_leaf_node *n = b->u.external;
		for (i = 0; i < b->n_used_nodes; i++) {
			unsigned t = file_secno - n[i].file_secno;
			if (t < n[i].length) {
				inode->i_hpfs_file_sec = n[i].file_secno;
				inode->i_hpfs_disk_sec = n[i].disk_secno;
				inode->i_hpfs_n_secs = n[i].length;
				return n[i].disk_secno + t;
			}
		}
	}
 
	/*
	 * A non-leaf tree gives a list of subtrees.  Find the one containing
	 * the file sector we want, read it in, and recurse to search it.
	 */
 
	else {
		struct bplus_internal_node *n = b->u.internal;
		for (i = 0; i < b->n_used_nodes; i++) {
			if (file_secno < n[i].file_secno) {
				struct anode *anode;
				anode_secno ano = n[i].down;
				brelse(*bhp);
				anode = map_anode(inode->i_dev, ano, bhp);
				if (!anode)
					break;
				return bplus_lookup(inode, &anode->btree,
						    file_secno, bhp);
			}
		}
	}
 
	/*
	 * If we get here there was a hole in the file.  As far as I know we
	 * never do get here, but falling off the end would be indelicate. So
	 * return a pointer to a handy all-zero sector.  This is not a
	 * reasonable way to handle files with holes if they really do
	 * happen.
	 */
 
	printk("HPFS: bplus_lookup: sector not found\n");
	return 15;
}

/* directory ops */
 
/*
 * lookup.  Search the specified directory for the specified name, set
 * *result to the corresponding inode.
 *
 * lookup uses the inode number to tell read_inode whether it is reading
 * the inode of a directory or a file -- file ino's are odd, directory
 * ino's are even.  read_inode avoids i/o for file inodes; everything
 * needed is up here in the directory.  (And file fnodes are out in
 * the boondocks.)
 */
 
static int hpfs_lookup(struct inode *dir, const char *name, int len,
		       struct inode **result)
{
	struct quad_buffer_head qbh;
	struct hpfs_dirent *de;
	struct inode *inode;
	ino_t ino;
 
	/* In case of madness */
 
	*result = 0;
	if (dir == 0)
		return -ENOENT;
	if (!S_ISDIR(dir->i_mode))
		goto bail;
 
	/*
	 * Read in the directory entry. "." is there under the name ^A^A .
	 * Always read the dir even for . and .. in case we need the dates.
	 */
 
	if (name[0] == '.' && len == 1)
		de = map_dirent(dir, dir->i_hpfs_dno, "\001\001", 2, &qbh);
	else if (name[0] == '.' && name[1] == '.' && len == 2)
		de = map_dirent(dir,
				fnode_dno(dir->i_dev, dir->i_hpfs_parent_dir),
				"\001\001", 2, &qbh);
	else
		de = map_dirent(dir, dir->i_hpfs_dno, name, len, &qbh);
 
	/*
	 * This is not really a bailout, just means file not found.
	 */
 
	if (!de)
		goto bail;
 
	/*
	 * Get inode number, what we're after.
	 */
 
	if (de->directory)
		ino = dir_ino(de->fnode);
	else
		ino = file_ino(de->fnode);
 
	/*
	 * Go find or make an inode.
	 */
 
	if (!(inode = iget(dir->i_sb, ino)))
		goto bail1;
 
	/*
	 * Fill in the info from the directory if this is a newly created
	 * inode.
	 */
 
	if (!inode->i_atime) {
		inode->i_atime = local_to_gmt(de->read_date);
		inode->i_mtime = local_to_gmt(de->write_date);
		inode->i_ctime = local_to_gmt(de->creation_date);
		if (de->read_only)
			inode->i_mode &= ~0222;
		if (!de->directory) {
			inode->i_size = de->file_size;
			/*
			 * i_blocks should count the fnode and any anodes.
			 * We count 1 for the fnode and don't bother about
			 * anodes -- the disk heads are on the directory band
			 * and we want them to stay there.
			 */
			inode->i_blocks = 1 + ((inode->i_size + 511) >> 9);
		}
	}
 
	brelse4(&qbh);
 
	/*
	 * Made it.
	 */
 
	*result = inode;
	iput(dir);
	return 0;
 
	/*
	 * Didn't.
	 */
 bail1:
	brelse4(&qbh);
 bail:
	iput(dir);
	return -ENOENT;
}
 
/*
 * Compare two counted strings ignoring case.
 * HPFS directory order sorts letters as if they're upper case.
 */
 
static inline int memcasecmp(const unsigned char *s1, const unsigned char *s2,
			     unsigned n)
{
	int t;
 
	if (n != 0)
		do {
			unsigned c1 = linux_char_to_upper_linux (*s1++);
			unsigned c2 = hpfs_char_to_upper_linux (*s2++);
			if ((t = c1 - c2) != 0)
				return t;
		} while (--n != 0);
 
	return 0;
}
 
/*
 * Search a directory for the given name, return a pointer to its dir entry
 * and a pointer to the buffer containing it.
 */
 
static struct hpfs_dirent *map_dirent(struct inode *inode, dnode_secno dno,
				      const unsigned char *name, unsigned len,
				      struct quad_buffer_head *qbh)
{
	struct dnode *dnode;
	struct hpfs_dirent *de;
	struct hpfs_dirent *de_end;
	int t, l;
 
	/*
	 * read the dnode at the root of our subtree
	 */
	dnode = map_dnode(inode->i_dev, dno, qbh);
	if (!dnode)
		return 0;
 
	/*
	 * get pointers to start and end+1 of dir entries
	 */
	de = dnode_first_de(dnode);
	de_end = dnode_end_de(dnode);
 
	/*
	 * look through the entries for the name we're after
	 */
	for ( ; de < de_end; de = de_next_de(de)) {
 
		/*
		 * compare names
		 */
		l = len < de->namelen ? len : de->namelen;
		t = memcasecmp(name, de->name, l);
 
		/*
		 * initial substring matches, compare lengths
		 */
		if (t == 0) {
			t = len - de->namelen;
			/* bingo */
			if (t == 0)
				return de;
		}
 
		/*
		 * wanted name .lt. dir name => not present.
		 */
		if (t < 0) {
			/*
			 * if there is a subtree, search it.
			 */
			if (de->down) {
				dnode_secno sub_dno = de_down_pointer(de);
				brelse4(qbh);
				return map_dirent(inode, sub_dno,
						  name, len, qbh);
			}
			else
				break;
		}
 
		/*
		 * de->last is set on the last name in the dnode (it's always
		 * a "\377" pseudo entry).  de->length == 0 means we're about
		 * to infinite loop. This test does nothing in a well-formed
		 * dnode.
		 */
		if (de->last || de->length == 0)
			break;
	}
 
	/*
	 * name not found.
	 */
	brelse4(qbh);
	return 0;
}
 
/*
 * readdir.  Return exactly 1 dirent.  (I tried and tried, but currently
 * the interface with libc just does not permit more than 1.  If it gets
 * fixed, throw this out and just walk the tree and write records into
 * the user buffer.)
 *
 * [ we now can handle multiple dirents, although the current libc doesn't
 *   use that. The way hpfs does this is pretty strange, as we need to do
 *   the name translation etc before calling "filldir()". This is untested,
 *   as I don't have any hpfs partitions to test against.   Linus ]
 *
 * We keep track of our position in the dnode tree with a sort of
 * dewey-decimal record of subtree locations.  Like so:
 *
 *   (1 (1.1 1.2 1.3) 2 3 (3.1 (3.1.1 3.1.2) 3.2 3.3 (3.3.1)) 4)
 *
 * Subtrees appear after their file, out of lexical order,
 * which would be before their file.  It's easier.
 *
 * A directory can't hold more than 56 files, so 6 bits are used for
 * position numbers.  If the tree is so deep that the position encoding
 * doesn't fit, I'm sure something absolutely fascinating happens.
 *
 * The actual sequence of f_pos values is
 *     0 => .   -1 => ..   1 1.1 ... 8.9 9 => files  -2 => eof
 *
 * The directory inode caches one position-to-dnode correspondence so
 * we won't have to repeatedly scan the top levels of the tree. 
 */
 
/*
 * Translate the given name: Blam it to lowercase if the mount option said to.
 */
 
static void translate_hpfs_name(const unsigned char * from, int len, char * to, int lowercase)
{
	while (len > 0) {
		unsigned t = *from;
		len--;
		if (lowercase)
			t = hpfs_char_to_lower_linux (t);
		else
			t = hpfs_char_to_linux (t);
		*to = t;
		from++;
		to++;
	}
}
 
static int hpfs_readdir(struct inode *inode, struct file *filp, void * dirent,
	filldir_t filldir)
{
	struct quad_buffer_head qbh;
	struct hpfs_dirent *de;
	int namelen, lc;
	ino_t ino;
	char * tempname;
	long old_pos;
 
	if (inode == 0
	    || inode->i_sb == 0
	    || !S_ISDIR(inode->i_mode))
		return -EBADF;
 
	tempname = (char *) __get_free_page(GFP_KERNEL);
	if (!tempname)
		return -ENOMEM;
 
	lc = inode->i_sb->s_hpfs_lowercase;
	switch ((long) filp->f_pos) {
	case -2:
		break;
 
	case 0:
		if (filldir(dirent, ".", 1, filp->f_pos, inode->i_ino) < 0)
			break;
		filp->f_pos = -1;
		/* fall through */
 
	case -1:
		if (filldir(dirent, "..", 2, filp->f_pos, inode->i_hpfs_parent_dir) < 0)
			break;
		filp->f_pos = 1;
		/* fall through */
 
	default:
		for (;;) {
			old_pos = filp->f_pos;
			de = map_pos_dirent(inode, &filp->f_pos, &qbh);
			if (!de) {
				filp->f_pos = -2;
				break;
			}
			namelen = de->namelen;
			translate_hpfs_name(de->name, namelen, tempname, lc);
			if (de->directory)
				ino = dir_ino(de->fnode);
			else
				ino = file_ino(de->fnode);
			brelse4(&qbh);
			if (filldir(dirent, tempname, namelen, old_pos, ino) < 0) {
				filp->f_pos = old_pos;
				break;
			}
		}
	}
	free_page((unsigned long) tempname);
	return 0;
}
 
/*
 * Map the dir entry at subtree coordinates given by *posp, and
 * increment *posp to point to the following dir entry. 
 */
 
static struct hpfs_dirent *map_pos_dirent(struct inode *inode, loff_t *posp,
					  struct quad_buffer_head *qbh)
{
	unsigned pos, q, r;
	dnode_secno dno;
	struct hpfs_dirent *de;
 
	/*
	 * Get the position code and split off the rightmost index r
	 */
 
	pos = *posp;
	q = pos >> 6;
	r = pos & 077;
 
	/*
	 * Get the sector address of the dnode
	 * pointed to by the leading part q
	 */
 
	dno = dir_subdno(inode, q);
	if (!dno)
		return 0;
 
	/*
	 * Get the entry at index r in dnode q
	 */
 
	de = map_nth_dirent(inode->i_dev, dno, r, qbh);
 
	/*
	 * If none, we're out of files in this dnode.  Ascend.
	 */
 
	if (!de) {
		if (q == 0)
			return 0;
		*posp = q + 1;
		return map_pos_dirent(inode, posp, qbh);
	}
 
	/*
	 * If a subtree is here, descend.
	 */
 
	if (de->down)
		*posp = pos << 6 | 1;
	else
		*posp = pos + 1;
 
	/*
	 * Don't return the ^A^A and \377 entries.
	 */
 
	if (de->first || de->last) {
		brelse4(qbh);
		return map_pos_dirent(inode, posp, qbh);
	}
	else
		return de;
}
 
/*
 * Return the address of the dnode with subtree coordinates given by pos.
 */
 
static dnode_secno dir_subdno(struct inode *inode, unsigned pos)
{
	struct hpfs_dirent *de;
	struct quad_buffer_head qbh;
 
	/*
	 * 0 is the root dnode
	 */
 
	if (pos == 0)
		return inode->i_hpfs_dno;
 
	/*
	 * we have one pos->dnode translation cached in the inode
	 */
 
	else if (pos == inode->i_hpfs_dpos)
		return inode->i_hpfs_dsubdno;
 
	/*
	 * otherwise go look
	 */
 
	else {
		unsigned q = pos >> 6;
		unsigned r = pos & 077;
		dnode_secno dno;
 
		/*
		 * dnode at position q
		 */
		dno = dir_subdno(inode, q);
		if (dno == 0)
			return 0;
 
		/*
		 * entry at index r
		 */
		de = map_nth_dirent(inode->i_dev, dno, r, &qbh);
		if (!de || !de->down)
			return 0;
 
		/*
		 * get the dnode down pointer
		 */
		dno = de_down_pointer(de);
		brelse4(&qbh);
 
		/*
		 * cache it for next time
		 */
		inode->i_hpfs_dpos = pos;
		inode->i_hpfs_dsubdno = dno;
		return dno;
	}
}
 
/*
 * Return the dir entry at index n in dnode dno, or 0 if there isn't one
 */
 
static struct hpfs_dirent *map_nth_dirent(kdev_t dev, dnode_secno dno,
					  int n,
					  struct quad_buffer_head *qbh)
{
	int i;
	struct hpfs_dirent *de, *de_end;
	struct dnode *dnode = map_dnode(dev, dno, qbh);
 
	de = dnode_first_de(dnode);
	de_end = dnode_end_de(dnode);
 
	for (i = 1; de < de_end; i++, de = de_next_de(de)) {
		if (i == n)
			return de;
		if (de->last || de->length == 0)
			break;
	}
 
	brelse4(qbh);
	return 0;
}
 
static int hpfs_dir_read(struct inode *inode, struct file *filp,
			 char *buf, int count)
{
	return -EISDIR;
}

/* Return the dnode pointer in a directory fnode */
 
static dnode_secno fnode_dno(kdev_t dev, ino_t ino)
{
	struct buffer_head *bh;
	struct fnode *fnode;
	dnode_secno dno;
 
	fnode = map_fnode(dev, ino, &bh);
	if (!fnode)
		return 0;
 
	dno = fnode->u.external[0].disk_secno;
	brelse(bh);
	return dno;
}
 
/* Map an fnode into a buffer and return pointers to it and to the buffer. */
 
static struct fnode *map_fnode(kdev_t dev, ino_t ino, struct buffer_head **bhp)
{
	struct fnode *fnode;
 
	if (ino == 0) {
		printk("HPFS: missing fnode\n");
		return 0;
	}
 
	fnode = map_sector(dev, ino_secno(ino), bhp);
	if (fnode)
		if (fnode->magic != FNODE_MAGIC) {
			printk("HPFS: map_fnode: bad fnode pointer\n");
			brelse(*bhp);
			return 0;
		}
	return fnode;
}
 
/* Map an anode into a buffer and return pointers to it and to the buffer. */
 
static struct anode *map_anode(kdev_t dev, unsigned secno,
			       struct buffer_head **bhp)
{
	struct anode *anode;
 
	if (secno == 0) {
		printk("HPFS: missing anode\n");
		return 0;
	}
 
	anode = map_sector(dev, secno, bhp);
	if (anode)
		if (anode->magic != ANODE_MAGIC || anode->self != secno) {
			printk("HPFS: map_anode: bad anode pointer\n");
			brelse(*bhp);
			return 0;
		}
	return anode;
}
 
/* Map a dnode into a buffer and return pointers to it and to the buffer. */
 
static struct dnode *map_dnode(kdev_t dev, unsigned secno,
			       struct quad_buffer_head *qbh)
{
	struct dnode *dnode;
 
	if (secno == 0) {
		printk("HPFS: missing dnode\n");
		return 0;
	}
 
	dnode = map_4sectors(dev, secno, qbh);
	if (dnode)
		if (dnode->magic != DNODE_MAGIC || dnode->self != secno) {
			printk("HPFS: map_dnode: bad dnode pointer\n");
			brelse4(qbh);
			return 0;
		}
	return dnode;
}
 
/* Map a sector into a buffer and return pointers to it and to the buffer. */
 
static void *map_sector(kdev_t dev, unsigned secno, struct buffer_head **bhp)
{
	struct buffer_head *bh;
 
	if ((*bhp = bh = bread(dev, secno, 512)) != 0)
		return bh->b_data;
	else {
		printk("HPFS: map_sector: read error\n");
		return 0;
	}
}
 
/* Map 4 sectors into a 4buffer and return pointers to it and to the buffer. */
 
static void *map_4sectors(kdev_t dev, unsigned secno,
			  struct quad_buffer_head *qbh)
{
	struct buffer_head *bh;
	char *data;
 
	if (secno & 3) {
		printk("HPFS: map_4sectors: unaligned read\n");
		return 0;
	}
 
	qbh->data = data = kmalloc(2048, GFP_KERNEL);
	if (!data)
		goto bail;
 
	qbh->bh[0] = bh = breada(dev, secno, 512, 0, UINT_MAX);
	if (!bh)
		goto bail0;
	memcpy(data, bh->b_data, 512);
 
	qbh->bh[1] = bh = bread(dev, secno + 1, 512);
	if (!bh)
		goto bail1;
	memcpy(data + 512, bh->b_data, 512);
 
	qbh->bh[2] = bh = bread(dev, secno + 2, 512);
	if (!bh)
		goto bail2;
	memcpy(data + 2 * 512, bh->b_data, 512);
 
	qbh->bh[3] = bh = bread(dev, secno + 3, 512);
	if (!bh)
		goto bail3;
	memcpy(data + 3 * 512, bh->b_data, 512);
 
	return data;
 
 bail3:
	brelse(qbh->bh[2]);
 bail2:
	brelse(qbh->bh[1]);
 bail1:
	brelse(qbh->bh[0]);
 bail0:
	kfree_s(data, 2048);
 bail:
	printk("HPFS: map_4sectors: read error\n");
	return 0;
}
 
/* Deallocate a 4-buffer block */
 
static void brelse4(struct quad_buffer_head *qbh)
{
	brelse(qbh->bh[3]);
	brelse(qbh->bh[2]);
	brelse(qbh->bh[1]);
	brelse(qbh->bh[0]);
	kfree_s(qbh->data, 2048);
}
 
static struct file_system_type hpfs_fs_type = {
        hpfs_read_super, "hpfs", 1, NULL
};
 
int init_hpfs_fs(void)
{
        return register_filesystem(&hpfs_fs_type);
}
 
#ifdef MODULE
int init_module(void)
{
	int status;
 
	if ((status = init_hpfs_fs()) == 0)
		register_symtab(0);
	return status;
}
 
void cleanup_module(void)
{
        unregister_filesystem(&hpfs_fs_type);
}
 
#endif
 
 

Compare with Previous | Blame | View Log

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.