58217f5900
WOO HOO!
537 lines
12 KiB
C
537 lines
12 KiB
C
/*
|
|
* 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 (char * 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");
|
|
}
|