diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /fs/autofs4 |
Linux-2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'fs/autofs4')
-rw-r--r-- | fs/autofs4/Makefile | 7 | ||||
-rw-r--r-- | fs/autofs4/autofs_i.h | 193 | ||||
-rw-r--r-- | fs/autofs4/expire.c | 358 | ||||
-rw-r--r-- | fs/autofs4/init.c | 42 | ||||
-rw-r--r-- | fs/autofs4/inode.c | 324 | ||||
-rw-r--r-- | fs/autofs4/root.c | 808 | ||||
-rw-r--r-- | fs/autofs4/symlink.c | 25 | ||||
-rw-r--r-- | fs/autofs4/waitq.c | 303 |
8 files changed, 2060 insertions, 0 deletions
diff --git a/fs/autofs4/Makefile b/fs/autofs4/Makefile new file mode 100644 index 00000000000..f2c3b79e94d --- /dev/null +++ b/fs/autofs4/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for the linux autofs-filesystem routines. +# + +obj-$(CONFIG_AUTOFS4_FS) += autofs4.o + +autofs4-objs := init.o inode.o root.o symlink.o waitq.o expire.o diff --git a/fs/autofs4/autofs_i.h b/fs/autofs4/autofs_i.h new file mode 100644 index 00000000000..f5a52c87172 --- /dev/null +++ b/fs/autofs4/autofs_i.h @@ -0,0 +1,193 @@ +/* -*- c -*- ------------------------------------------------------------- * + * + * linux/fs/autofs/autofs_i.h + * + * Copyright 1997-1998 Transmeta Corporation - All Rights Reserved + * + * This file is part of the Linux kernel and is made available under + * the terms of the GNU General Public License, version 2, or at your + * option, any later version, incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +/* Internal header file for autofs */ + +#include <linux/auto_fs4.h> +#include <linux/list.h> + +/* This is the range of ioctl() numbers we claim as ours */ +#define AUTOFS_IOC_FIRST AUTOFS_IOC_READY +#define AUTOFS_IOC_COUNT 32 + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/time.h> +#include <linux/string.h> +#include <linux/wait.h> +#include <linux/sched.h> +#include <linux/mount.h> +#include <linux/namei.h> +#include <asm/current.h> +#include <asm/uaccess.h> + +/* #define DEBUG */ + +#ifdef DEBUG +#define DPRINTK(fmt,args...) do { printk(KERN_DEBUG "pid %d: %s: " fmt "\n" , current->pid , __FUNCTION__ , ##args); } while(0) +#else +#define DPRINTK(fmt,args...) do {} while(0) +#endif + +#define AUTOFS_SUPER_MAGIC 0x0187 + +/* + * If the daemon returns a negative response (AUTOFS_IOC_FAIL) then the + * kernel will keep the negative response cached for up to the time given + * here, although the time can be shorter if the kernel throws the dcache + * entry away. This probably should be settable from user space. + */ +#define AUTOFS_NEGATIVE_TIMEOUT (60*HZ) /* 1 minute */ + +/* Unified info structure. This is pointed to by both the dentry and + inode structures. Each file in the filesystem has an instance of this + structure. It holds a reference to the dentry, so dentries are never + flushed while the file exists. All name lookups are dealt with at the + dentry level, although the filesystem can interfere in the validation + process. Readdir is implemented by traversing the dentry lists. */ +struct autofs_info { + struct dentry *dentry; + struct inode *inode; + + int flags; + + struct autofs_sb_info *sbi; + unsigned long last_used; + + mode_t mode; + size_t size; + + void (*free)(struct autofs_info *); + union { + const char *symlink; + } u; +}; + +#define AUTOFS_INF_EXPIRING (1<<0) /* dentry is in the process of expiring */ + +struct autofs_wait_queue { + wait_queue_head_t queue; + struct autofs_wait_queue *next; + autofs_wqt_t wait_queue_token; + /* We use the following to see what we are waiting for */ + int hash; + int len; + char *name; + /* This is for status reporting upon return */ + int status; + atomic_t wait_ctr; +}; + +#define AUTOFS_SBI_MAGIC 0x6d4a556d + +struct autofs_sb_info { + u32 magic; + struct file *pipe; + pid_t oz_pgrp; + int catatonic; + int version; + int sub_version; + unsigned long exp_timeout; + int reghost_enabled; + int needs_reghost; + struct super_block *sb; + struct semaphore wq_sem; + struct autofs_wait_queue *queues; /* Wait queue pointer */ +}; + +static inline struct autofs_sb_info *autofs4_sbi(struct super_block *sb) +{ + return (struct autofs_sb_info *)(sb->s_fs_info); +} + +static inline struct autofs_info *autofs4_dentry_ino(struct dentry *dentry) +{ + return (struct autofs_info *)(dentry->d_fsdata); +} + +/* autofs4_oz_mode(): do we see the man behind the curtain? (The + processes which do manipulations for us in user space sees the raw + filesystem without "magic".) */ + +static inline int autofs4_oz_mode(struct autofs_sb_info *sbi) { + return sbi->catatonic || process_group(current) == sbi->oz_pgrp; +} + +/* Does a dentry have some pending activity? */ +static inline int autofs4_ispending(struct dentry *dentry) +{ + struct autofs_info *inf = autofs4_dentry_ino(dentry); + + return (dentry->d_flags & DCACHE_AUTOFS_PENDING) || + (inf != NULL && inf->flags & AUTOFS_INF_EXPIRING); +} + +static inline void autofs4_copy_atime(struct file *src, struct file *dst) +{ + dst->f_dentry->d_inode->i_atime = src->f_dentry->d_inode->i_atime; + return; +} + +struct inode *autofs4_get_inode(struct super_block *, struct autofs_info *); +void autofs4_free_ino(struct autofs_info *); + +/* Expiration */ +int is_autofs4_dentry(struct dentry *); +int autofs4_expire_run(struct super_block *, struct vfsmount *, + struct autofs_sb_info *, + struct autofs_packet_expire __user *); +int autofs4_expire_multi(struct super_block *, struct vfsmount *, + struct autofs_sb_info *, int __user *); + +/* Operations structures */ + +extern struct inode_operations autofs4_symlink_inode_operations; +extern struct inode_operations autofs4_dir_inode_operations; +extern struct inode_operations autofs4_root_inode_operations; +extern struct file_operations autofs4_dir_operations; +extern struct file_operations autofs4_root_operations; + +/* Initializing function */ + +int autofs4_fill_super(struct super_block *, void *, int); +struct autofs_info *autofs4_init_ino(struct autofs_info *, struct autofs_sb_info *sbi, mode_t mode); + +/* Queue management functions */ + +enum autofs_notify +{ + NFY_NONE, + NFY_MOUNT, + NFY_EXPIRE +}; + +int autofs4_wait(struct autofs_sb_info *,struct dentry *, enum autofs_notify); +int autofs4_wait_release(struct autofs_sb_info *,autofs_wqt_t,int); +void autofs4_catatonic_mode(struct autofs_sb_info *); + +static inline int simple_positive(struct dentry *dentry) +{ + return dentry->d_inode && !d_unhashed(dentry); +} + +static inline int simple_empty_nolock(struct dentry *dentry) +{ + struct dentry *child; + int ret = 0; + + list_for_each_entry(child, &dentry->d_subdirs, d_child) + if (simple_positive(child)) + goto out; + ret = 1; +out: + return ret; +} diff --git a/fs/autofs4/expire.c b/fs/autofs4/expire.c new file mode 100644 index 00000000000..31540a6404d --- /dev/null +++ b/fs/autofs4/expire.c @@ -0,0 +1,358 @@ +/* -*- c -*- --------------------------------------------------------------- * + * + * linux/fs/autofs/expire.c + * + * Copyright 1997-1998 Transmeta Corporation -- All Rights Reserved + * Copyright 1999-2000 Jeremy Fitzhardinge <jeremy@goop.org> + * Copyright 2001-2003 Ian Kent <raven@themaw.net> + * + * This file is part of the Linux kernel and is made available under + * the terms of the GNU General Public License, version 2, or at your + * option, any later version, incorporated herein by reference. + * + * ------------------------------------------------------------------------- */ + +#include "autofs_i.h" + +static unsigned long now; + +/* Check if a dentry can be expired return 1 if it can else return 0 */ +static inline int autofs4_can_expire(struct dentry *dentry, + unsigned long timeout, int do_now) +{ + struct autofs_info *ino = autofs4_dentry_ino(dentry); + + /* dentry in the process of being deleted */ + if (ino == NULL) + return 0; + + /* No point expiring a pending mount */ + if (dentry->d_flags & DCACHE_AUTOFS_PENDING) + return 0; + + if (!do_now) { + /* Too young to die */ + if (time_after(ino->last_used + timeout, now)) + return 0; + + /* update last_used here :- + - obviously makes sense if it is in use now + - less obviously, prevents rapid-fire expire + attempts if expire fails the first time */ + ino->last_used = now; + } + + return 1; +} + +/* Check a mount point for busyness return 1 if not busy, otherwise */ +static int autofs4_check_mount(struct vfsmount *mnt, struct dentry *dentry) +{ + int status = 0; + + DPRINTK("dentry %p %.*s", + dentry, (int)dentry->d_name.len, dentry->d_name.name); + + mntget(mnt); + dget(dentry); + + if (!follow_down(&mnt, &dentry)) + goto done; + + while (d_mountpoint(dentry) && follow_down(&mnt, &dentry)) + ; + + /* This is an autofs submount, we can't expire it */ + if (is_autofs4_dentry(dentry)) + goto done; + + /* The big question */ + if (may_umount_tree(mnt) == 0) + status = 1; +done: + DPRINTK("returning = %d", status); + mntput(mnt); + dput(dentry); + return status; +} + +/* Check a directory tree of mount points for busyness + * The tree is not busy iff no mountpoints are busy + * Return 1 if the tree is busy or 0 otherwise + */ +static int autofs4_check_tree(struct vfsmount *mnt, + struct dentry *top, + unsigned long timeout, + int do_now) +{ + struct dentry *this_parent = top; + struct list_head *next; + + DPRINTK("parent %p %.*s", + top, (int)top->d_name.len, top->d_name.name); + + /* Negative dentry - give up */ + if (!simple_positive(top)) + return 0; + + /* Timeout of a tree mount is determined by its top dentry */ + if (!autofs4_can_expire(top, timeout, do_now)) + return 0; + + spin_lock(&dcache_lock); +repeat: + next = this_parent->d_subdirs.next; +resume: + while (next != &this_parent->d_subdirs) { + struct dentry *dentry = list_entry(next, struct dentry, d_child); + + /* Negative dentry - give up */ + if (!simple_positive(dentry)) { + next = next->next; + continue; + } + + DPRINTK("dentry %p %.*s", + dentry, (int)dentry->d_name.len, dentry->d_name.name); + + if (!simple_empty_nolock(dentry)) { + this_parent = dentry; + goto repeat; + } + + dentry = dget(dentry); + spin_unlock(&dcache_lock); + + if (d_mountpoint(dentry)) { + /* First busy => tree busy */ + if (!autofs4_check_mount(mnt, dentry)) { + dput(dentry); + return 0; + } + } + + dput(dentry); + spin_lock(&dcache_lock); + next = next->next; + } + + if (this_parent != top) { + next = this_parent->d_child.next; + this_parent = this_parent->d_parent; + goto resume; + } + spin_unlock(&dcache_lock); + + return 1; +} + +static struct dentry *autofs4_check_leaves(struct vfsmount *mnt, + struct dentry *parent, + unsigned long timeout, + int do_now) +{ + struct dentry *this_parent = parent; + struct list_head *next; + + DPRINTK("parent %p %.*s", + parent, (int)parent->d_name.len, parent->d_name.name); + + spin_lock(&dcache_lock); +repeat: + next = this_parent->d_subdirs.next; +resume: + while (next != &this_parent->d_subdirs) { + struct dentry *dentry = list_entry(next, struct dentry, d_child); + + /* Negative dentry - give up */ + if (!simple_positive(dentry)) { + next = next->next; + continue; + } + + DPRINTK("dentry %p %.*s", + dentry, (int)dentry->d_name.len, dentry->d_name.name); + + if (!list_empty(&dentry->d_subdirs)) { + this_parent = dentry; + goto repeat; + } + + dentry = dget(dentry); + spin_unlock(&dcache_lock); + + if (d_mountpoint(dentry)) { + /* Can we expire this guy */ + if (!autofs4_can_expire(dentry, timeout, do_now)) + goto cont; + + /* Can we umount this guy */ + if (autofs4_check_mount(mnt, dentry)) + return dentry; + + } +cont: + dput(dentry); + spin_lock(&dcache_lock); + next = next->next; + } + + if (this_parent != parent) { + next = this_parent->d_child.next; + this_parent = this_parent->d_parent; + goto resume; + } + spin_unlock(&dcache_lock); + + return NULL; +} + +/* + * Find an eligible tree to time-out + * A tree is eligible if :- + * - it is unused by any user process + * - it has been unused for exp_timeout time + */ +static struct dentry *autofs4_expire(struct super_block *sb, + struct vfsmount *mnt, + struct autofs_sb_info *sbi, + int how) +{ + unsigned long timeout; + struct dentry *root = sb->s_root; + struct dentry *expired = NULL; + struct list_head *next; + int do_now = how & AUTOFS_EXP_IMMEDIATE; + int exp_leaves = how & AUTOFS_EXP_LEAVES; + + if ( !sbi->exp_timeout || !root ) + return NULL; + + now = jiffies; + timeout = sbi->exp_timeout; + + spin_lock(&dcache_lock); + next = root->d_subdirs.next; + + /* On exit from the loop expire is set to a dgot dentry + * to expire or it's NULL */ + while ( next != &root->d_subdirs ) { + struct dentry *dentry = list_entry(next, struct dentry, d_child); + + /* Negative dentry - give up */ + if ( !simple_positive(dentry) ) { + next = next->next; + continue; + } + + dentry = dget(dentry); + spin_unlock(&dcache_lock); + + /* Case 1: indirect mount or top level direct mount */ + if (d_mountpoint(dentry)) { + DPRINTK("checking mountpoint %p %.*s", + dentry, (int)dentry->d_name.len, dentry->d_name.name); + + /* Can we expire this guy */ + if (!autofs4_can_expire(dentry, timeout, do_now)) + goto next; + + /* Can we umount this guy */ + if (autofs4_check_mount(mnt, dentry)) { + expired = dentry; + break; + } + goto next; + } + + if ( simple_empty(dentry) ) + goto next; + + /* Case 2: tree mount, expire iff entire tree is not busy */ + if (!exp_leaves) { + if (autofs4_check_tree(mnt, dentry, timeout, do_now)) { + expired = dentry; + break; + } + /* Case 3: direct mount, expire individual leaves */ + } else { + expired = autofs4_check_leaves(mnt, dentry, timeout, do_now); + if (expired) { + dput(dentry); + break; + } + } +next: + dput(dentry); + spin_lock(&dcache_lock); + next = next->next; + } + + if ( expired ) { + DPRINTK("returning %p %.*s", + expired, (int)expired->d_name.len, expired->d_name.name); + spin_lock(&dcache_lock); + list_del(&expired->d_parent->d_subdirs); + list_add(&expired->d_parent->d_subdirs, &expired->d_child); + spin_unlock(&dcache_lock); + return expired; + } + spin_unlock(&dcache_lock); + + return NULL; +} + +/* Perform an expiry operation */ +int autofs4_expire_run(struct super_block *sb, + struct vfsmount *mnt, + struct autofs_sb_info *sbi, + struct autofs_packet_expire __user *pkt_p) +{ + struct autofs_packet_expire pkt; + struct dentry *dentry; + + memset(&pkt,0,sizeof pkt); + + pkt.hdr.proto_version = sbi->version; + pkt.hdr.type = autofs_ptype_expire; + + if ((dentry = autofs4_expire(sb, mnt, sbi, 0)) == NULL) + return -EAGAIN; + + pkt.len = dentry->d_name.len; + memcpy(pkt.name, dentry->d_name.name, pkt.len); + pkt.name[pkt.len] = '\0'; + dput(dentry); + + if ( copy_to_user(pkt_p, &pkt, sizeof(struct autofs_packet_expire)) ) + return -EFAULT; + + return 0; +} + +/* Call repeatedly until it returns -EAGAIN, meaning there's nothing + more to be done */ +int autofs4_expire_multi(struct super_block *sb, struct vfsmount *mnt, + struct autofs_sb_info *sbi, int __user *arg) +{ + struct dentry *dentry; + int ret = -EAGAIN; + int do_now = 0; + + if (arg && get_user(do_now, arg)) + return -EFAULT; + + if ((dentry = autofs4_expire(sb, mnt, sbi, do_now)) != NULL) { + struct autofs_info *de_info = autofs4_dentry_ino(dentry); + + /* This is synchronous because it makes the daemon a + little easier */ + de_info->flags |= AUTOFS_INF_EXPIRING; + ret = autofs4_wait(sbi, dentry, NFY_EXPIRE); + de_info->flags &= ~AUTOFS_INF_EXPIRING; + dput(dentry); + } + + return ret; +} + diff --git a/fs/autofs4/init.c b/fs/autofs4/init.c new file mode 100644 index 00000000000..acecec8578c --- /dev/null +++ b/fs/autofs4/init.c @@ -0,0 +1,42 @@ +/* -*- c -*- --------------------------------------------------------------- * + * + * linux/fs/autofs/init.c + * + * Copyright 1997-1998 Transmeta Corporation -- All Rights Reserved + * + * This file is part of the Linux kernel and is made available under + * the terms of the GNU General Public License, version 2, or at your + * option, any later version, incorporated herein by reference. + * + * ------------------------------------------------------------------------- */ + +#include <linux/module.h> +#include <linux/init.h> +#include "autofs_i.h" + +static struct super_block *autofs_get_sb(struct file_system_type *fs_type, + int flags, const char *dev_name, void *data) +{ + return get_sb_nodev(fs_type, flags, data, autofs4_fill_super); +} + +static struct file_system_type autofs_fs_type = { + .owner = THIS_MODULE, + .name = "autofs", + .get_sb = autofs_get_sb, + .kill_sb = kill_anon_super, +}; + +static int __init init_autofs4_fs(void) +{ + return register_filesystem(&autofs_fs_type); +} + +static void __exit exit_autofs4_fs(void) +{ + unregister_filesystem(&autofs_fs_type); +} + +module_init(init_autofs4_fs) +module_exit(exit_autofs4_fs) +MODULE_LICENSE("GPL"); diff --git a/fs/autofs4/inode.c b/fs/autofs4/inode.c new file mode 100644 index 00000000000..a5256074662 --- /dev/null +++ b/fs/autofs4/inode.c @@ -0,0 +1,324 @@ +/* -*- c -*- --------------------------------------------------------------- * + * + * linux/fs/autofs/inode.c + * + * Copyright 1997-1998 Transmeta Corporation -- All Rights Reserved + * + * This file is part of the Linux kernel and is made available under + * the terms of the GNU General Public License, version 2, or at your + * option, any later version, incorporated herein by reference. + * + * ------------------------------------------------------------------------- */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/file.h> +#include <linux/pagemap.h> +#include <linux/parser.h> +#include <linux/bitops.h> +#include "autofs_i.h" +#include <linux/module.h> + +static void ino_lnkfree(struct autofs_info *ino) +{ + if (ino->u.symlink) { + kfree(ino->u.symlink); + ino->u.symlink = NULL; + } +} + +struct autofs_info *autofs4_init_ino(struct autofs_info *ino, + struct autofs_sb_info *sbi, mode_t mode) +{ + int reinit = 1; + + if (ino == NULL) { + reinit = 0; + ino = kmalloc(sizeof(*ino), GFP_KERNEL); + } + + if (ino == NULL) + return NULL; + + ino->flags = 0; + ino->mode = mode; + ino->inode = NULL; + ino->dentry = NULL; + ino->size = 0; + + ino->last_used = jiffies; + + ino->sbi = sbi; + + if (reinit && ino->free) + (ino->free)(ino); + + memset(&ino->u, 0, sizeof(ino->u)); + + ino->free = NULL; + + if (S_ISLNK(mode)) + ino->free = ino_lnkfree; + + return ino; +} + +void autofs4_free_ino(struct autofs_info *ino) +{ + if (ino->dentry) { + ino->dentry->d_fsdata = NULL; + if (ino->dentry->d_inode) + dput(ino->dentry); + ino->dentry = NULL; + } + if (ino->free) + (ino->free)(ino); + kfree(ino); +} + +static void autofs4_put_super(struct super_block *sb) +{ + struct autofs_sb_info *sbi = autofs4_sbi(sb); + + sb->s_fs_info = NULL; + + if ( !sbi->catatonic ) + autofs4_catatonic_mode(sbi); /* Free wait queues, close pipe */ + + kfree(sbi); + + DPRINTK("shutting down"); +} + +static struct super_operations autofs4_sops = { + .put_super = autofs4_put_super, + .statfs = simple_statfs, +}; + +enum {Opt_err, Opt_fd, Opt_uid, Opt_gid, Opt_pgrp, Opt_minproto, Opt_maxproto}; + +static match_table_t tokens = { + {Opt_fd, "fd=%u"}, + {Opt_uid, "uid=%u"}, + {Opt_gid, "gid=%u"}, + {Opt_pgrp, "pgrp=%u"}, + {Opt_minproto, "minproto=%u"}, + {Opt_maxproto, "maxproto=%u"}, + {Opt_err, NULL} +}; + +static int parse_options(char *options, int *pipefd, uid_t *uid, gid_t *gid, + pid_t *pgrp, int *minproto, int *maxproto) +{ + char *p; + substring_t args[MAX_OPT_ARGS]; + int option; + + *uid = current->uid; + *gid = current->gid; + *pgrp = process_group(current); + + *minproto = AUTOFS_MIN_PROTO_VERSION; + *maxproto = AUTOFS_MAX_PROTO_VERSION; + + *pipefd = -1; + + if (!options) + return 1; + + while ((p = strsep(&options, ",")) != NULL) { + int token; + if (!*p) + continue; + + token = match_token(p, tokens, args); + switch (token) { + case Opt_fd: + if (match_int(args, pipefd)) + return 1; + break; + case Opt_uid: + if (match_int(args, &option)) + return 1; + *uid = option; + break; + case Opt_gid: + if (match_int(args, &option)) + return 1; + *gid = option; + break; + case Opt_pgrp: + if (match_int(args, &option)) + return 1; + *pgrp = option; + break; + case Opt_minproto: + if (match_int(args, &option)) + return 1; + *minproto = option; + break; + case Opt_maxproto: + if (match_int(args, &option)) + return 1; + *maxproto = option; + break; + default: + return 1; + } + } + return (*pipefd < 0); +} + +static struct autofs_info *autofs4_mkroot(struct autofs_sb_info *sbi) +{ + struct autofs_info *ino; + + ino = autofs4_init_ino(NULL, sbi, S_IFDIR | 0755); + if (!ino) + return NULL; + + return ino; +} + +int autofs4_fill_super(struct super_block *s, void *data, int silent) +{ + struct inode * root_inode; + struct dentry * root; + struct file * pipe; + int pipefd; + struct autofs_sb_info *sbi; + struct autofs_info *ino; + int minproto, maxproto; + + sbi = (struct autofs_sb_info *) kmalloc(sizeof(*sbi), GFP_KERNEL); + if ( !sbi ) + goto fail_unlock; + DPRINTK("starting up, sbi = %p",sbi); + + memset(sbi, 0, sizeof(*sbi)); + + s->s_fs_info = sbi; + sbi->magic = AUTOFS_SBI_MAGIC; + sbi->catatonic = 0; + sbi->exp_timeout = 0; + sbi->oz_pgrp = process_group(current); + sbi->sb = s; + sbi->version = 0; + sbi->sub_version = 0; + init_MUTEX(&sbi->wq_sem); + sbi->queues = NULL; + s->s_blocksize = 1024; + s->s_blocksize_bits = 10; + s->s_magic = AUTOFS_SUPER_MAGIC; + s->s_op = &autofs4_sops; + s->s_time_gran = 1; + + /* + * Get the root inode and dentry, but defer checking for errors. + */ + ino = autofs4_mkroot(sbi); + if (!ino) + goto fail_free; + root_inode = autofs4_get_inode(s, ino); + kfree(ino); + if (!root_inode) + goto fail_free; + + root_inode->i_op = &autofs4_root_inode_operations; + root_inode->i_fop = &autofs4_root_operations; + root = d_alloc_root(root_inode); + pipe = NULL; + + if (!root) + goto fail_iput; + + /* Can this call block? */ + if (parse_options(data, &pipefd, + &root_inode->i_uid, &root_inode->i_gid, + &sbi->oz_pgrp, + &minproto, &maxproto)) { + printk("autofs: called with bogus options\n"); + goto fail_dput; + } + + /* Couldn't this be tested earlier? */ + if (maxproto < AUTOFS_MIN_PROTO_VERSION || + minproto > AUTOFS_MAX_PROTO_VERSION) { + printk("autofs: kernel does not match daemon version " + "daemon (%d, %d) kernel (%d, %d)\n", + minproto, maxproto, + AUTOFS_MIN_PROTO_VERSION, AUTOFS_MAX_PROTO_VERSION); + goto fail_dput; + } + + sbi->version = maxproto > AUTOFS_MAX_PROTO_VERSION ? AUTOFS_MAX_PROTO_VERSION : maxproto; + sbi->sub_version = AUTOFS_PROTO_SUBVERSION; + + DPRINTK("pipe fd = %d, pgrp = %u", pipefd, sbi->oz_pgrp); + pipe = fget(pipefd); + + if ( !pipe ) { + printk("autofs: could not open pipe file descriptor\n"); + goto fail_dput; + } + if ( !pipe->f_op || !pipe->f_op->write ) + goto fail_fput; + sbi->pipe = pipe; + + /* + * Success! Install the root dentry now to indicate completion. + */ + s->s_root = root; + return 0; + + /* + * Failure ... clean up. + */ +fail_fput: + printk("autofs: pipe file descriptor does not contain proper ops\n"); + fput(pipe); + /* fall through */ +fail_dput: + dput(root); + goto fail_free; +fail_iput: + printk("autofs: get root dentry failed\n"); + iput(root_inode); +fail_free: + kfree(sbi); +fail_unlock: + return -EINVAL; +} + +struct inode *autofs4_get_inode(struct super_block *sb, + struct autofs_info *inf) +{ + struct inode *inode = new_inode(sb); + + if (inode == NULL) + return NULL; + + inf->inode = inode; + inode->i_mode = inf->mode; + if (sb->s_root) { + inode->i_uid = sb->s_root->d_inode->i_uid; + inode->i_gid = sb->s_root->d_inode->i_gid; + } else { + inode->i_uid = 0; + inode->i_gid = 0; + } + inode->i_blksize = PAGE_CACHE_SIZE; + inode->i_blocks = 0; + inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME; + + if (S_ISDIR(inf->mode)) { + inode->i_nlink = 2; + inode->i_op = &autofs4_dir_inode_operations; + inode->i_fop = &autofs4_dir_operations; + } else if (S_ISLNK(inf->mode)) { + inode->i_size = inf->size; + inode->i_op = &autofs4_symlink_inode_operations; + } + + return inode; +} diff --git a/fs/autofs4/root.c b/fs/autofs4/root.c new file mode 100644 index 00000000000..3765c047f15 --- /dev/null +++ b/fs/autofs4/root.c @@ -0,0 +1,808 @@ +/* -*- c -*- --------------------------------------------------------------- * + * + * linux/fs/autofs/root.c + * + * Copyright 1997-1998 Transmeta Corporation -- All Rights Reserved + * Copyright 1999-2000 Jeremy Fitzhardinge <jeremy@goop.org> + * Copyright 2001-2003 Ian Kent <raven@themaw.net> + * + * This file is part of the Linux kernel and is made available under + * the terms of the GNU General Public License, version 2, or at your + * option, any later version, incorporated herein by reference. + * + * ------------------------------------------------------------------------- */ + +#include <linux/errno.h> +#include <linux/stat.h> +#include <linux/param.h> +#include <linux/time.h> +#include <linux/smp_lock.h> +#include "autofs_i.h" + +static int autofs4_dir_symlink(struct inode *,struct dentry *,const char *); +static int autofs4_dir_unlink(struct inode *,struct dentry *); +static int autofs4_dir_rmdir(struct inode *,struct dentry *); +static int autofs4_dir_mkdir(struct inode *,struct dentry *,int); +static int autofs4_root_ioctl(struct inode *, struct file *,unsigned int,unsigned long); +static int autofs4_dir_open(struct inode *inode, struct file *file); +static int autofs4_dir_close(struct inode *inode, struct file *file); +static int autofs4_dir_readdir(struct file * filp, void * dirent, filldir_t filldir); +static int autofs4_root_readdir(struct file * filp, void * dirent, filldir_t filldir); +static struct dentry *autofs4_lookup(struct inode *,struct dentry *, struct nameidata *); +static int autofs4_dcache_readdir(struct file *, void *, filldir_t); + +struct file_operations autofs4_root_operations = { + .open = dcache_dir_open, + .release = dcache_dir_close, + .read = generic_read_dir, + .readdir = autofs4_root_readdir, + .ioctl = autofs4_root_ioctl, +}; + +struct file_operations autofs4_dir_operations = { + .open = autofs4_dir_open, + .release = autofs4_dir_close, + .read = generic_read_dir, + .readdir = autofs4_dir_readdir, +}; + +struct inode_operations autofs4_root_inode_operations = { + .lookup = autofs4_lookup, + .unlink = autofs4_dir_unlink, + .symlink = autofs4_dir_symlink, + .mkdir = autofs4_dir_mkdir, + .rmdir = autofs4_dir_rmdir, +}; + +struct inode_operations autofs4_dir_inode_operations = { + .lookup = autofs4_lookup, + .unlink = autofs4_dir_unlink, + .symlink = autofs4_dir_symlink, + .mkdir = autofs4_dir_mkdir, + .rmdir = autofs4_dir_rmdir, +}; + +static int autofs4_root_readdir(struct file *file, void *dirent, + filldir_t filldir) +{ + struct autofs_sb_info *sbi = autofs4_sbi(file->f_dentry->d_sb); + int oz_mode = autofs4_oz_mode(sbi); + + DPRINTK("called, filp->f_pos = %lld", file->f_pos); + + /* + * Don't set reghost flag if: + * 1) f_pos is larger than zero -- we've already been here. + * 2) we haven't even enabled reghosting in the 1st place. + * 3) this is the daemon doing a readdir + */ + if (oz_mode && file->f_pos == 0 && sbi->reghost_enabled) + sbi->needs_reghost = 1; + + DPRINTK("needs_reghost = %d", sbi->needs_reghost); + + return autofs4_dcache_readdir(file, dirent, filldir); +} + +/* Update usage from here to top of tree, so that scan of + top-level directories will give a useful result */ +static void autofs4_update_usage(struct dentry *dentry) +{ + struct dentry *top = dentry->d_sb->s_root; + + spin_lock(&dcache_lock); + for(; dentry != top; dentry = dentry->d_parent) { + struct autofs_info *ino = autofs4_dentry_ino(dentry); + + if (ino) { + update_atime(dentry->d_inode); + ino->last_used = jiffies; + } + } + spin_unlock(&dcache_lock); +} + +/* + * From 2.4 kernel readdir.c + */ +static int autofs4_dcache_readdir(struct file * filp, void * dirent, filldir_t filldir) +{ + int i; + struct dentry *dentry = filp->f_dentry; + + i = filp->f_pos; + switch (i) { + case 0: + if (filldir(dirent, ".", 1, i, dentry->d_inode->i_ino, DT_DIR) < 0) + break; + i++; + filp->f_pos++; + /* fallthrough */ + case 1: + if (filldir(dirent, "..", 2, i, dentry->d_parent->d_inode->i_ino, DT_DIR) < 0) + break; + i++; + filp->f_pos++; + /* fallthrough */ + default: { + struct list_head *list; + int j = i-2; + + spin_lock(&dcache_lock); + list = dentry->d_subdirs.next; + + for (;;) { + if (list == &dentry->d_subdirs) { + spin_unlock(&dcache_lock); + return 0; + } + if (!j) + break; + j--; + list = list->next; + } + + while(1) { + struct dentry *de = list_entry(list, struct dentry, d_child); + + if (!d_unhashed(de) && de->d_inode) { + spin_unlock(&dcache_lock); + if (filldir(dirent, de->d_name.name, de->d_name.len, filp->f_pos, de->d_inode->i_ino, DT_UNKNOWN) < 0) + break; + spin_lock(&dcache_lock); + } + filp->f_pos++; + list = list->next; + if (list != &dentry->d_subdirs) + continue; + spin_unlock(&dcache_lock); + break; + } + } + } + return 0; +} + +static int autofs4_dir_open(struct inode *inode, struct file *file) +{ + struct dentry *dentry = file->f_dentry; + struct vfsmount *mnt = file->f_vfsmnt; + struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); + int status; + + DPRINTK("file=%p dentry=%p %.*s", + file, dentry, dentry->d_name.len, dentry->d_name.name); + + if (autofs4_oz_mode(sbi)) + goto out; + + if (autofs4_ispending(dentry)) { + DPRINTK("dentry busy"); + return -EBUSY; + } + + if (!d_mountpoint(dentry) && dentry->d_op && dentry->d_op->d_revalidate) { + struct nameidata nd; + int empty; + + /* In case there are stale directory dentrys from a failed mount */ + spin_lock(&dcache_lock); + empty = list_empty(&dentry->d_subdirs); + spin_unlock(&dcache_lock); + + if (!empty) + d_invalidate(dentry); + + nd.flags = LOOKUP_DIRECTORY; + status = (dentry->d_op->d_revalidate)(dentry, &nd); + + if (!status) + return -ENOENT; + } + + if (d_mountpoint(dentry)) { + struct file *fp = NULL; + struct vfsmount *fp_mnt = mntget(mnt); + struct dentry *fp_dentry = dget(dentry); + + while (follow_down(&fp_mnt, &fp_dentry) && d_mountpoint(fp_dentry)); + + fp = dentry_open(fp_dentry, fp_mnt, file->f_flags); + status = PTR_ERR(fp); + if (IS_ERR(fp)) { + file->private_data = NULL; + return status; + } + file->private_data = fp; + } +out: + return 0; +} + +static int autofs4_dir_close(struct inode *inode, struct file *file) +{ + struct dentry *dentry = file->f_dentry; + struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); + + DPRINTK("file=%p dentry=%p %.*s", + file, dentry, dentry->d_name.len, dentry->d_name.name); + + if (autofs4_oz_mode(sbi)) + goto out; + + if (autofs4_ispending(dentry)) { + DPRINTK("dentry busy"); + return -EBUSY; + } + + if (d_mountpoint(dentry)) { + struct file *fp = file->private_data; + + if (!fp) + return -ENOENT; + + filp_close(fp, current->files); + file->private_data = NULL; + } +out: + return 0; +} + +static int autofs4_dir_readdir(struct file *file, void *dirent, filldir_t filldir) +{ + struct dentry *dentry = file->f_dentry; + struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); + int status; + + DPRINTK("file=%p dentry=%p %.*s", + file, dentry, dentry->d_name.len, dentry->d_name.name); + + if (autofs4_oz_mode(sbi)) + goto out; + + if (autofs4_ispending(dentry)) { + DPRINTK("dentry busy"); + return -EBUSY; + } + + if (d_mountpoint(dentry)) { + struct file *fp = file->private_data; + + if (!fp) + return -ENOENT; + + if (!fp->f_op || !fp->f_op->readdir) + goto out; + + status = vfs_readdir(fp, filldir, dirent); + file->f_pos = fp->f_pos; + if (status) + autofs4_copy_atime(file, fp); + return status; + } +out: + return autofs4_dcache_readdir(file, dirent, filldir); +} + +static int try_to_fill_dentry(struct dentry *dentry, + struct super_block *sb, + struct autofs_sb_info *sbi, int flags) +{ + struct autofs_info *de_info = autofs4_dentry_ino(dentry); + int status = 0; + + /* Block on any pending expiry here; invalidate the dentry + when expiration is done to trigger mount request with a new + dentry */ + if (de_info && (de_info->flags & AUTOFS_INF_EXPIRING)) { + DPRINTK("waiting for expire %p name=%.*s", + dentry, dentry->d_name.len, dentry->d_name.name); + + status = autofs4_wait(sbi, dentry, NFY_NONE); + + DPRINTK("expire done status=%d", status); + + return 0; + } + + DPRINTK("dentry=%p %.*s ino=%p", + dentry, dentry->d_name.len, dentry->d_name.name, dentry->d_inode); + + /* Wait for a pending mount, triggering one if there isn't one already */ + if (dentry->d_inode == NULL) { + DPRINTK("waiting for mount name=%.*s", + dentry->d_name.len, dentry->d_name.name); + + status = autofs4_wait(sbi, dentry, NFY_MOUNT); + + DPRINTK("mount done status=%d", status); + + if (status && dentry->d_inode) + return 0; /* Try to get the kernel to invalidate this dentry */ + + /* Turn this into a real negative dentry? */ + if (status == -ENOENT) { + dentry->d_time = jiffies + AUTOFS_NEGATIVE_TIMEOUT; + spin_lock(&dentry->d_lock); + dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; + spin_unlock(&dentry->d_lock); + return 1; + } else if (status) { + /* Return a negative dentry, but leave it "pending" */ + return 1; + } + /* Trigger mount for path component or follow link */ + } else if (flags & (LOOKUP_CONTINUE | LOOKUP_DIRECTORY) || + current->link_count) { + DPRINTK("waiting for mount name=%.*s", + dentry->d_name.len, dentry->d_name.name); + + spin_lock(&dentry->d_lock); + dentry->d_flags |= DCACHE_AUTOFS_PENDING; + spin_unlock(&dentry->d_lock); + status = autofs4_wait(sbi, dentry, NFY_MOUNT); + + DPRINTK("mount done status=%d", status); + + if (status) { + spin_lock(&dentry->d_lock); + dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; + spin_unlock(&dentry->d_lock); + return 0; + } + } + + /* We don't update the usages for the autofs daemon itself, this + is necessary for recursive autofs mounts */ + if (!autofs4_oz_mode(sbi)) + autofs4_update_usage(dentry); + + spin_lock(&dentry->d_lock); + dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; + spin_unlock(&dentry->d_lock); + return 1; +} + +/* + * Revalidate is called on every cache lookup. Some of those + * cache lookups may actually happen while the dentry is not + * yet completely filled in, and revalidate has to delay such + * lookups.. + */ +static int autofs4_revalidate(struct dentry * dentry, struct nameidata *nd) +{ + struct inode * dir = dentry->d_parent->d_inode; + struct autofs_sb_info *sbi = autofs4_sbi(dir->i_sb); + int oz_mode = autofs4_oz_mode(sbi); + int flags = nd ? nd->flags : 0; + int status = 1; + + /* Pending dentry */ + if (autofs4_ispending(dentry)) { + if (!oz_mode) + status = try_to_fill_dentry(dentry, dir->i_sb, sbi, flags); + return status; + } + + /* Negative dentry.. invalidate if "old" */ + if (dentry->d_inode == NULL) + return (dentry->d_time - jiffies <= AUTOFS_NEGATIVE_TIMEOUT); + + /* Check for a non-mountpoint directory with no contents */ + spin_lock(&dcache_lock); + if (S_ISDIR(dentry->d_inode->i_mode) && + !d_mountpoint(dentry) && + list_empty(&dentry->d_subdirs)) { + DPRINTK("dentry=%p %.*s, emptydir", + dentry, dentry->d_name.len, dentry->d_name.name); + spin_unlock(&dcache_lock); + if (!oz_mode) + status = try_to_fill_dentry(dentry, dir->i_sb, sbi, flags); + return status; + } + spin_unlock(&dcache_lock); + + /* Update the usage list */ + if (!oz_mode) + autofs4_update_usage(dentry); + + return 1; +} + +static void autofs4_dentry_release(struct dentry *de) +{ + struct autofs_info *inf; + + DPRINTK("releasing %p", de); + + inf = autofs4_dentry_ino(de); + de->d_fsdata = NULL; + + if (inf) { + inf->dentry = NULL; + inf->inode = NULL; + + autofs4_free_ino(inf); + } +} + +/* For dentries of directories in the root dir */ +static struct dentry_operations autofs4_root_dentry_operations = { + .d_revalidate = autofs4_revalidate, + .d_release = autofs4_dentry_release, +}; + +/* For other dentries */ +static struct dentry_operations autofs4_dentry_operations = { + .d_revalidate = autofs4_revalidate, + .d_release = autofs4_dentry_release, +}; + +/* Lookups in the root directory */ +static struct dentry *autofs4_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd) +{ + struct autofs_sb_info *sbi; + int oz_mode; + + DPRINTK("name = %.*s", + dentry->d_name.len, dentry->d_name.name); + + if (dentry->d_name.len > NAME_MAX) + return ERR_PTR(-ENAMETOOLONG);/* File name too long to exist */ + + sbi = autofs4_sbi(dir->i_sb); + + oz_mode = autofs4_oz_mode(sbi); + DPRINTK("pid = %u, pgrp = %u, catatonic = %d, oz_mode = %d", + current->pid, process_group(current), sbi->catatonic, oz_mode); + + /* + * Mark the dentry incomplete, but add it. This is needed so + * that the VFS layer knows about the dentry, and we can count + * on catching any lookups through the revalidate. + * + * Let all the hard work be done by the revalidate function that + * needs to be able to do this anyway.. + * + * We need to do this before we release the directory semaphore. + */ + dentry->d_op = &autofs4_root_dentry_operations; + + if (!oz_mode) { + spin_lock(&dentry->d_lock); + dentry->d_flags |= DCACHE_AUTOFS_PENDING; + spin_unlock(&dentry->d_lock); + } + dentry->d_fsdata = NULL; + d_add(dentry, NULL); + + if (dentry->d_op && dentry->d_op->d_revalidate) { + up(&dir->i_sem); + (dentry->d_op->d_revalidate)(dentry, nd); + down(&dir->i_sem); + } + + /* + * If we are still pending, check if we had to handle + * a signal. If so we can force a restart.. + */ + if (dentry->d_flags & DCACHE_AUTOFS_PENDING) { + /* See if we were interrupted */ + if (signal_pending(current)) { + sigset_t *sigset = ¤t->pending.signal; + if (sigismember (sigset, SIGKILL) || + sigismember (sigset, SIGQUIT) || + sigismember (sigset, SIGINT)) { + return ERR_PTR(-ERESTARTNOINTR); + } + } + } + + /* + * If this dentry is unhashed, then we shouldn't honour this + * lookup even if the dentry is positive. Returning ENOENT here + * doesn't do the right thing for all system calls, but it should + * be OK for the operations we permit from an autofs. + */ + if ( dentry->d_inode && d_unhashed(dentry) ) + return ERR_PTR(-ENOENT); + + return NULL; +} + +static int autofs4_dir_symlink(struct inode *dir, + struct dentry *dentry, + const char *symname) +{ + struct autofs_sb_info *sbi = autofs4_sbi(dir->i_sb); + struct autofs_info *ino = autofs4_dentry_ino(dentry); + struct inode *inode; + char *cp; + + DPRINTK("%s <- %.*s", symname, + dentry->d_name.len, dentry->d_name.name); + + if (!autofs4_oz_mode(sbi)) + return -EACCES; + + ino = autofs4_init_ino(ino, sbi, S_IFLNK | 0555); + if (ino == NULL) + return -ENOSPC; + + ino->size = strlen(symname); + ino->u.symlink = cp = kmalloc(ino->size + 1, GFP_KERNEL); + + if (cp == NULL) { + kfree(ino); + return -ENOSPC; + } + + strcpy(cp, symname); + + inode = autofs4_get_inode(dir->i_sb, ino); + d_instantiate(dentry, inode); + + if (dir == dir->i_sb->s_root->d_inode) + dentry->d_op = &autofs4_root_dentry_operations; + else + dentry->d_op = &autofs4_dentry_operations; + + dentry->d_fsdata = ino; + ino->dentry = dget(dentry); + ino->inode = inode; + + dir->i_mtime = CURRENT_TIME; + + return 0; +} + +/* + * NOTE! + * + * Normal filesystems would do a "d_delete()" to tell the VFS dcache + * that the file no longer exists. However, doing that means that the + * VFS layer can turn the dentry into a negative dentry. We don't want + * this, because since the unlink is probably the result of an expire. + * We simply d_drop it, which allows the dentry lookup to remount it + * if necessary. + * + * If a process is blocked on the dentry waiting for the expire to finish, + * it will invalidate the dentry and try to mount with a new one. + * + * Also see autofs4_dir_rmdir().. + */ +static int autofs4_dir_unlink(struct inode *dir, struct dentry *dentry) +{ + struct autofs_sb_info *sbi = autofs4_sbi(dir->i_sb); + struct autofs_info *ino = autofs4_dentry_ino(dentry); + + /* This allows root to remove symlinks */ + if ( !autofs4_oz_mode(sbi) && !capable(CAP_SYS_ADMIN) ) + return -EACCES; + + dput(ino->dentry); + + dentry->d_inode->i_size = 0; + dentry->d_inode->i_nlink = 0; + + dir->i_mtime = CURRENT_TIME; + + d_drop(dentry); + + return 0; +} + +static int autofs4_dir_rmdir(struct inode *dir, struct dentry *dentry) +{ + struct autofs_sb_info *sbi = autofs4_sbi(dir->i_sb); + struct autofs_info *ino = autofs4_dentry_ino(dentry); + + if (!autofs4_oz_mode(sbi)) + return -EACCES; + + spin_lock(&dcache_lock); + if (!list_empty(&dentry->d_subdirs)) { + spin_unlock(&dcache_lock); + return -ENOTEMPTY; + } + spin_lock(&dentry->d_lock); + __d_drop(dentry); + spin_unlock(&dentry->d_lock); + spin_unlock(&dcache_lock); + + dput(ino->dentry); + + dentry->d_inode->i_size = 0; + dentry->d_inode->i_nlink = 0; + + if (dir->i_nlink) + dir->i_nlink--; + + return 0; +} + +static int autofs4_dir_mkdir(struct inode *dir, struct dentry *dentry, int mode) +{ + struct autofs_sb_info *sbi = autofs4_sbi(dir->i_sb); + struct autofs_info *ino = autofs4_dentry_ino(dentry); + struct inode *inode; + + if ( !autofs4_oz_mode(sbi) ) + return -EACCES; + + DPRINTK("dentry %p, creating %.*s", + dentry, dentry->d_name.len, dentry->d_name.name); + + ino = autofs4_init_ino(ino, sbi, S_IFDIR | 0555); + if (ino == NULL) + return -ENOSPC; + + inode = autofs4_get_inode(dir->i_sb, ino); + d_instantiate(dentry, inode); + + if (dir == dir->i_sb->s_root->d_inode) + dentry->d_op = &autofs4_root_dentry_operations; + else + dentry->d_op = &autofs4_dentry_operations; + + dentry->d_fsdata = ino; + ino->dentry = dget(dentry); + ino->inode = inode; + dir->i_nlink++; + dir->i_mtime = CURRENT_TIME; + + return 0; +} + +/* Get/set timeout ioctl() operation */ +static inline int autofs4_get_set_timeout(struct autofs_sb_info *sbi, + unsigned long __user *p) +{ + int rv; + unsigned long ntimeout; + + if ( (rv = get_user(ntimeout, p)) || + (rv = put_user(sbi->exp_timeout/HZ, p)) ) + return rv; + + if ( ntimeout > ULONG_MAX/HZ ) + sbi->exp_timeout = 0; + else + sbi->exp_timeout = ntimeout * HZ; + + return 0; +} + +/* Return protocol version */ +static inline int autofs4_get_protover(struct autofs_sb_info *sbi, int __user *p) +{ + return put_user(sbi->version, p); +} + +/* Return protocol sub version */ +static inline int autofs4_get_protosubver(struct autofs_sb_info *sbi, int __user *p) +{ + return put_user(sbi->sub_version, p); +} + +/* + * Tells the daemon whether we need to reghost or not. Also, clears + * the reghost_needed flag. + */ +static inline int autofs4_ask_reghost(struct autofs_sb_info *sbi, int __user *p) +{ + int status; + + DPRINTK("returning %d", sbi->needs_reghost); + + status = put_user(sbi->needs_reghost, p); + if ( status ) + return status; + + sbi->needs_reghost = 0; + return 0; +} + +/* + * Enable / Disable reghosting ioctl() operation + */ +static inline int autofs4_toggle_reghost(struct autofs_sb_info *sbi, int __user *p) +{ + int status; + int val; + + status = get_user(val, p); + + DPRINTK("reghost = %d", val); + + if (status) + return status; + + /* turn on/off reghosting, with the val */ + sbi->reghost_enabled = val; + return 0; +} + +/* +* Tells the daemon whether it can umount the autofs mount. +*/ +static inline int autofs4_ask_umount(struct vfsmount *mnt, int __user *p) +{ + int status = 0; + + if (may_umount(mnt) == 0) + status = 1; + + DPRINTK("returning %d", status); + + status = put_user(status, p); + + return status; +} + +/* Identify autofs4_dentries - this is so we can tell if there's + an extra dentry refcount or not. We only hold a refcount on the + dentry if its non-negative (ie, d_inode != NULL) +*/ +int is_autofs4_dentry(struct dentry *dentry) +{ + return dentry && dentry->d_inode && + (dentry->d_op == &autofs4_root_dentry_operations || + dentry->d_op == &autofs4_dentry_operations) && + dentry->d_fsdata != NULL; +} + +/* + * ioctl()'s on the root directory is the chief method for the daemon to + * generate kernel reactions + */ +static int autofs4_root_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + struct autofs_sb_info *sbi = autofs4_sbi(inode->i_sb); + void __user *p = (void __user *)arg; + + DPRINTK("cmd = 0x%08x, arg = 0x%08lx, sbi = %p, pgrp = %u", + cmd,arg,sbi,process_group(current)); + + if ( _IOC_TYPE(cmd) != _IOC_TYPE(AUTOFS_IOC_FIRST) || + _IOC_NR(cmd) - _IOC_NR(AUTOFS_IOC_FIRST) >= AUTOFS_IOC_COUNT ) + return -ENOTTY; + + if ( !autofs4_oz_mode(sbi) && !capable(CAP_SYS_ADMIN) ) + return -EPERM; + + switch(cmd) { + case AUTOFS_IOC_READY: /* Wait queue: go ahead and retry */ + return autofs4_wait_release(sbi,(autofs_wqt_t)arg,0); + case AUTOFS_IOC_FAIL: /* Wait queue: fail with ENOENT */ + return autofs4_wait_release(sbi,(autofs_wqt_t)arg,-ENOENT); + case AUTOFS_IOC_CATATONIC: /* Enter catatonic mode (daemon shutdown) */ + autofs4_catatonic_mode(sbi); + return 0; + case AUTOFS_IOC_PROTOVER: /* Get protocol version */ + return autofs4_get_protover(sbi, p); + case AUTOFS_IOC_PROTOSUBVER: /* Get protocol sub version */ + return autofs4_get_protosubver(sbi, p); + case AUTOFS_IOC_SETTIMEOUT: + return autofs4_get_set_timeout(sbi, p); + + case AUTOFS_IOC_TOGGLEREGHOST: + return autofs4_toggle_reghost(sbi, p); + case AUTOFS_IOC_ASKREGHOST: + return autofs4_ask_reghost(sbi, p); + + case AUTOFS_IOC_ASKUMOUNT: + return autofs4_ask_umount(filp->f_vfsmnt, p); + + /* return a single thing to expire */ + case AUTOFS_IOC_EXPIRE: + return autofs4_expire_run(inode->i_sb,filp->f_vfsmnt,sbi, p); + /* same as above, but can send multiple expires through pipe */ + case AUTOFS_IOC_EXPIRE_MULTI: + return autofs4_expire_multi(inode->i_sb,filp->f_vfsmnt,sbi, p); + + default: + return -ENOSYS; + } +} diff --git a/fs/autofs4/symlink.c b/fs/autofs4/symlink.c new file mode 100644 index 00000000000..c265a66edf0 --- /dev/null +++ b/fs/autofs4/symlink.c @@ -0,0 +1,25 @@ +/* -*- c -*- --------------------------------------------------------------- * + * + * linux/fs/autofs/symlink.c + * + * Copyright 1997-1998 Transmeta Corporation -- All Rights Reserved + * + * This file is part of the Linux kernel and is made available under + * the terms of the GNU General Public License, version 2, or at your + * option, any later version, incorporated herein by reference. + * + * ------------------------------------------------------------------------- */ + +#include "autofs_i.h" + +static int autofs4_follow_link(struct dentry *dentry, struct nameidata *nd) +{ + struct autofs_info *ino = autofs4_dentry_ino(dentry); + nd_set_link(nd, (char *)ino->u.symlink); + return 0; +} + +struct inode_operations autofs4_symlink_inode_operations = { + .readlink = generic_readlink, + .follow_link = autofs4_follow_link +}; diff --git a/fs/autofs4/waitq.c b/fs/autofs4/waitq.c new file mode 100644 index 00000000000..1ab24a662e0 --- /dev/null +++ b/fs/autofs4/waitq.c @@ -0,0 +1,303 @@ +/* -*- c -*- --------------------------------------------------------------- * + * + * linux/fs/autofs/waitq.c + * + * Copyright 1997-1998 Transmeta Corporation -- All Rights Reserved + * Copyright 2001-2003 Ian Kent <raven@themaw.net> + * + * This file is part of the Linux kernel and is made available under + * the terms of the GNU General Public License, version 2, or at your + * option, any later version, incorporated herein by reference. + * + * ------------------------------------------------------------------------- */ + +#include <linux/slab.h> +#include <linux/time.h> +#include <linux/signal.h> +#include <linux/file.h> +#include "autofs_i.h" + +/* We make this a static variable rather than a part of the superblock; it + is better if we don't reassign numbers easily even across filesystems */ +static autofs_wqt_t autofs4_next_wait_queue = 1; + +/* These are the signals we allow interrupting a pending mount */ +#define SHUTDOWN_SIGS (sigmask(SIGKILL) | sigmask(SIGINT) | sigmask(SIGQUIT)) + +void autofs4_catatonic_mode(struct autofs_sb_info *sbi) +{ + struct autofs_wait_queue *wq, *nwq; + + DPRINTK("entering catatonic mode"); + + sbi->catatonic = 1; + wq = sbi->queues; + sbi->queues = NULL; /* Erase all wait queues */ + while ( wq ) { + nwq = wq->next; + wq->status = -ENOENT; /* Magic is gone - report failure */ + kfree(wq->name); + wq->name = NULL; + wake_up_interruptible(&wq->queue); + wq = nwq; + } + if (sbi->pipe) { + fput(sbi->pipe); /* Close the pipe */ + sbi->pipe = NULL; + } + + shrink_dcache_sb(sbi->sb); +} + +static int autofs4_write(struct file *file, const void *addr, int bytes) +{ + unsigned long sigpipe, flags; + mm_segment_t fs; + const char *data = (const char *)addr; + ssize_t wr = 0; + + /** WARNING: this is not safe for writing more than PIPE_BUF bytes! **/ + + sigpipe = sigismember(¤t->pending.signal, SIGPIPE); + + /* Save pointer to user space and point back to kernel space */ + fs = get_fs(); + set_fs(KERNEL_DS); + + while (bytes && + (wr = file->f_op->write(file,data,bytes,&file->f_pos)) > 0) { + data += wr; + bytes -= wr; + } + + set_fs(fs); + + /* Keep the currently executing process from receiving a + SIGPIPE unless it was already supposed to get one */ + if (wr == -EPIPE && !sigpipe) { + spin_lock_irqsave(¤t->sighand->siglock, flags); + sigdelset(¤t->pending.signal, SIGPIPE); + recalc_sigpending(); + spin_unlock_irqrestore(¤t->sighand->siglock, flags); + } + + return (bytes > 0); +} + +static void autofs4_notify_daemon(struct autofs_sb_info *sbi, + struct autofs_wait_queue *wq, + int type) +{ + union autofs_packet_union pkt; + size_t pktsz; + + DPRINTK("wait id = 0x%08lx, name = %.*s, type=%d", + wq->wait_queue_token, wq->len, wq->name, type); + + memset(&pkt,0,sizeof pkt); /* For security reasons */ + + pkt.hdr.proto_version = sbi->version; + pkt.hdr.type = type; + if (type == autofs_ptype_missing) { + struct autofs_packet_missing *mp = &pkt.missing; + + pktsz = sizeof(*mp); + + mp->wait_queue_token = wq->wait_queue_token; + mp->len = wq->len; + memcpy(mp->name, wq->name, wq->len); + mp->name[wq->len] = '\0'; + } else if (type == autofs_ptype_expire_multi) { + struct autofs_packet_expire_multi *ep = &pkt.expire_multi; + + pktsz = sizeof(*ep); + + ep->wait_queue_token = wq->wait_queue_token; + ep->len = wq->len; + memcpy(ep->name, wq->name, wq->len); + ep->name[wq->len] = '\0'; + } else { + printk("autofs4_notify_daemon: bad type %d!\n", type); + return; + } + + if (autofs4_write(sbi->pipe, &pkt, pktsz)) + autofs4_catatonic_mode(sbi); +} + +static int autofs4_getpath(struct autofs_sb_info *sbi, + struct dentry *dentry, char **name) +{ + struct dentry *root = sbi->sb->s_root; + struct dentry *tmp; + char *buf = *name; + char *p; + int len = 0; + + spin_lock(&dcache_lock); + for (tmp = dentry ; tmp != root ; tmp = tmp->d_parent) + len += tmp->d_name.len + 1; + + if (--len > NAME_MAX) { + spin_unlock(&dcache_lock); + return 0; + } + + *(buf + len) = '\0'; + p = buf + len - dentry->d_name.len; + strncpy(p, dentry->d_name.name, dentry->d_name.len); + + for (tmp = dentry->d_parent; tmp != root ; tmp = tmp->d_parent) { + *(--p) = '/'; + p -= tmp->d_name.len; + strncpy(p, tmp->d_name.name, tmp->d_name.len); + } + spin_unlock(&dcache_lock); + + return len; +} + +int autofs4_wait(struct autofs_sb_info *sbi, struct dentry *dentry, + enum autofs_notify notify) +{ + struct autofs_wait_queue *wq; + char *name; + int len, status; + + /* In catatonic mode, we don't wait for nobody */ + if ( sbi->catatonic ) + return -ENOENT; + + name = kmalloc(NAME_MAX + 1, GFP_KERNEL); + if (!name) + return -ENOMEM; + + len = autofs4_getpath(sbi, dentry, &name); + if (!len) { + kfree(name); + return -ENOENT; + } + + if (down_interruptible(&sbi->wq_sem)) { + kfree(name); + return -EINTR; + } + + for (wq = sbi->queues ; wq ; wq = wq->next) { + if (wq->hash == dentry->d_name.hash && + wq->len == len && + wq->name && !memcmp(wq->name, name, len)) + break; + } + + if ( !wq ) { + /* Create a new wait queue */ + wq = kmalloc(sizeof(struct autofs_wait_queue),GFP_KERNEL); + if ( !wq ) { + kfree(name); + up(&sbi->wq_sem); + return -ENOMEM; + } + + wq->wait_queue_token = autofs4_next_wait_queue; + if (++autofs4_next_wait_queue == 0) + autofs4_next_wait_queue = 1; + wq->next = sbi->queues; + sbi->queues = wq; + init_waitqueue_head(&wq->queue); + wq->hash = dentry->d_name.hash; + wq->name = name; + wq->len = len; + wq->status = -EINTR; /* Status return if interrupted */ + atomic_set(&wq->wait_ctr, 2); + up(&sbi->wq_sem); + + DPRINTK("new wait id = 0x%08lx, name = %.*s, nfy=%d", + (unsigned long) wq->wait_queue_token, wq->len, wq->name, notify); + /* autofs4_notify_daemon() may block */ + if (notify != NFY_NONE) { + autofs4_notify_daemon(sbi,wq, + notify == NFY_MOUNT ? + autofs_ptype_missing : + autofs_ptype_expire_multi); + } + } else { + atomic_inc(&wq->wait_ctr); + up(&sbi->wq_sem); + kfree(name); + DPRINTK("existing wait id = 0x%08lx, name = %.*s, nfy=%d", + (unsigned long) wq->wait_queue_token, wq->len, wq->name, notify); + } + + /* wq->name is NULL if and only if the lock is already released */ + + if ( sbi->catatonic ) { + /* We might have slept, so check again for catatonic mode */ + wq->status = -ENOENT; + if ( wq->name ) { + kfree(wq->name); + wq->name = NULL; + } + } + + if ( wq->name ) { + /* Block all but "shutdown" signals while waiting */ + sigset_t oldset; + unsigned long irqflags; + + spin_lock_irqsave(¤t->sighand->siglock, irqflags); + oldset = current->blocked; + siginitsetinv(¤t->blocked, SHUTDOWN_SIGS & ~oldset.sig[0]); + recalc_sigpending(); + spin_unlock_irqrestore(¤t->sighand->siglock, irqflags); + + wait_event_interruptible(wq->queue, wq->name == NULL); + + spin_lock_irqsave(¤t->sighand->siglock, irqflags); + current->blocked = oldset; + recalc_sigpending(); + spin_unlock_irqrestore(¤t->sighand->siglock, irqflags); + } else { + DPRINTK("skipped sleeping"); + } + + status = wq->status; + + /* Are we the last process to need status? */ + if (atomic_dec_and_test(&wq->wait_ctr)) + kfree(wq); + + return status; +} + + +int autofs4_wait_release(struct autofs_sb_info *sbi, autofs_wqt_t wait_queue_token, int status) +{ + struct autofs_wait_queue *wq, **wql; + + down(&sbi->wq_sem); + for ( wql = &sbi->queues ; (wq = *wql) != 0 ; wql = &wq->next ) { + if ( wq->wait_queue_token == wait_queue_token ) + break; + } + + if ( !wq ) { + up(&sbi->wq_sem); + return -EINVAL; + } + + *wql = wq->next; /* Unlink from chain */ + up(&sbi->wq_sem); + kfree(wq->name); + wq->name = NULL; /* Do not wait on this queue */ + + wq->status = status; + + if (atomic_dec_and_test(&wq->wait_ctr)) /* Is anyone still waiting for this guy? */ + kfree(wq); + else + wake_up_interruptible(&wq->queue); + + return 0; +} + |