home *** CD-ROM | disk | FTP | other *** search
- /*
- * linux/fs/super.c
- *
- * Copyright (C) 1991, 1992 Linus Torvalds
- */
-
- /*
- * super.c contains code to handle the super-block tables.
- */
- #include <linux/config.h>
- #include <linux/sched.h>
- #include <linux/kernel.h>
- #include <linux/major.h>
- #include <linux/stat.h>
- #include <linux/errno.h>
- #include <linux/string.h>
- #include <linux/locks.h>
-
- #include <asm/system.h>
- #include <asm/segment.h>
-
-
- /*
- * The definition of file_systems that used to be here is now in
- * filesystems.c. Now super.c contains no fs specific code. -- jrs
- */
-
- extern struct file_system_type file_systems[];
- extern struct file_operations * get_blkfops(unsigned int);
- extern struct file_operations * get_chrfops(unsigned int);
-
- extern void wait_for_keypress(void);
- extern void fcntl_init_locks(void);
-
- extern int root_mountflags;
-
- struct super_block super_blocks[NR_SUPER];
-
- static int do_remount_sb(struct super_block *sb, int flags, char * data);
-
- /* this is initialized in init/main.c */
- dev_t ROOT_DEV = 0;
-
- struct file_system_type *get_fs_type(char *name)
- {
- int a;
-
- if (!name)
- return &file_systems[0];
- for(a = 0 ; file_systems[a].read_super ; a++)
- if (!strcmp(name,file_systems[a].name))
- return(&file_systems[a]);
- return NULL;
- }
-
- void __wait_on_super(struct super_block * sb)
- {
- struct wait_queue wait = { current, NULL };
-
- add_wait_queue(&sb->s_wait, &wait);
- repeat:
- current->state = TASK_UNINTERRUPTIBLE;
- if (sb->s_lock) {
- schedule();
- goto repeat;
- }
- remove_wait_queue(&sb->s_wait, &wait);
- current->state = TASK_RUNNING;
- }
-
- void sync_supers(dev_t dev)
- {
- struct super_block * sb;
-
- for (sb = super_blocks + 0 ; sb < super_blocks + NR_SUPER ; sb++) {
- if (!sb->s_dev)
- continue;
- if (dev && sb->s_dev != dev)
- continue;
- wait_on_super(sb);
- if (!sb->s_dev || !sb->s_dirt)
- continue;
- if (dev && (dev != sb->s_dev))
- continue;
- if (sb->s_op && sb->s_op->write_super)
- sb->s_op->write_super(sb);
- }
- }
-
- static struct super_block * get_super(dev_t dev)
- {
- struct super_block * s;
-
- if (!dev)
- return NULL;
- s = 0+super_blocks;
- while (s < NR_SUPER+super_blocks)
- if (s->s_dev == dev) {
- wait_on_super(s);
- if (s->s_dev == dev)
- return s;
- s = 0+super_blocks;
- } else
- s++;
- return NULL;
- }
-
- void put_super(dev_t dev)
- {
- struct super_block * sb;
-
- if (dev == ROOT_DEV) {
- printk("VFS: Root device %d/%d: prepare for armageddon\n",
- MAJOR(dev), MINOR(dev));
- return;
- }
- if (!(sb = get_super(dev)))
- return;
- if (sb->s_covered) {
- printk("VFS: Mounted device %d/%d - tssk, tssk\n",
- MAJOR(dev), MINOR(dev));
- return;
- }
- if (sb->s_op && sb->s_op->put_super)
- sb->s_op->put_super(sb);
- }
-
- static struct super_block * read_super(dev_t dev,char *name,int flags,
- void *data, int silent)
- {
- struct super_block * s;
- struct file_system_type *type;
-
- if (!dev)
- return NULL;
- check_disk_change(dev);
- s = get_super(dev);
- if (s)
- return s;
- if (!(type = get_fs_type(name))) {
- printk("VFS: on device %d/%d: get_fs_type(%s) failed\n",
- MAJOR(dev), MINOR(dev), name);
- return NULL;
- }
- for (s = 0+super_blocks ;; s++) {
- if (s >= NR_SUPER+super_blocks)
- return NULL;
- if (!s->s_dev)
- break;
- }
- s->s_dev = dev;
- s->s_flags = flags;
- if (!type->read_super(s,data, silent)) {
- s->s_dev = 0;
- return NULL;
- }
- s->s_dev = dev;
- s->s_covered = NULL;
- s->s_rd_only = 0;
- s->s_dirt = 0;
- return s;
- }
-
- /*
- * Unnamed block devices are dummy devices used by virtual
- * filesystems which don't use real block-devices. -- jrs
- */
-
- static char unnamed_dev_in_use[256];
-
- static dev_t get_unnamed_dev(void)
- {
- static int first_use = 0;
- int i;
-
- if (first_use == 0) {
- first_use = 1;
- memset(unnamed_dev_in_use, 0, sizeof(unnamed_dev_in_use));
- unnamed_dev_in_use[0] = 1; /* minor 0 (nodev) is special */
- }
- for (i = 0; i < sizeof unnamed_dev_in_use/sizeof unnamed_dev_in_use[0]; i++) {
- if (!unnamed_dev_in_use[i]) {
- unnamed_dev_in_use[i] = 1;
- return (UNNAMED_MAJOR << 8) | i;
- }
- }
- return 0;
- }
-
- static void put_unnamed_dev(dev_t dev)
- {
- if (!dev)
- return;
- if (!unnamed_dev_in_use[dev]) {
- printk("VFS: put_unnamed_dev: freeing unused device %d/%d\n",
- MAJOR(dev), MINOR(dev));
- return;
- }
- unnamed_dev_in_use[dev] = 0;
- }
-
- static int do_umount(dev_t dev)
- {
- struct super_block * sb;
- int retval;
-
- if (dev==ROOT_DEV) {
- /* Special case for "unmounting" root. We just try to remount
- it readonly, and sync() the device. */
- if (!(sb=get_super(dev)))
- return -ENOENT;
- if (!(sb->s_flags & MS_RDONLY)) {
- fsync_dev(dev);
- retval = do_remount_sb(sb, MS_RDONLY, 0);
- if (retval)
- return retval;
- }
- return 0;
- }
- if (!(sb=get_super(dev)) || !(sb->s_covered))
- return -ENOENT;
- if (!sb->s_covered->i_mount)
- printk("VFS: umount(%d/%d): mounted inode has i_mount=NULL\n",
- MAJOR(dev), MINOR(dev));
- if (!fs_may_umount(dev, sb->s_mounted))
- return -EBUSY;
- sb->s_covered->i_mount = NULL;
- iput(sb->s_covered);
- sb->s_covered = NULL;
- iput(sb->s_mounted);
- sb->s_mounted = NULL;
- if (sb->s_op && sb->s_op->write_super && sb->s_dirt)
- sb->s_op->write_super(sb);
- put_super(dev);
- return 0;
- }
-
- /*
- * Now umount can handle mount points as well as block devices.
- * This is important for filesystems which use unnamed block devices.
- *
- * There is a little kludge here with the dummy_inode. The current
- * vfs release functions only use the r_dev field in the inode so
- * we give them the info they need without using a real inode.
- * If any other fields are ever needed by any block device release
- * functions, they should be faked here. -- jrs
- */
-
- asmlinkage int sys_umount(char * name)
- {
- struct inode * inode;
- dev_t dev;
- int retval;
- struct inode dummy_inode;
- struct file_operations * fops;
-
- if (!suser())
- return -EPERM;
- retval = namei(name,&inode);
- if (retval) {
- retval = lnamei(name,&inode);
- if (retval)
- return retval;
- }
- if (S_ISBLK(inode->i_mode)) {
- dev = inode->i_rdev;
- if (IS_NODEV(inode)) {
- iput(inode);
- return -EACCES;
- }
- } else {
- if (!inode || !inode->i_sb || inode != inode->i_sb->s_mounted) {
- iput(inode);
- return -EINVAL;
- }
- dev = inode->i_sb->s_dev;
- iput(inode);
- memset(&dummy_inode, 0, sizeof(dummy_inode));
- dummy_inode.i_rdev = dev;
- inode = &dummy_inode;
- }
- if (MAJOR(dev) >= MAX_BLKDEV) {
- iput(inode);
- return -ENXIO;
- }
- if (!(retval = do_umount(dev)) && dev != ROOT_DEV) {
- fops = get_blkfops(MAJOR(dev));
- if (fops && fops->release)
- fops->release(inode,NULL);
- if (MAJOR(dev) == UNNAMED_MAJOR)
- put_unnamed_dev(dev);
- }
- if (inode != &dummy_inode)
- iput(inode);
- if (retval)
- return retval;
- fsync_dev(dev);
- return 0;
- }
-
- /*
- * do_mount() does the actual mounting after sys_mount has done the ugly
- * parameter parsing. When enough time has gone by, and everything uses the
- * new mount() parameters, sys_mount() can then be cleaned up.
- *
- * We cannot mount a filesystem if it has active, used, or dirty inodes.
- * We also have to flush all inode-data for this device, as the new mount
- * might need new info.
- */
- static int do_mount(dev_t dev, const char * dir, char * type, int flags, void * data)
- {
- struct inode * dir_i;
- struct super_block * sb;
- int error;
-
- error = namei(dir,&dir_i);
- if (error)
- return error;
- if (dir_i->i_count != 1 || dir_i->i_mount) {
- iput(dir_i);
- return -EBUSY;
- }
- if (!S_ISDIR(dir_i->i_mode)) {
- iput(dir_i);
- return -EPERM;
- }
- if (!fs_may_mount(dev)) {
- iput(dir_i);
- return -EBUSY;
- }
- sb = read_super(dev,type,flags,data,0);
- if (!sb || sb->s_covered) {
- iput(dir_i);
- return -EBUSY;
- }
- sb->s_covered = dir_i;
- dir_i->i_mount = sb->s_mounted;
- return 0; /* we don't iput(dir_i) - see umount */
- }
-
-
- /*
- * Alters the mount flags of a mounted file system. Only the mount point
- * is used as a reference - file system type and the device are ignored.
- * FS-specific mount options can't be altered by remounting.
- */
-
- static int do_remount_sb(struct super_block *sb, int flags, char *data)
- {
- int retval;
-
- /* If we are remounting RDONLY, make sure there are no rw files open */
- if ((flags & MS_RDONLY) && !(sb->s_flags & MS_RDONLY))
- if (!fs_may_remount_ro(sb->s_dev))
- return -EBUSY;
- if (sb->s_op && sb->s_op->remount_fs) {
- retval = sb->s_op->remount_fs(sb, &flags, data);
- if (retval)
- return retval;
- }
- sb->s_flags = (sb->s_flags & ~MS_RMT_MASK) |
- (flags & MS_RMT_MASK);
- return 0;
- }
-
- static int do_remount(const char *dir,int flags,char *data)
- {
- struct inode *dir_i;
- int retval;
-
- retval = namei(dir,&dir_i);
- if (retval)
- return retval;
- if (dir_i != dir_i->i_sb->s_mounted) {
- iput(dir_i);
- return -EINVAL;
- }
- retval = do_remount_sb(dir_i->i_sb, flags, data);
- iput(dir_i);
- return retval;
- }
-
- static int copy_mount_options (const void * data, unsigned long *where)
- {
- int i;
- unsigned long page;
- struct vm_area_struct * vma;
-
- *where = 0;
- if (!data)
- return 0;
-
- for (vma = current->mmap ; ; ) {
- if (!vma ||
- (unsigned long) data < vma->vm_start) {
- return -EFAULT;
- }
- if ((unsigned long) data < vma->vm_end)
- break;
- vma = vma->vm_next;
- }
- i = vma->vm_end - (unsigned long) data;
- if (PAGE_SIZE <= (unsigned long) i)
- i = PAGE_SIZE-1;
- if (!(page = __get_free_page(GFP_KERNEL))) {
- return -ENOMEM;
- }
- memcpy_fromfs((void *) page,data,i);
- *where = page;
- return 0;
- }
-
- /*
- * Flags is a 16-bit value that allows up to 16 non-fs dependent flags to
- * be given to the mount() call (ie: read-only, no-dev, no-suid etc).
- *
- * data is a (void *) that can point to any structure up to
- * PAGE_SIZE-1 bytes, which can contain arbitrary fs-dependent
- * information (or be NULL).
- *
- * NOTE! As old versions of mount() didn't use this setup, the flags
- * has to have a special 16-bit magic number in the hight word:
- * 0xC0ED. If this magic word isn't present, the flags and data info
- * isn't used, as the syscall assumes we are talking to an older
- * version that didn't understand them.
- */
- asmlinkage int sys_mount(char * dev_name, char * dir_name, char * type,
- unsigned long new_flags, void * data)
- {
- struct file_system_type * fstype;
- struct inode * inode;
- struct file_operations * fops;
- dev_t dev;
- int retval;
- char * t;
- unsigned long flags = 0;
- unsigned long page = 0;
-
- if (!suser())
- return -EPERM;
- if ((new_flags &
- (MS_MGC_MSK | MS_REMOUNT)) == (MS_MGC_VAL | MS_REMOUNT)) {
- retval = copy_mount_options (data, &page);
- if (retval < 0)
- return retval;
- retval = do_remount(dir_name,
- new_flags & ~MS_MGC_MSK & ~MS_REMOUNT,
- (char *) page);
- free_page(page);
- return retval;
- }
- retval = copy_mount_options (type, &page);
- if (retval < 0)
- return retval;
- fstype = get_fs_type((char *) page);
- free_page(page);
- if (!fstype)
- return -ENODEV;
- t = fstype->name;
- if (fstype->requires_dev) {
- retval = namei(dev_name,&inode);
- if (retval)
- return retval;
- if (!S_ISBLK(inode->i_mode)) {
- iput(inode);
- return -ENOTBLK;
- }
- if (IS_NODEV(inode)) {
- iput(inode);
- return -EACCES;
- }
- dev = inode->i_rdev;
- if (MAJOR(dev) >= MAX_BLKDEV) {
- iput(inode);
- return -ENXIO;
- }
- } else {
- if (!(dev = get_unnamed_dev()))
- return -EMFILE;
- inode = NULL;
- }
- fops = get_blkfops(MAJOR(dev));
- if (fops && fops->open) {
- retval = fops->open(inode,NULL);
- if (retval) {
- iput(inode);
- return retval;
- }
- }
- page = 0;
- if ((new_flags & MS_MGC_MSK) == MS_MGC_VAL) {
- flags = new_flags & ~MS_MGC_MSK;
- retval = copy_mount_options(data, &page);
- if (retval < 0) {
- iput(inode);
- return retval;
- }
- }
- retval = do_mount(dev,dir_name,t,flags,(void *) page);
- free_page(page);
- if (retval && fops && fops->release)
- fops->release(inode,NULL);
- iput(inode);
- return retval;
- }
-
- void mount_root(void)
- {
- struct file_system_type * fs_type;
- struct super_block * sb;
- struct inode * inode;
-
- memset(super_blocks, 0, sizeof(super_blocks));
- fcntl_init_locks();
- if (MAJOR(ROOT_DEV) == FLOPPY_MAJOR) {
- printk(KERN_NOTICE "VFS: Insert root floppy and press ENTER\n");
- wait_for_keypress();
- }
- for (fs_type = file_systems; fs_type->read_super; fs_type++) {
- if (!fs_type->requires_dev)
- continue;
- sb = read_super(ROOT_DEV,fs_type->name,root_mountflags,NULL,1);
- if (sb) {
- inode = sb->s_mounted;
- inode->i_count += 3 ; /* NOTE! it is logically used 4 times, not 1 */
- sb->s_covered = inode;
- sb->s_flags = root_mountflags;
- current->pwd = inode;
- current->root = inode;
- printk ("VFS: Mounted root (%s filesystem)%s.\n",
- fs_type->name,
- (sb->s_flags & MS_RDONLY) ? " readonly" : "");
- return;
- }
- }
- panic("VFS: Unable to mount root");
- }
-