diff options
Diffstat (limited to 'fs/romfs/inode.c')
-rw-r--r-- | fs/romfs/inode.c | 648 |
1 files changed, 648 insertions, 0 deletions
diff --git a/fs/romfs/inode.c b/fs/romfs/inode.c new file mode 100644 index 00000000000..c74f382dabb --- /dev/null +++ b/fs/romfs/inode.c @@ -0,0 +1,648 @@ +/* + * ROMFS file system, Linux implementation + * + * Copyright (C) 1997-1999 Janos Farkas <chexum@shadow.banki.hu> + * + * Using parts of the minix filesystem + * Copyright (C) 1991, 1992 Linus Torvalds + * + * and parts of the affs filesystem additionally + * Copyright (C) 1993 Ray Burr + * Copyright (C) 1996 Hans-Joachim Widmaier + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Changes + * Changed for 2.1.19 modules + * Jan 1997 Initial release + * Jun 1997 2.1.43+ changes + * Proper page locking in readpage + * Changed to work with 2.1.45+ fs + * Jul 1997 Fixed follow_link + * 2.1.47 + * lookup shouldn't return -ENOENT + * from Horst von Brand: + * fail on wrong checksum + * double unlock_super was possible + * correct namelen for statfs + * spotted by Bill Hawes: + * readlink shouldn't iput() + * Jun 1998 2.1.106 from Avery Pennarun: glibc scandir() + * exposed a problem in readdir + * 2.1.107 code-freeze spellchecker run + * Aug 1998 2.1.118+ VFS changes + * Sep 1998 2.1.122 another VFS change (follow_link) + * Apr 1999 2.2.7 no more EBADF checking in + * lookup/readdir, use ERR_PTR + * Jun 1999 2.3.6 d_alloc_root use changed + * 2.3.9 clean up usage of ENOENT/negative + * dentries in lookup + * clean up page flags setting + * (error, uptodate, locking) in + * in readpage + * use init_special_inode for + * fifos/sockets (and streamline) in + * read_inode, fix _ops table order + * Aug 1999 2.3.16 __initfunc() => __init change + * Oct 1999 2.3.24 page->owner hack obsoleted + * Nov 1999 2.3.27 2.3.25+ page->offset => index change + */ + +/* todo: + * - see Documentation/filesystems/romfs.txt + * - use allocated, not stack memory for file names? + * - considering write access... + * - network (tftp) files? + * - merge back some _op tables + */ + +/* + * Sorry about some optimizations and for some goto's. I just wanted + * to squeeze some more bytes out of this code.. :) + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/romfs_fs.h> +#include <linux/fs.h> +#include <linux/init.h> +#include <linux/pagemap.h> +#include <linux/smp_lock.h> +#include <linux/buffer_head.h> +#include <linux/vfs.h> + +#include <asm/uaccess.h> + +struct romfs_inode_info { + unsigned long i_metasize; /* size of non-data area */ + unsigned long i_dataoffset; /* from the start of fs */ + struct inode vfs_inode; +}; + +/* instead of private superblock data */ +static inline unsigned long romfs_maxsize(struct super_block *sb) +{ + return (unsigned long)sb->s_fs_info; +} + +static inline struct romfs_inode_info *ROMFS_I(struct inode *inode) +{ + return list_entry(inode, struct romfs_inode_info, vfs_inode); +} + +static __u32 +romfs_checksum(void *data, int size) +{ + __u32 sum; + __be32 *ptr; + + sum = 0; ptr = data; + size>>=2; + while (size>0) { + sum += be32_to_cpu(*ptr++); + size--; + } + return sum; +} + +static struct super_operations romfs_ops; + +static int romfs_fill_super(struct super_block *s, void *data, int silent) +{ + struct buffer_head *bh; + struct romfs_super_block *rsb; + struct inode *root; + int sz; + + /* I would parse the options here, but there are none.. :) */ + + sb_set_blocksize(s, ROMBSIZE); + s->s_maxbytes = 0xFFFFFFFF; + + bh = sb_bread(s, 0); + if (!bh) { + /* XXX merge with other printk? */ + printk ("romfs: unable to read superblock\n"); + goto outnobh; + } + + rsb = (struct romfs_super_block *)bh->b_data; + sz = be32_to_cpu(rsb->size); + if (rsb->word0 != ROMSB_WORD0 || rsb->word1 != ROMSB_WORD1 + || sz < ROMFH_SIZE) { + if (!silent) + printk ("VFS: Can't find a romfs filesystem on dev " + "%s.\n", s->s_id); + goto out; + } + if (romfs_checksum(rsb, min_t(int, sz, 512))) { + printk ("romfs: bad initial checksum on dev " + "%s.\n", s->s_id); + goto out; + } + + s->s_magic = ROMFS_MAGIC; + s->s_fs_info = (void *)(long)sz; + + s->s_flags |= MS_RDONLY; + + /* Find the start of the fs */ + sz = (ROMFH_SIZE + + strnlen(rsb->name, ROMFS_MAXFN) + 1 + ROMFH_PAD) + & ROMFH_MASK; + + s->s_op = &romfs_ops; + root = iget(s, sz); + if (!root) + goto out; + + s->s_root = d_alloc_root(root); + if (!s->s_root) + goto outiput; + + brelse(bh); + return 0; + +outiput: + iput(root); +out: + brelse(bh); +outnobh: + return -EINVAL; +} + +/* That's simple too. */ + +static int +romfs_statfs(struct super_block *sb, struct kstatfs *buf) +{ + buf->f_type = ROMFS_MAGIC; + buf->f_bsize = ROMBSIZE; + buf->f_bfree = buf->f_bavail = buf->f_ffree; + buf->f_blocks = (romfs_maxsize(sb)+ROMBSIZE-1)>>ROMBSBITS; + buf->f_namelen = ROMFS_MAXFN; + return 0; +} + +/* some helper routines */ + +static int +romfs_strnlen(struct inode *i, unsigned long offset, unsigned long count) +{ + struct buffer_head *bh; + unsigned long avail, maxsize, res; + + maxsize = romfs_maxsize(i->i_sb); + if (offset >= maxsize) + return -1; + + /* strnlen is almost always valid */ + if (count > maxsize || offset+count > maxsize) + count = maxsize-offset; + + bh = sb_bread(i->i_sb, offset>>ROMBSBITS); + if (!bh) + return -1; /* error */ + + avail = ROMBSIZE - (offset & ROMBMASK); + maxsize = min_t(unsigned long, count, avail); + res = strnlen(((char *)bh->b_data)+(offset&ROMBMASK), maxsize); + brelse(bh); + + if (res < maxsize) + return res; /* found all of it */ + + while (res < count) { + offset += maxsize; + + bh = sb_bread(i->i_sb, offset>>ROMBSBITS); + if (!bh) + return -1; + maxsize = min_t(unsigned long, count - res, ROMBSIZE); + avail = strnlen(bh->b_data, maxsize); + res += avail; + brelse(bh); + if (avail < maxsize) + return res; + } + return res; +} + +static int +romfs_copyfrom(struct inode *i, void *dest, unsigned long offset, unsigned long count) +{ + struct buffer_head *bh; + unsigned long avail, maxsize, res; + + maxsize = romfs_maxsize(i->i_sb); + if (offset >= maxsize || count > maxsize || offset+count>maxsize) + return -1; + + bh = sb_bread(i->i_sb, offset>>ROMBSBITS); + if (!bh) + return -1; /* error */ + + avail = ROMBSIZE - (offset & ROMBMASK); + maxsize = min_t(unsigned long, count, avail); + memcpy(dest, ((char *)bh->b_data) + (offset & ROMBMASK), maxsize); + brelse(bh); + + res = maxsize; /* all of it */ + + while (res < count) { + offset += maxsize; + dest += maxsize; + + bh = sb_bread(i->i_sb, offset>>ROMBSBITS); + if (!bh) + return -1; + maxsize = min_t(unsigned long, count - res, ROMBSIZE); + memcpy(dest, bh->b_data, maxsize); + brelse(bh); + res += maxsize; + } + return res; +} + +static unsigned char romfs_dtype_table[] = { + DT_UNKNOWN, DT_DIR, DT_REG, DT_LNK, DT_BLK, DT_CHR, DT_SOCK, DT_FIFO +}; + +static int +romfs_readdir(struct file *filp, void *dirent, filldir_t filldir) +{ + struct inode *i = filp->f_dentry->d_inode; + struct romfs_inode ri; + unsigned long offset, maxoff; + int j, ino, nextfh; + int stored = 0; + char fsname[ROMFS_MAXFN]; /* XXX dynamic? */ + + lock_kernel(); + + maxoff = romfs_maxsize(i->i_sb); + + offset = filp->f_pos; + if (!offset) { + offset = i->i_ino & ROMFH_MASK; + if (romfs_copyfrom(i, &ri, offset, ROMFH_SIZE) <= 0) + goto out; + offset = be32_to_cpu(ri.spec) & ROMFH_MASK; + } + + /* Not really failsafe, but we are read-only... */ + for(;;) { + if (!offset || offset >= maxoff) { + offset = maxoff; + filp->f_pos = offset; + goto out; + } + filp->f_pos = offset; + + /* Fetch inode info */ + if (romfs_copyfrom(i, &ri, offset, ROMFH_SIZE) <= 0) + goto out; + + j = romfs_strnlen(i, offset+ROMFH_SIZE, sizeof(fsname)-1); + if (j < 0) + goto out; + + fsname[j]=0; + romfs_copyfrom(i, fsname, offset+ROMFH_SIZE, j); + + ino = offset; + nextfh = be32_to_cpu(ri.next); + if ((nextfh & ROMFH_TYPE) == ROMFH_HRD) + ino = be32_to_cpu(ri.spec); + if (filldir(dirent, fsname, j, offset, ino, + romfs_dtype_table[nextfh & ROMFH_TYPE]) < 0) { + goto out; + } + stored++; + offset = nextfh & ROMFH_MASK; + } +out: + unlock_kernel(); + return stored; +} + +static struct dentry * +romfs_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd) +{ + unsigned long offset, maxoff; + int fslen, res; + struct inode *inode; + char fsname[ROMFS_MAXFN]; /* XXX dynamic? */ + struct romfs_inode ri; + const char *name; /* got from dentry */ + int len; + + res = -EACCES; /* placeholder for "no data here" */ + offset = dir->i_ino & ROMFH_MASK; + lock_kernel(); + if (romfs_copyfrom(dir, &ri, offset, ROMFH_SIZE) <= 0) + goto out; + + maxoff = romfs_maxsize(dir->i_sb); + offset = be32_to_cpu(ri.spec) & ROMFH_MASK; + + /* OK, now find the file whose name is in "dentry" in the + * directory specified by "dir". */ + + name = dentry->d_name.name; + len = dentry->d_name.len; + + for(;;) { + if (!offset || offset >= maxoff) + goto out0; + if (romfs_copyfrom(dir, &ri, offset, ROMFH_SIZE) <= 0) + goto out; + + /* try to match the first 16 bytes of name */ + fslen = romfs_strnlen(dir, offset+ROMFH_SIZE, ROMFH_SIZE); + if (len < ROMFH_SIZE) { + if (len == fslen) { + /* both are shorter, and same size */ + romfs_copyfrom(dir, fsname, offset+ROMFH_SIZE, len+1); + if (strncmp (name, fsname, len) == 0) + break; + } + } else if (fslen >= ROMFH_SIZE) { + /* both are longer; XXX optimize max size */ + fslen = romfs_strnlen(dir, offset+ROMFH_SIZE, sizeof(fsname)-1); + if (len == fslen) { + romfs_copyfrom(dir, fsname, offset+ROMFH_SIZE, len+1); + if (strncmp(name, fsname, len) == 0) + break; + } + } + /* next entry */ + offset = be32_to_cpu(ri.next) & ROMFH_MASK; + } + + /* Hard link handling */ + if ((be32_to_cpu(ri.next) & ROMFH_TYPE) == ROMFH_HRD) + offset = be32_to_cpu(ri.spec) & ROMFH_MASK; + + if ((inode = iget(dir->i_sb, offset))) + goto outi; + + /* + * it's a bit funky, _lookup needs to return an error code + * (negative) or a NULL, both as a dentry. ENOENT should not + * be returned, instead we need to create a negative dentry by + * d_add(dentry, NULL); and return 0 as no error. + * (Although as I see, it only matters on writable file + * systems). + */ + +out0: inode = NULL; +outi: res = 0; + d_add (dentry, inode); + +out: unlock_kernel(); + return ERR_PTR(res); +} + +/* + * Ok, we do readpage, to be able to execute programs. Unfortunately, + * we can't use bmap, since we may have looser alignments. + */ + +static int +romfs_readpage(struct file *file, struct page * page) +{ + struct inode *inode = page->mapping->host; + unsigned long offset, avail, readlen; + void *buf; + int result = -EIO; + + page_cache_get(page); + lock_kernel(); + buf = kmap(page); + if (!buf) + goto err_out; + + /* 32 bit warning -- but not for us :) */ + offset = page->index << PAGE_CACHE_SHIFT; + if (offset < inode->i_size) { + avail = inode->i_size-offset; + readlen = min_t(unsigned long, avail, PAGE_SIZE); + if (romfs_copyfrom(inode, buf, ROMFS_I(inode)->i_dataoffset+offset, readlen) == readlen) { + if (readlen < PAGE_SIZE) { + memset(buf + readlen,0,PAGE_SIZE-readlen); + } + SetPageUptodate(page); + result = 0; + } + } + if (result) { + memset(buf, 0, PAGE_SIZE); + SetPageError(page); + } + flush_dcache_page(page); + + unlock_page(page); + + kunmap(page); +err_out: + page_cache_release(page); + unlock_kernel(); + + return result; +} + +/* Mapping from our types to the kernel */ + +static struct address_space_operations romfs_aops = { + .readpage = romfs_readpage +}; + +static struct file_operations romfs_dir_operations = { + .read = generic_read_dir, + .readdir = romfs_readdir, +}; + +static struct inode_operations romfs_dir_inode_operations = { + .lookup = romfs_lookup, +}; + +static mode_t romfs_modemap[] = +{ + 0, S_IFDIR+0644, S_IFREG+0644, S_IFLNK+0777, + S_IFBLK+0600, S_IFCHR+0600, S_IFSOCK+0644, S_IFIFO+0644 +}; + +static void +romfs_read_inode(struct inode *i) +{ + int nextfh, ino; + struct romfs_inode ri; + + ino = i->i_ino & ROMFH_MASK; + i->i_mode = 0; + + /* Loop for finding the real hard link */ + for(;;) { + if (romfs_copyfrom(i, &ri, ino, ROMFH_SIZE) <= 0) { + printk("romfs: read error for inode 0x%x\n", ino); + return; + } + /* XXX: do romfs_checksum here too (with name) */ + + nextfh = be32_to_cpu(ri.next); + if ((nextfh & ROMFH_TYPE) != ROMFH_HRD) + break; + + ino = be32_to_cpu(ri.spec) & ROMFH_MASK; + } + + i->i_nlink = 1; /* Hard to decide.. */ + i->i_size = be32_to_cpu(ri.size); + i->i_mtime.tv_sec = i->i_atime.tv_sec = i->i_ctime.tv_sec = 0; + i->i_mtime.tv_nsec = i->i_atime.tv_nsec = i->i_ctime.tv_nsec = 0; + i->i_uid = i->i_gid = 0; + + /* Precalculate the data offset */ + ino = romfs_strnlen(i, ino+ROMFH_SIZE, ROMFS_MAXFN); + if (ino >= 0) + ino = ((ROMFH_SIZE+ino+1+ROMFH_PAD)&ROMFH_MASK); + else + ino = 0; + + ROMFS_I(i)->i_metasize = ino; + ROMFS_I(i)->i_dataoffset = ino+(i->i_ino&ROMFH_MASK); + + /* Compute permissions */ + ino = romfs_modemap[nextfh & ROMFH_TYPE]; + /* only "normal" files have ops */ + switch (nextfh & ROMFH_TYPE) { + case 1: + i->i_size = ROMFS_I(i)->i_metasize; + i->i_op = &romfs_dir_inode_operations; + i->i_fop = &romfs_dir_operations; + if (nextfh & ROMFH_EXEC) + ino |= S_IXUGO; + i->i_mode = ino; + break; + case 2: + i->i_fop = &generic_ro_fops; + i->i_data.a_ops = &romfs_aops; + if (nextfh & ROMFH_EXEC) + ino |= S_IXUGO; + i->i_mode = ino; + break; + case 3: + i->i_op = &page_symlink_inode_operations; + i->i_data.a_ops = &romfs_aops; + i->i_mode = ino | S_IRWXUGO; + break; + default: + /* depending on MBZ for sock/fifos */ + nextfh = be32_to_cpu(ri.spec); + init_special_inode(i, ino, + MKDEV(nextfh>>16,nextfh&0xffff)); + } +} + +static kmem_cache_t * romfs_inode_cachep; + +static struct inode *romfs_alloc_inode(struct super_block *sb) +{ + struct romfs_inode_info *ei; + ei = (struct romfs_inode_info *)kmem_cache_alloc(romfs_inode_cachep, SLAB_KERNEL); + if (!ei) + return NULL; + return &ei->vfs_inode; +} + +static void romfs_destroy_inode(struct inode *inode) +{ + kmem_cache_free(romfs_inode_cachep, ROMFS_I(inode)); +} + +static void init_once(void * foo, kmem_cache_t * cachep, unsigned long flags) +{ + struct romfs_inode_info *ei = (struct romfs_inode_info *) foo; + + if ((flags & (SLAB_CTOR_VERIFY|SLAB_CTOR_CONSTRUCTOR)) == + SLAB_CTOR_CONSTRUCTOR) + inode_init_once(&ei->vfs_inode); +} + +static int init_inodecache(void) +{ + romfs_inode_cachep = kmem_cache_create("romfs_inode_cache", + sizeof(struct romfs_inode_info), + 0, SLAB_RECLAIM_ACCOUNT, + init_once, NULL); + if (romfs_inode_cachep == NULL) + return -ENOMEM; + return 0; +} + +static void destroy_inodecache(void) +{ + if (kmem_cache_destroy(romfs_inode_cachep)) + printk(KERN_INFO "romfs_inode_cache: not all structures were freed\n"); +} + +static int romfs_remount(struct super_block *sb, int *flags, char *data) +{ + *flags |= MS_RDONLY; + return 0; +} + +static struct super_operations romfs_ops = { + .alloc_inode = romfs_alloc_inode, + .destroy_inode = romfs_destroy_inode, + .read_inode = romfs_read_inode, + .statfs = romfs_statfs, + .remount_fs = romfs_remount, +}; + +static struct super_block *romfs_get_sb(struct file_system_type *fs_type, + int flags, const char *dev_name, void *data) +{ + return get_sb_bdev(fs_type, flags, dev_name, data, romfs_fill_super); +} + +static struct file_system_type romfs_fs_type = { + .owner = THIS_MODULE, + .name = "romfs", + .get_sb = romfs_get_sb, + .kill_sb = kill_block_super, + .fs_flags = FS_REQUIRES_DEV, +}; + +static int __init init_romfs_fs(void) +{ + int err = init_inodecache(); + if (err) + goto out1; + err = register_filesystem(&romfs_fs_type); + if (err) + goto out; + return 0; +out: + destroy_inodecache(); +out1: + return err; +} + +static void __exit exit_romfs_fs(void) +{ + unregister_filesystem(&romfs_fs_type); + destroy_inodecache(); +} + +/* Yes, works even as a module... :) */ + +module_init(init_romfs_fs) +module_exit(exit_romfs_fs) +MODULE_LICENSE("GPL"); |