| |
| |
| @@ -25,25 +25,25 @@ static int autofs4_dir_rmdir(struct inod |
| 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 void *autofs4_follow_link(struct dentry *, struct nameidata *); |
| |
| +#define TRIGGER_FLAGS (LOOKUP_CONTINUE | LOOKUP_DIRECTORY) |
| +#define TRIGGER_INTENTS (LOOKUP_OPEN | LOOKUP_CREATE) |
| + |
| const struct file_operations autofs4_root_operations = { |
| .open = dcache_dir_open, |
| .release = dcache_dir_close, |
| .read = generic_read_dir, |
| - .readdir = autofs4_root_readdir, |
| + .readdir = dcache_readdir, |
| .ioctl = autofs4_root_ioctl, |
| }; |
| |
| const struct file_operations autofs4_dir_operations = { |
| .open = autofs4_dir_open, |
| - .release = autofs4_dir_close, |
| + .release = dcache_dir_close, |
| .read = generic_read_dir, |
| - .readdir = autofs4_dir_readdir, |
| + .readdir = dcache_readdir, |
| }; |
| |
| const struct inode_operations autofs4_indirect_root_inode_operations = { |
| @@ -70,42 +70,10 @@ const struct inode_operations autofs4_di |
| .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_path.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 dcache_readdir(file, dirent, filldir); |
| -} |
| - |
| static int autofs4_dir_open(struct inode *inode, struct file *file) |
| { |
| struct dentry *dentry = file->f_path.dentry; |
| - struct vfsmount *mnt = file->f_path.mnt; |
| struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); |
| - struct dentry *cursor; |
| - int status; |
| - |
| - status = dcache_dir_open(inode, file); |
| - if (status) |
| - goto out; |
| - |
| - cursor = file->private_data; |
| - cursor->d_fsdata = NULL; |
| |
| DPRINTK("file=%p dentry=%p %.*s", |
| file, dentry, dentry->d_name.len, dentry->d_name.name); |
| @@ -113,157 +81,31 @@ static int autofs4_dir_open(struct inode |
| if (autofs4_oz_mode(sbi)) |
| goto out; |
| |
| - if (autofs4_ispending(dentry)) { |
| - DPRINTK("dentry busy"); |
| - dcache_dir_close(inode, file); |
| - status = -EBUSY; |
| - goto out; |
| - } |
| - |
| - status = -ENOENT; |
| - if (!d_mountpoint(dentry) && dentry->d_op && dentry->d_op->d_revalidate) { |
| - struct nameidata nd; |
| - int empty, ret; |
| - |
| - /* In case there are stale directory dentrys from a failed mount */ |
| - spin_lock(&dcache_lock); |
| - empty = list_empty(&dentry->d_subdirs); |
| + /* |
| + * An empty directory in an autofs file system is always a |
| + * mount point. The daemon must have failed to mount this |
| + * during lookup so it doesn't exist. This can happen, for |
| + * example, if user space returns an incorrect status for a |
| + * mount request. Otherwise we're doing a readdir on the |
| + * autofs file system so just let the libfs routines handle |
| + * it. |
| + */ |
| + spin_lock(&dcache_lock); |
| + if (!d_mountpoint(dentry) && __simple_empty(dentry)) { |
| spin_unlock(&dcache_lock); |
| - |
| - if (!empty) |
| - d_invalidate(dentry); |
| - |
| - nd.flags = LOOKUP_DIRECTORY; |
| - ret = (dentry->d_op->d_revalidate)(dentry, &nd); |
| - |
| - if (ret <= 0) { |
| - if (ret < 0) |
| - status = ret; |
| - dcache_dir_close(inode, file); |
| - goto out; |
| - } |
| - } |
| - |
| - if (d_mountpoint(dentry)) { |
| - struct file *fp = NULL; |
| - struct vfsmount *fp_mnt = mntget(mnt); |
| - struct dentry *fp_dentry = dget(dentry); |
| - |
| - if (!autofs4_follow_mount(&fp_mnt, &fp_dentry)) { |
| - dput(fp_dentry); |
| - mntput(fp_mnt); |
| - dcache_dir_close(inode, file); |
| - goto out; |
| - } |
| - |
| - fp = dentry_open(fp_dentry, fp_mnt, file->f_flags); |
| - status = PTR_ERR(fp); |
| - if (IS_ERR(fp)) { |
| - dcache_dir_close(inode, file); |
| - goto out; |
| - } |
| - cursor->d_fsdata = fp; |
| - } |
| - return 0; |
| -out: |
| - return status; |
| -} |
| - |
| -static int autofs4_dir_close(struct inode *inode, struct file *file) |
| -{ |
| - struct dentry *dentry = file->f_path.dentry; |
| - struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); |
| - struct dentry *cursor = file->private_data; |
| - int status = 0; |
| - |
| - 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"); |
| - status = -EBUSY; |
| - goto out; |
| - } |
| - |
| - if (d_mountpoint(dentry)) { |
| - struct file *fp = cursor->d_fsdata; |
| - if (!fp) { |
| - status = -ENOENT; |
| - goto out; |
| - } |
| - filp_close(fp, current->files); |
| - } |
| -out: |
| - dcache_dir_close(inode, file); |
| - return status; |
| -} |
| - |
| -static int autofs4_dir_readdir(struct file *file, void *dirent, filldir_t filldir) |
| -{ |
| - struct dentry *dentry = file->f_path.dentry; |
| - struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); |
| - struct dentry *cursor = file->private_data; |
| - 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; |
| + return -ENOENT; |
| } |
| + spin_unlock(&dcache_lock); |
| |
| - if (d_mountpoint(dentry)) { |
| - struct file *fp = cursor->d_fsdata; |
| - |
| - 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 dcache_readdir(file, dirent, filldir); |
| + return dcache_dir_open(inode, file); |
| } |
| |
| static int try_to_fill_dentry(struct dentry *dentry, int flags) |
| { |
| struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); |
| struct autofs_info *ino = 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 (ino && (ino->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); |
| - |
| - /* |
| - * If the directory still exists the mount request must |
| - * continue otherwise it can't be followed at the right |
| - * time during the walk. |
| - */ |
| - status = d_invalidate(dentry); |
| - if (status != -EBUSY) |
| - return -EAGAIN; |
| - } |
| + int status; |
| |
| DPRINTK("dentry=%p %.*s ino=%p", |
| dentry, dentry->d_name.len, dentry->d_name.name, dentry->d_inode); |
| @@ -291,7 +133,8 @@ static int try_to_fill_dentry(struct den |
| return status; |
| } |
| /* Trigger mount for path component or follow link */ |
| - } else if (flags & (LOOKUP_CONTINUE | LOOKUP_DIRECTORY) || |
| + } else if (dentry->d_flags & DCACHE_AUTOFS_PENDING || |
| + flags & (TRIGGER_FLAGS | TRIGGER_INTENTS) || |
| current->link_count) { |
| DPRINTK("waiting for mount name=%.*s", |
| dentry->d_name.len, dentry->d_name.name); |
| @@ -318,7 +161,8 @@ static int try_to_fill_dentry(struct den |
| spin_lock(&dentry->d_lock); |
| dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; |
| spin_unlock(&dentry->d_lock); |
| - return status; |
| + |
| + return 0; |
| } |
| |
| /* For autofs direct mounts the follow link triggers the mount */ |
| @@ -333,50 +177,62 @@ static void *autofs4_follow_link(struct |
| DPRINTK("dentry=%p %.*s oz_mode=%d nd->flags=%d", |
| dentry, dentry->d_name.len, dentry->d_name.name, oz_mode, |
| nd->flags); |
| - |
| - /* If it's our master or we shouldn't trigger a mount we're done */ |
| - lookup_type = nd->flags & (LOOKUP_CONTINUE | LOOKUP_DIRECTORY); |
| - if (oz_mode || !lookup_type) |
| + /* |
| + * For an expire of a covered direct or offset mount we need |
| + * to beeak out of follow_down() at the autofs mount trigger |
| + * (d_mounted--), so we can see the expiring flag, and manage |
| + * the blocking and following here until the expire is completed. |
| + */ |
| + if (oz_mode) { |
| + spin_lock(&sbi->fs_lock); |
| + if (ino->flags & AUTOFS_INF_EXPIRING) { |
| + spin_unlock(&sbi->fs_lock); |
| + /* Follow down to our covering mount. */ |
| + if (!follow_down(&nd->mnt, &nd->dentry)) |
| + goto done; |
| + goto follow; |
| + } |
| + spin_unlock(&sbi->fs_lock); |
| goto done; |
| + } |
| |
| - /* If an expire request is pending wait for it. */ |
| - if (ino && (ino->flags & AUTOFS_INF_EXPIRING)) { |
| - DPRINTK("waiting for active request %p name=%.*s", |
| - dentry, dentry->d_name.len, dentry->d_name.name); |
| + /* If an expire request is pending everyone must wait. */ |
| + autofs4_expire_wait(dentry); |
| |
| - status = autofs4_wait(sbi, dentry, NFY_NONE); |
| - |
| - DPRINTK("request done status=%d", status); |
| - } |
| + /* We trigger a mount for almost all flags */ |
| + lookup_type = nd->flags & (TRIGGER_FLAGS | TRIGGER_INTENTS); |
| + if (!(lookup_type || dentry->d_flags & DCACHE_AUTOFS_PENDING)) |
| + goto follow; |
| |
| /* |
| - * If the dentry contains directories then it is an |
| - * autofs multi-mount with no root mount offset. So |
| - * don't try to mount it again. |
| + * If the dentry contains directories then it is an autofs |
| + * multi-mount with no root mount offset. So don't try to |
| + * mount it again. |
| */ |
| spin_lock(&dcache_lock); |
| - if (!d_mountpoint(dentry) && __simple_empty(dentry)) { |
| + if (dentry->d_flags & DCACHE_AUTOFS_PENDING || |
| + (!d_mountpoint(dentry) && __simple_empty(dentry))) { |
| spin_unlock(&dcache_lock); |
| |
| status = try_to_fill_dentry(dentry, 0); |
| if (status) |
| goto out_error; |
| |
| - /* |
| - * The mount succeeded but if there is no root mount |
| - * it must be an autofs multi-mount with no root offset |
| - * so we don't need to follow the mount. |
| - */ |
| - if (d_mountpoint(dentry)) { |
| - if (!autofs4_follow_mount(&nd->mnt, &nd->dentry)) { |
| - status = -ENOENT; |
| - goto out_error; |
| - } |
| - } |
| - |
| - goto done; |
| + goto follow; |
| } |
| spin_unlock(&dcache_lock); |
| +follow: |
| + /* |
| + * If there is no root mount it must be an autofs |
| + * multi-mount with no root offset so we don't need |
| + * to follow it. |
| + */ |
| + if (d_mountpoint(dentry)) { |
| + if (!autofs4_follow_mount(&nd->mnt, &nd->dentry)) { |
| + status = -ENOENT; |
| + goto out_error; |
| + } |
| + } |
| |
| done: |
| return NULL; |
| @@ -401,12 +257,23 @@ static int autofs4_revalidate(struct den |
| int status = 1; |
| |
| /* Pending dentry */ |
| + spin_lock(&sbi->fs_lock); |
| if (autofs4_ispending(dentry)) { |
| /* The daemon never causes a mount to trigger */ |
| + spin_unlock(&sbi->fs_lock); |
| + |
| if (oz_mode) |
| return 1; |
| |
| /* |
| + * If the directory has gone away due to an expire |
| + * we have been called as ->d_revalidate() and so |
| + * we need to return false and proceed to ->lookup(). |
| + */ |
| + if (autofs4_expire_wait(dentry) == -EAGAIN) |
| + return 0; |
| + |
| + /* |
| * A zero status is success otherwise we have a |
| * negative error code. |
| */ |
| @@ -414,17 +281,9 @@ static int autofs4_revalidate(struct den |
| if (status == 0) |
| return 1; |
| |
| - /* |
| - * A status of EAGAIN here means that the dentry has gone |
| - * away while waiting for an expire to complete. If we are |
| - * racing with expire lookup will wait for it so this must |
| - * be a revalidate and we need to send it to lookup. |
| - */ |
| - if (status == -EAGAIN) |
| - return 0; |
| - |
| return status; |
| } |
| + spin_unlock(&sbi->fs_lock); |
| |
| /* Negative dentry.. invalidate if "old" */ |
| if (dentry->d_inode == NULL) |
| @@ -438,6 +297,7 @@ static int autofs4_revalidate(struct den |
| DPRINTK("dentry=%p %.*s, emptydir", |
| dentry, dentry->d_name.len, dentry->d_name.name); |
| spin_unlock(&dcache_lock); |
| + |
| /* The daemon never causes a mount to trigger */ |
| if (oz_mode) |
| return 1; |
| @@ -470,10 +330,12 @@ void autofs4_dentry_release(struct dentr |
| struct autofs_sb_info *sbi = autofs4_sbi(de->d_sb); |
| |
| if (sbi) { |
| - spin_lock(&sbi->rehash_lock); |
| - if (!list_empty(&inf->rehash)) |
| - list_del(&inf->rehash); |
| - spin_unlock(&sbi->rehash_lock); |
| + spin_lock(&sbi->lookup_lock); |
| + if (!list_empty(&inf->active)) |
| + list_del(&inf->active); |
| + if (!list_empty(&inf->expiring)) |
| + list_del(&inf->expiring); |
| + spin_unlock(&sbi->lookup_lock); |
| } |
| |
| inf->dentry = NULL; |
| @@ -495,7 +357,59 @@ static struct dentry_operations autofs4_ |
| .d_release = autofs4_dentry_release, |
| }; |
| |
| -static struct dentry *autofs4_lookup_unhashed(struct autofs_sb_info *sbi, struct dentry *parent, struct qstr *name) |
| +static struct dentry *autofs4_lookup_active(struct autofs_sb_info *sbi, struct dentry *parent, struct qstr *name) |
| +{ |
| + unsigned int len = name->len; |
| + unsigned int hash = name->hash; |
| + const unsigned char *str = name->name; |
| + struct list_head *p, *head; |
| + |
| + spin_lock(&dcache_lock); |
| + spin_lock(&sbi->lookup_lock); |
| + head = &sbi->active_list; |
| + list_for_each(p, head) { |
| + struct autofs_info *ino; |
| + struct dentry *dentry; |
| + struct qstr *qstr; |
| + |
| + ino = list_entry(p, struct autofs_info, active); |
| + dentry = ino->dentry; |
| + |
| + spin_lock(&dentry->d_lock); |
| + |
| + /* Already gone? */ |
| + if (atomic_read(&dentry->d_count) == 0) |
| + goto next; |
| + |
| + qstr = &dentry->d_name; |
| + |
| + if (dentry->d_name.hash != hash) |
| + goto next; |
| + if (dentry->d_parent != parent) |
| + goto next; |
| + |
| + if (qstr->len != len) |
| + goto next; |
| + if (memcmp(qstr->name, str, len)) |
| + goto next; |
| + |
| + if (d_unhashed(dentry)) { |
| + dget(dentry); |
| + spin_unlock(&dentry->d_lock); |
| + spin_unlock(&sbi->lookup_lock); |
| + spin_unlock(&dcache_lock); |
| + return dentry; |
| + } |
| +next: |
| + spin_unlock(&dentry->d_lock); |
| + } |
| + spin_unlock(&sbi->lookup_lock); |
| + spin_unlock(&dcache_lock); |
| + |
| + return NULL; |
| +} |
| + |
| +static struct dentry *autofs4_lookup_expiring(struct autofs_sb_info *sbi, struct dentry *parent, struct qstr *name) |
| { |
| unsigned int len = name->len; |
| unsigned int hash = name->hash; |
| @@ -503,14 +417,14 @@ static struct dentry *autofs4_lookup_unh |
| struct list_head *p, *head; |
| |
| spin_lock(&dcache_lock); |
| - spin_lock(&sbi->rehash_lock); |
| - head = &sbi->rehash_list; |
| + spin_lock(&sbi->lookup_lock); |
| + head = &sbi->expiring_list; |
| list_for_each(p, head) { |
| struct autofs_info *ino; |
| struct dentry *dentry; |
| struct qstr *qstr; |
| |
| - ino = list_entry(p, struct autofs_info, rehash); |
| + ino = list_entry(p, struct autofs_info, expiring); |
| dentry = ino->dentry; |
| |
| spin_lock(&dentry->d_lock); |
| @@ -532,33 +446,16 @@ static struct dentry *autofs4_lookup_unh |
| goto next; |
| |
| if (d_unhashed(dentry)) { |
| - struct autofs_info *ino = autofs4_dentry_ino(dentry); |
| - struct inode *inode = dentry->d_inode; |
| - |
| - list_del_init(&ino->rehash); |
| dget(dentry); |
| - /* |
| - * Make the rehashed dentry negative so the VFS |
| - * behaves as it should. |
| - */ |
| - if (inode) { |
| - dentry->d_inode = NULL; |
| - list_del_init(&dentry->d_alias); |
| - spin_unlock(&dentry->d_lock); |
| - spin_unlock(&sbi->rehash_lock); |
| - spin_unlock(&dcache_lock); |
| - iput(inode); |
| - return dentry; |
| - } |
| spin_unlock(&dentry->d_lock); |
| - spin_unlock(&sbi->rehash_lock); |
| + spin_unlock(&sbi->lookup_lock); |
| spin_unlock(&dcache_lock); |
| return dentry; |
| } |
| next: |
| spin_unlock(&dentry->d_lock); |
| } |
| - spin_unlock(&sbi->rehash_lock); |
| + spin_unlock(&sbi->lookup_lock); |
| spin_unlock(&dcache_lock); |
| |
| return NULL; |
| @@ -568,7 +465,8 @@ next: |
| static struct dentry *autofs4_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd) |
| { |
| struct autofs_sb_info *sbi; |
| - struct dentry *unhashed; |
| + struct autofs_info *ino; |
| + struct dentry *expiring, *unhashed; |
| int oz_mode; |
| |
| DPRINTK("name = %.*s", |
| @@ -584,50 +482,67 @@ static struct dentry *autofs4_lookup(str |
| DPRINTK("pid = %u, pgrp = %u, catatonic = %d, oz_mode = %d", |
| current->pid, process_group(current), sbi->catatonic, oz_mode); |
| |
| - unhashed = autofs4_lookup_unhashed(sbi, dentry->d_parent, &dentry->d_name); |
| - if (!unhashed) { |
| + unhashed = autofs4_lookup_active(sbi, dentry->d_parent, &dentry->d_name); |
| + if (unhashed) |
| + dentry = unhashed; |
| + else { |
| /* |
| - * 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. |
| + * Mark the dentry incomplete but don't hash it. We do this |
| + * to serialize our inode creation operations (symlink and |
| + * mkdir) which prevents deadlock during the callback to |
| + * the daemon. Subsequent user space lookups for the same |
| + * dentry are placed on the wait queue while the daemon |
| + * itself is allowed passage unresticted so the create |
| + * operation itself can then hash the dentry. Finally, |
| + * we check for the hashed dentry and return the newly |
| + * hashed dentry. |
| */ |
| dentry->d_op = &autofs4_root_dentry_operations; |
| |
| - dentry->d_fsdata = NULL; |
| - d_add(dentry, NULL); |
| - } else { |
| - struct autofs_info *ino = autofs4_dentry_ino(unhashed); |
| - DPRINTK("rehash %p with %p", dentry, unhashed); |
| /* |
| - * If we are racing with expire the request might not |
| - * be quite complete but the directory has been removed |
| - * so it must have been successful, so just wait for it. |
| + * And we need to ensure that the same dentry is used for |
| + * all following lookup calls until it is hashed so that |
| + * the dentry flags are persistent throughout the request. |
| */ |
| - if (ino && (ino->flags & AUTOFS_INF_EXPIRING)) { |
| - DPRINTK("wait for incomplete expire %p name=%.*s", |
| - unhashed, unhashed->d_name.len, |
| - unhashed->d_name.name); |
| - autofs4_wait(sbi, unhashed, NFY_NONE); |
| - DPRINTK("request completed"); |
| - } |
| - d_rehash(unhashed); |
| - dentry = unhashed; |
| + ino = autofs4_init_ino(NULL, sbi, 0555); |
| + if (!ino) |
| + return ERR_PTR(-ENOMEM); |
| + |
| + dentry->d_fsdata = ino; |
| + ino->dentry = dentry; |
| + |
| + spin_lock(&sbi->lookup_lock); |
| + list_add(&ino->active, &sbi->active_list); |
| + spin_unlock(&sbi->lookup_lock); |
| + |
| + d_instantiate(dentry, NULL); |
| } |
| |
| if (!oz_mode) { |
| + mutex_unlock(&dir->i_mutex); |
| + expiring = autofs4_lookup_expiring(sbi, |
| + dentry->d_parent, |
| + &dentry->d_name); |
| + if (expiring) { |
| + /* |
| + * If we are racing with expire the request might not |
| + * be quite complete but the directory has been removed |
| + * so it must have been successful, so just wait for it. |
| + */ |
| + ino = autofs4_dentry_ino(expiring); |
| + autofs4_expire_wait(expiring); |
| + spin_lock(&sbi->lookup_lock); |
| + if (!list_empty(&ino->expiring)) |
| + list_del_init(&ino->expiring); |
| + spin_unlock(&sbi->lookup_lock); |
| + dput(expiring); |
| + } |
| + |
| spin_lock(&dentry->d_lock); |
| dentry->d_flags |= DCACHE_AUTOFS_PENDING; |
| spin_unlock(&dentry->d_lock); |
| - } |
| - |
| - if (dentry->d_op && dentry->d_op->d_revalidate) { |
| - mutex_unlock(&dir->i_mutex); |
| - (dentry->d_op->d_revalidate)(dentry, nd); |
| + if (dentry->d_op && dentry->d_op->d_revalidate) |
| + (dentry->d_op->d_revalidate)(dentry, nd); |
| mutex_lock(&dir->i_mutex); |
| } |
| |
| @@ -647,9 +562,11 @@ static struct dentry *autofs4_lookup(str |
| return ERR_PTR(-ERESTARTNOINTR); |
| } |
| } |
| - spin_lock(&dentry->d_lock); |
| - dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; |
| - spin_unlock(&dentry->d_lock); |
| + if (!oz_mode) { |
| + spin_lock(&dentry->d_lock); |
| + dentry->d_flags &= ~DCACHE_AUTOFS_PENDING; |
| + spin_unlock(&dentry->d_lock); |
| + } |
| } |
| |
| /* |
| @@ -658,7 +575,7 @@ static struct dentry *autofs4_lookup(str |
| * for all system calls, but it should be OK for the operations |
| * we permit from an autofs. |
| */ |
| - if (dentry->d_inode && d_unhashed(dentry)) { |
| + if (!oz_mode && d_unhashed(dentry)) { |
| /* |
| * A user space application can (and has done in the past) |
| * remove and re-create this directory during the callback. |
| @@ -680,7 +597,7 @@ static struct dentry *autofs4_lookup(str |
| } |
| |
| if (unhashed) |
| - return dentry; |
| + return unhashed; |
| |
| return NULL; |
| } |
| @@ -702,21 +619,32 @@ static int autofs4_dir_symlink(struct in |
| return -EACCES; |
| |
| ino = autofs4_init_ino(ino, sbi, S_IFLNK | 0555); |
| - if (ino == NULL) |
| - return -ENOSPC; |
| + if (!ino) |
| + return -ENOMEM; |
| |
| - ino->size = strlen(symname); |
| - ino->u.symlink = cp = kmalloc(ino->size + 1, GFP_KERNEL); |
| + spin_lock(&sbi->lookup_lock); |
| + if (!list_empty(&ino->active)) |
| + list_del_init(&ino->active); |
| + spin_unlock(&sbi->lookup_lock); |
| |
| - if (cp == NULL) { |
| - kfree(ino); |
| - return -ENOSPC; |
| + ino->size = strlen(symname); |
| + cp = kmalloc(ino->size + 1, GFP_KERNEL); |
| + if (!cp) { |
| + if (!dentry->d_fsdata) |
| + kfree(ino); |
| + return -ENOMEM; |
| } |
| |
| strcpy(cp, symname); |
| |
| inode = autofs4_get_inode(dir->i_sb, ino); |
| - d_instantiate(dentry, inode); |
| + if (!inode) { |
| + kfree(cp); |
| + if (!dentry->d_fsdata) |
| + kfree(ino); |
| + return -ENOMEM; |
| + } |
| + d_add(dentry, inode); |
| |
| if (dir == dir->i_sb->s_root->d_inode) |
| dentry->d_op = &autofs4_root_dentry_operations; |
| @@ -731,6 +659,7 @@ static int autofs4_dir_symlink(struct in |
| atomic_inc(&p_ino->count); |
| ino->inode = inode; |
| |
| + ino->u.symlink = cp; |
| dir->i_mtime = CURRENT_TIME; |
| |
| return 0; |
| @@ -743,9 +672,8 @@ static int autofs4_dir_symlink(struct in |
| * 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 the unlink is probably the result of an expire. |
| - * We simply d_drop it and add it to a rehash candidates list in the |
| - * super block, which allows the dentry lookup to reuse it retaining |
| - * the flags, such as expire in progress, in case we're racing with expire. |
| + * We simply d_drop it and add it to a expiring list in the super block, |
| + * which allows the dentry lookup to check for an incomplete expire. |
| * |
| * 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. |
| @@ -775,9 +703,10 @@ static int autofs4_dir_unlink(struct ino |
| dir->i_mtime = CURRENT_TIME; |
| |
| spin_lock(&dcache_lock); |
| - spin_lock(&sbi->rehash_lock); |
| - list_add(&ino->rehash, &sbi->rehash_list); |
| - spin_unlock(&sbi->rehash_lock); |
| + spin_lock(&sbi->lookup_lock); |
| + if (list_empty(&ino->expiring)) |
| + list_add(&ino->expiring, &sbi->expiring_list); |
| + spin_unlock(&sbi->lookup_lock); |
| spin_lock(&dentry->d_lock); |
| __d_drop(dentry); |
| spin_unlock(&dentry->d_lock); |
| @@ -803,9 +732,10 @@ static int autofs4_dir_rmdir(struct inod |
| spin_unlock(&dcache_lock); |
| return -ENOTEMPTY; |
| } |
| - spin_lock(&sbi->rehash_lock); |
| - list_add(&ino->rehash, &sbi->rehash_list); |
| - spin_unlock(&sbi->rehash_lock); |
| + spin_lock(&sbi->lookup_lock); |
| + if (list_empty(&ino->expiring)) |
| + list_add(&ino->expiring, &sbi->expiring_list); |
| + spin_unlock(&sbi->lookup_lock); |
| spin_lock(&dentry->d_lock); |
| __d_drop(dentry); |
| spin_unlock(&dentry->d_lock); |
| @@ -840,11 +770,21 @@ static int autofs4_dir_mkdir(struct inod |
| dentry, dentry->d_name.len, dentry->d_name.name); |
| |
| ino = autofs4_init_ino(ino, sbi, S_IFDIR | 0555); |
| - if (ino == NULL) |
| - return -ENOSPC; |
| + if (!ino) |
| + return -ENOMEM; |
| + |
| + spin_lock(&sbi->lookup_lock); |
| + if (!list_empty(&ino->active)) |
| + list_del_init(&ino->active); |
| + spin_unlock(&sbi->lookup_lock); |
| |
| inode = autofs4_get_inode(dir->i_sb, ino); |
| - d_instantiate(dentry, inode); |
| + if (!inode) { |
| + if (!dentry->d_fsdata) |
| + kfree(ino); |
| + return -ENOMEM; |
| + } |
| + d_add(dentry, inode); |
| |
| if (dir == dir->i_sb->s_root->d_inode) |
| dentry->d_op = &autofs4_root_dentry_operations; |
| @@ -896,44 +836,6 @@ static inline int autofs4_get_protosubve |
| } |
| |
| /* |
| - * 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) |
| @@ -997,11 +899,6 @@ static int autofs4_root_ioctl(struct ino |
| 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_path.mnt, p); |
| |
| |
| |
| @@ -28,6 +28,12 @@ void autofs4_catatonic_mode(struct autof |
| { |
| struct autofs_wait_queue *wq, *nwq; |
| |
| + mutex_lock(&sbi->wq_mutex); |
| + if (sbi->catatonic) { |
| + mutex_unlock(&sbi->wq_mutex); |
| + return; |
| + } |
| + |
| DPRINTK("entering catatonic mode"); |
| |
| sbi->catatonic = 1; |
| @@ -36,13 +42,18 @@ void autofs4_catatonic_mode(struct autof |
| while (wq) { |
| nwq = wq->next; |
| wq->status = -ENOENT; /* Magic is gone - report failure */ |
| - kfree(wq->name); |
| - wq->name = NULL; |
| + if (wq->name.name) { |
| + kfree(wq->name.name); |
| + wq->name.name = NULL; |
| + } |
| + wq->wait_ctr--; |
| wake_up_interruptible(&wq->queue); |
| wq = nwq; |
| } |
| fput(sbi->pipe); /* Close the pipe */ |
| sbi->pipe = NULL; |
| + sbi->pipefd = -1; |
| + mutex_unlock(&sbi->wq_mutex); |
| } |
| |
| static int autofs4_write(struct file *file, const void *addr, int bytes) |
| @@ -89,10 +100,11 @@ static void autofs4_notify_daemon(struct |
| union autofs_packet_union v4_pkt; |
| union autofs_v5_packet_union v5_pkt; |
| } pkt; |
| + struct file *pipe = NULL; |
| size_t pktsz; |
| |
| DPRINTK("wait id = 0x%08lx, name = %.*s, type=%d", |
| - wq->wait_queue_token, wq->len, wq->name, type); |
| + wq->wait_queue_token, wq->name.len, wq->name.name, type); |
| |
| memset(&pkt,0,sizeof pkt); /* For security reasons */ |
| |
| @@ -107,9 +119,9 @@ static void autofs4_notify_daemon(struct |
| 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'; |
| + mp->len = wq->name.len; |
| + memcpy(mp->name, wq->name.name, wq->name.len); |
| + mp->name[wq->name.len] = '\0'; |
| break; |
| } |
| case autofs_ptype_expire_multi: |
| @@ -119,9 +131,9 @@ static void autofs4_notify_daemon(struct |
| 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'; |
| + ep->len = wq->name.len; |
| + memcpy(ep->name, wq->name.name, wq->name.len); |
| + ep->name[wq->name.len] = '\0'; |
| break; |
| } |
| /* |
| @@ -138,9 +150,9 @@ static void autofs4_notify_daemon(struct |
| pktsz = sizeof(*packet); |
| |
| packet->wait_queue_token = wq->wait_queue_token; |
| - packet->len = wq->len; |
| - memcpy(packet->name, wq->name, wq->len); |
| - packet->name[wq->len] = '\0'; |
| + packet->len = wq->name.len; |
| + memcpy(packet->name, wq->name.name, wq->name.len); |
| + packet->name[wq->name.len] = '\0'; |
| packet->dev = wq->dev; |
| packet->ino = wq->ino; |
| packet->uid = wq->uid; |
| @@ -154,8 +166,19 @@ static void autofs4_notify_daemon(struct |
| return; |
| } |
| |
| - if (autofs4_write(sbi->pipe, &pkt, pktsz)) |
| - autofs4_catatonic_mode(sbi); |
| + /* Check if we have become catatonic */ |
| + mutex_lock(&sbi->wq_mutex); |
| + if (!sbi->catatonic) { |
| + pipe = sbi->pipe; |
| + get_file(pipe); |
| + } |
| + mutex_unlock(&sbi->wq_mutex); |
| + |
| + if (pipe) { |
| + if (autofs4_write(pipe, &pkt, pktsz)) |
| + autofs4_catatonic_mode(sbi); |
| + fput(pipe); |
| + } |
| } |
| |
| static int autofs4_getpath(struct autofs_sb_info *sbi, |
| @@ -171,7 +194,7 @@ static int autofs4_getpath(struct autofs |
| for (tmp = dentry ; tmp != root ; tmp = tmp->d_parent) |
| len += tmp->d_name.len + 1; |
| |
| - if (--len > NAME_MAX) { |
| + if (!len || --len > NAME_MAX) { |
| spin_unlock(&dcache_lock); |
| return 0; |
| } |
| @@ -191,58 +214,55 @@ static int autofs4_getpath(struct autofs |
| } |
| |
| static struct autofs_wait_queue * |
| -autofs4_find_wait(struct autofs_sb_info *sbi, |
| - char *name, unsigned int hash, unsigned int len) |
| +autofs4_find_wait(struct autofs_sb_info *sbi, struct qstr *qstr) |
| { |
| struct autofs_wait_queue *wq; |
| |
| for (wq = sbi->queues; wq; wq = wq->next) { |
| - if (wq->hash == hash && |
| - wq->len == len && |
| - wq->name && !memcmp(wq->name, name, len)) |
| + if (wq->name.hash == qstr->hash && |
| + wq->name.len == qstr->len && |
| + wq->name.name && |
| + !memcmp(wq->name.name, qstr->name, qstr->len)) |
| break; |
| } |
| return wq; |
| } |
| |
| -int autofs4_wait(struct autofs_sb_info *sbi, struct dentry *dentry, |
| - enum autofs_notify notify) |
| +/* |
| + * Check if we have a valid request. |
| + * Returns |
| + * 1 if the request should continue. |
| + * In this case we can return an autofs_wait_queue entry if one is |
| + * found or NULL to idicate a new wait needs to be created. |
| + * 0 or a negative errno if the request shouldn't continue. |
| + */ |
| +static int validate_request(struct autofs_wait_queue **wait, |
| + struct autofs_sb_info *sbi, |
| + struct qstr *qstr, |
| + struct dentry*dentry, enum autofs_notify notify) |
| { |
| - struct autofs_info *ino; |
| struct autofs_wait_queue *wq; |
| - char *name; |
| - unsigned int len = 0; |
| - unsigned int hash = 0; |
| - int status, type; |
| - |
| - /* 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; |
| + struct autofs_info *ino; |
| |
| - /* If this is a direct mount request create a dummy name */ |
| - if (IS_ROOT(dentry) && (sbi->type & AUTOFS_TYPE_DIRECT)) |
| - len = sprintf(name, "%p", dentry); |
| - else { |
| - len = autofs4_getpath(sbi, dentry, &name); |
| - if (!len) { |
| - kfree(name); |
| - return -ENOENT; |
| - } |
| + /* Wait in progress, continue; */ |
| + wq = autofs4_find_wait(sbi, qstr); |
| + if (wq) { |
| + *wait = wq; |
| + return 1; |
| } |
| - hash = full_name_hash(name, len); |
| |
| - if (mutex_lock_interruptible(&sbi->wq_mutex)) { |
| - kfree(name); |
| - return -EINTR; |
| - } |
| + *wait = NULL; |
| |
| - wq = autofs4_find_wait(sbi, name, hash, len); |
| + /* If we don't yet have any info this is a new request */ |
| ino = autofs4_dentry_ino(dentry); |
| - if (!wq && ino && notify == NFY_NONE) { |
| + if (!ino) |
| + return 1; |
| + |
| + /* |
| + * If we've been asked to wait on an existing expire (NFY_NONE) |
| + * but there is no wait in the queue ... |
| + */ |
| + if (notify == NFY_NONE) { |
| /* |
| * Either we've betean the pending expire to post it's |
| * wait or it finished while we waited on the mutex. |
| @@ -253,13 +273,14 @@ int autofs4_wait(struct autofs_sb_info * |
| while (ino->flags & AUTOFS_INF_EXPIRING) { |
| mutex_unlock(&sbi->wq_mutex); |
| schedule_timeout_interruptible(HZ/10); |
| - if (mutex_lock_interruptible(&sbi->wq_mutex)) { |
| - kfree(name); |
| + if (mutex_lock_interruptible(&sbi->wq_mutex)) |
| return -EINTR; |
| + |
| + wq = autofs4_find_wait(sbi, qstr); |
| + if (wq) { |
| + *wait = wq; |
| + return 1; |
| } |
| - wq = autofs4_find_wait(sbi, name, hash, len); |
| - if (wq) |
| - break; |
| } |
| |
| /* |
| @@ -267,18 +288,90 @@ int autofs4_wait(struct autofs_sb_info * |
| * cases where we wait on NFY_NONE neither depend on the |
| * return status of the wait. |
| */ |
| - if (!wq) { |
| - kfree(name); |
| - mutex_unlock(&sbi->wq_mutex); |
| + return 0; |
| + } |
| + |
| + /* |
| + * If we've been asked to trigger a mount and the request |
| + * completed while we waited on the mutex ... |
| + */ |
| + if (notify == NFY_MOUNT) { |
| + /* |
| + * If the dentry was successfully mounted while we slept |
| + * on the wait queue mutex we can return success. If it |
| + * isn't mounted (doesn't have submounts for the case of |
| + * a multi-mount with no mount at it's base) we can |
| + * continue on and create a new request. |
| + */ |
| + if (have_submounts(dentry)) |
| return 0; |
| + } |
| + |
| + return 1; |
| +} |
| + |
| +int autofs4_wait(struct autofs_sb_info *sbi, struct dentry *dentry, |
| + enum autofs_notify notify) |
| +{ |
| + struct autofs_wait_queue *wq; |
| + struct qstr qstr; |
| + char *name; |
| + int status, ret, type; |
| + |
| + /* In catatonic mode, we don't wait for nobody */ |
| + if (sbi->catatonic) |
| + return -ENOENT; |
| + |
| + if (!dentry->d_inode) { |
| + /* |
| + * A wait for a negative dentry is invalid for certain |
| + * cases. A direct or offset mount "always" has its mount |
| + * point directory created and so the request dentry must |
| + * be positive or the map key doesn't exist. The situation |
| + * is very similar for indirect mounts except only dentrys |
| + * in the root of the autofs file system may be negative. |
| + */ |
| + if (autofs_type_trigger(sbi->type)) |
| + return -ENOENT; |
| + else if (!IS_ROOT(dentry->d_parent)) |
| + return -ENOENT; |
| + } |
| + |
| + name = kmalloc(NAME_MAX + 1, GFP_KERNEL); |
| + if (!name) |
| + return -ENOMEM; |
| + |
| + /* If this is a direct mount request create a dummy name */ |
| + if (IS_ROOT(dentry) && autofs_type_trigger(sbi->type)) |
| + qstr.len = sprintf(name, "%p", dentry); |
| + else { |
| + qstr.len = autofs4_getpath(sbi, dentry, &name); |
| + if (!qstr.len) { |
| + kfree(name); |
| + return -ENOENT; |
| } |
| } |
| + qstr.name = name; |
| + qstr.hash = full_name_hash(name, qstr.len); |
| + |
| + if (mutex_lock_interruptible(&sbi->wq_mutex)) { |
| + kfree(qstr.name); |
| + return -EINTR; |
| + } |
| + |
| + ret = validate_request(&wq, sbi, &qstr, dentry, notify); |
| + if (ret <= 0) { |
| + if (ret == 0) |
| + mutex_unlock(&sbi->wq_mutex); |
| + kfree(qstr.name); |
| + return ret; |
| + } |
| |
| if (!wq) { |
| /* Create a new wait queue */ |
| wq = kmalloc(sizeof(struct autofs_wait_queue),GFP_KERNEL); |
| if (!wq) { |
| - kfree(name); |
| + kfree(qstr.name); |
| mutex_unlock(&sbi->wq_mutex); |
| return -ENOMEM; |
| } |
| @@ -289,9 +382,7 @@ int autofs4_wait(struct autofs_sb_info * |
| wq->next = sbi->queues; |
| sbi->queues = wq; |
| init_waitqueue_head(&wq->queue); |
| - wq->hash = hash; |
| - wq->name = name; |
| - wq->len = len; |
| + memcpy(&wq->name, &qstr, sizeof(struct qstr)); |
| wq->dev = autofs4_get_dev(sbi); |
| wq->ino = autofs4_get_ino(sbi); |
| wq->uid = current->uid; |
| @@ -299,7 +390,7 @@ int autofs4_wait(struct autofs_sb_info * |
| wq->pid = current->pid; |
| wq->tgid = current->tgid; |
| wq->status = -EINTR; /* Status return if interrupted */ |
| - atomic_set(&wq->wait_ctr, 2); |
| + wq->wait_ctr = 2; |
| mutex_unlock(&sbi->wq_mutex); |
| |
| if (sbi->version < 5) { |
| @@ -309,38 +400,35 @@ int autofs4_wait(struct autofs_sb_info * |
| type = autofs_ptype_expire_multi; |
| } else { |
| if (notify == NFY_MOUNT) |
| - type = (sbi->type & AUTOFS_TYPE_DIRECT) ? |
| + type = autofs_type_trigger(sbi->type) ? |
| autofs_ptype_missing_direct : |
| autofs_ptype_missing_indirect; |
| else |
| - type = (sbi->type & AUTOFS_TYPE_DIRECT) ? |
| + type = autofs_type_trigger(sbi->type) ? |
| autofs_ptype_expire_direct : |
| autofs_ptype_expire_indirect; |
| } |
| |
| DPRINTK("new wait id = 0x%08lx, name = %.*s, nfy=%d\n", |
| - (unsigned long) wq->wait_queue_token, wq->len, wq->name, notify); |
| + (unsigned long) wq->wait_queue_token, wq->name.len, |
| + wq->name.name, notify); |
| |
| /* autofs4_notify_daemon() may block */ |
| autofs4_notify_daemon(sbi, wq, type); |
| } else { |
| - atomic_inc(&wq->wait_ctr); |
| + wq->wait_ctr++; |
| mutex_unlock(&sbi->wq_mutex); |
| - kfree(name); |
| + kfree(qstr.name); |
| DPRINTK("existing wait id = 0x%08lx, name = %.*s, nfy=%d", |
| - (unsigned long) wq->wait_queue_token, wq->len, wq->name, notify); |
| + (unsigned long) wq->wait_queue_token, wq->name.len, |
| + wq->name.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; |
| - kfree(wq->name); |
| - wq->name = NULL; |
| - } |
| - |
| - if (wq->name) { |
| + /* |
| + * wq->name.name is NULL iff the lock is already released |
| + * or the mount has been made catatonic. |
| + */ |
| + if (wq->name.name) { |
| /* Block all but "shutdown" signals while waiting */ |
| sigset_t oldset; |
| unsigned long irqflags; |
| @@ -351,7 +439,7 @@ int autofs4_wait(struct autofs_sb_info * |
| recalc_sigpending(); |
| spin_unlock_irqrestore(¤t->sighand->siglock, irqflags); |
| |
| - wait_event_interruptible(wq->queue, wq->name == NULL); |
| + wait_event_interruptible(wq->queue, wq->name.name == NULL); |
| |
| spin_lock_irqsave(¤t->sighand->siglock, irqflags); |
| current->blocked = oldset; |
| @@ -363,9 +451,45 @@ int autofs4_wait(struct autofs_sb_info * |
| |
| status = wq->status; |
| |
| + /* |
| + * For direct and offset mounts we need to track the requester's |
| + * uid and gid in the dentry info struct. This is so it can be |
| + * supplied, on request, by the misc device ioctl interface. |
| + * This is needed during daemon resatart when reconnecting |
| + * to existing, active, autofs mounts. The uid and gid (and |
| + * related string values) may be used for macro substitution |
| + * in autofs mount maps. |
| + */ |
| + if (!status) { |
| + struct autofs_info *ino; |
| + struct dentry *de = NULL; |
| + |
| + /* direct mount or browsable map */ |
| + ino = autofs4_dentry_ino(dentry); |
| + if (!ino) { |
| + /* If not lookup actual dentry used */ |
| + de = d_lookup(dentry->d_parent, &dentry->d_name); |
| + if (de) |
| + ino = autofs4_dentry_ino(de); |
| + } |
| + |
| + /* Set mount requester */ |
| + if (ino) { |
| + spin_lock(&sbi->fs_lock); |
| + ino->uid = wq->uid; |
| + ino->gid = wq->gid; |
| + spin_unlock(&sbi->fs_lock); |
| + } |
| + |
| + if (de) |
| + dput(de); |
| + } |
| + |
| /* Are we the last process to need status? */ |
| - if (atomic_dec_and_test(&wq->wait_ctr)) |
| + mutex_lock(&sbi->wq_mutex); |
| + if (!--wq->wait_ctr) |
| kfree(wq); |
| + mutex_unlock(&sbi->wq_mutex); |
| |
| return status; |
| } |
| @@ -387,16 +511,13 @@ int autofs4_wait_release(struct autofs_s |
| } |
| |
| *wql = wq->next; /* Unlink from chain */ |
| - mutex_unlock(&sbi->wq_mutex); |
| - kfree(wq->name); |
| - wq->name = NULL; /* Do not wait on this queue */ |
| - |
| + kfree(wq->name.name); |
| + wq->name.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? */ |
| + wake_up_interruptible(&wq->queue); |
| + if (!--wq->wait_ctr) |
| kfree(wq); |
| - else |
| - wake_up_interruptible(&wq->queue); |
| + mutex_unlock(&sbi->wq_mutex); |
| |
| return 0; |
| } |
| |
| |
| @@ -56,12 +56,25 @@ static int autofs4_mount_busy(struct vfs |
| mntget(mnt); |
| dget(dentry); |
| |
| - if (!autofs4_follow_mount(&mnt, &dentry)) |
| + if (!follow_down(&mnt, &dentry)) |
| goto done; |
| |
| - /* This is an autofs submount, we can't expire it */ |
| - if (is_autofs4_dentry(dentry)) |
| - goto done; |
| + if (is_autofs4_dentry(dentry)) { |
| + struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); |
| + |
| + /* This is an autofs submount, we can't expire it */ |
| + if (autofs_type_indirect(sbi->type)) |
| + goto done; |
| + |
| + /* |
| + * Otherwise it's an offset mount and we need to check |
| + * if we can umount its mount, if there is one. |
| + */ |
| + if (!d_mountpoint(dentry)) { |
| + status = 0; |
| + goto done; |
| + } |
| + } |
| |
| /* Update the expiry counter if fs is busy */ |
| if (!may_umount_tree(mnt)) { |
| @@ -73,8 +86,8 @@ static int autofs4_mount_busy(struct vfs |
| status = 0; |
| done: |
| DPRINTK("returning = %d", status); |
| - mntput(mnt); |
| dput(dentry); |
| + mntput(mnt); |
| return status; |
| } |
| |
| @@ -244,10 +257,10 @@ cont: |
| } |
| |
| /* Check if we can expire a direct mount (possibly a tree) */ |
| -static struct dentry *autofs4_expire_direct(struct super_block *sb, |
| - struct vfsmount *mnt, |
| - struct autofs_sb_info *sbi, |
| - int how) |
| +struct dentry *autofs4_expire_direct(struct super_block *sb, |
| + struct vfsmount *mnt, |
| + struct autofs_sb_info *sbi, |
| + int how) |
| { |
| unsigned long timeout; |
| struct dentry *root = dget(sb->s_root); |
| @@ -259,13 +272,15 @@ static struct dentry *autofs4_expire_dir |
| now = jiffies; |
| timeout = sbi->exp_timeout; |
| |
| - /* Lock the tree as we must expire as a whole */ |
| spin_lock(&sbi->fs_lock); |
| if (!autofs4_direct_busy(mnt, root, timeout, do_now)) { |
| struct autofs_info *ino = autofs4_dentry_ino(root); |
| - |
| - /* Set this flag early to catch sys_chdir and the like */ |
| + if (d_mountpoint(root)) { |
| + ino->flags |= AUTOFS_INF_MOUNTPOINT; |
| + root->d_mounted--; |
| + } |
| ino->flags |= AUTOFS_INF_EXPIRING; |
| + init_completion(&ino->expire_complete); |
| spin_unlock(&sbi->fs_lock); |
| return root; |
| } |
| @@ -281,10 +296,10 @@ static struct dentry *autofs4_expire_dir |
| * - it is unused by any user process |
| * - it has been unused for exp_timeout time |
| */ |
| -static struct dentry *autofs4_expire_indirect(struct super_block *sb, |
| - struct vfsmount *mnt, |
| - struct autofs_sb_info *sbi, |
| - int how) |
| +struct dentry *autofs4_expire_indirect(struct super_block *sb, |
| + struct vfsmount *mnt, |
| + struct autofs_sb_info *sbi, |
| + int how) |
| { |
| unsigned long timeout; |
| struct dentry *root = sb->s_root; |
| @@ -292,6 +307,8 @@ static struct dentry *autofs4_expire_ind |
| struct list_head *next; |
| int do_now = how & AUTOFS_EXP_IMMEDIATE; |
| int exp_leaves = how & AUTOFS_EXP_LEAVES; |
| + struct autofs_info *ino; |
| + unsigned int ino_count; |
| |
| if (!root) |
| return NULL; |
| @@ -316,6 +333,9 @@ static struct dentry *autofs4_expire_ind |
| dentry = dget(dentry); |
| spin_unlock(&dcache_lock); |
| |
| + spin_lock(&sbi->fs_lock); |
| + ino = autofs4_dentry_ino(dentry); |
| + |
| /* |
| * Case 1: (i) indirect mount or top level pseudo direct mount |
| * (autofs-4.1). |
| @@ -326,6 +346,11 @@ static struct dentry *autofs4_expire_ind |
| DPRINTK("checking mountpoint %p %.*s", |
| dentry, (int)dentry->d_name.len, dentry->d_name.name); |
| |
| + /* Path walk currently on this dentry? */ |
| + ino_count = atomic_read(&ino->count) + 2; |
| + if (atomic_read(&dentry->d_count) > ino_count) |
| + goto next; |
| + |
| /* Can we umount this guy */ |
| if (autofs4_mount_busy(mnt, dentry)) |
| goto next; |
| @@ -333,7 +358,7 @@ static struct dentry *autofs4_expire_ind |
| /* Can we expire this guy */ |
| if (autofs4_can_expire(dentry, timeout, do_now)) { |
| expired = dentry; |
| - break; |
| + goto found; |
| } |
| goto next; |
| } |
| @@ -343,46 +368,80 @@ static struct dentry *autofs4_expire_ind |
| |
| /* Case 2: tree mount, expire iff entire tree is not busy */ |
| if (!exp_leaves) { |
| - /* Lock the tree as we must expire as a whole */ |
| - spin_lock(&sbi->fs_lock); |
| - if (!autofs4_tree_busy(mnt, dentry, timeout, do_now)) { |
| - struct autofs_info *inf = autofs4_dentry_ino(dentry); |
| + /* Path walk currently on this dentry? */ |
| + ino_count = atomic_read(&ino->count) + 1; |
| + if (atomic_read(&dentry->d_count) > ino_count) |
| + goto next; |
| |
| - /* Set this flag early to catch sys_chdir and the like */ |
| - inf->flags |= AUTOFS_INF_EXPIRING; |
| - spin_unlock(&sbi->fs_lock); |
| + if (!autofs4_tree_busy(mnt, dentry, timeout, do_now)) { |
| expired = dentry; |
| - break; |
| + goto found; |
| } |
| - spin_unlock(&sbi->fs_lock); |
| /* |
| * Case 3: pseudo direct mount, expire individual leaves |
| * (autofs-4.1). |
| */ |
| } else { |
| + /* Path walk currently on this dentry? */ |
| + ino_count = atomic_read(&ino->count) + 1; |
| + if (atomic_read(&dentry->d_count) > ino_count) |
| + goto next; |
| + |
| expired = autofs4_check_leaves(mnt, dentry, timeout, do_now); |
| if (expired) { |
| dput(dentry); |
| - break; |
| + goto found; |
| } |
| } |
| next: |
| + spin_unlock(&sbi->fs_lock); |
| dput(dentry); |
| spin_lock(&dcache_lock); |
| next = next->next; |
| } |
| + spin_unlock(&dcache_lock); |
| + return NULL; |
| |
| - if (expired) { |
| - DPRINTK("returning %p %.*s", |
| - expired, (int)expired->d_name.len, expired->d_name.name); |
| - spin_lock(&dcache_lock); |
| - list_move(&expired->d_parent->d_subdirs, &expired->d_u.d_child); |
| - spin_unlock(&dcache_lock); |
| - return expired; |
| - } |
| +found: |
| + DPRINTK("returning %p %.*s", |
| + expired, (int)expired->d_name.len, expired->d_name.name); |
| + ino = autofs4_dentry_ino(expired); |
| + ino->flags |= AUTOFS_INF_EXPIRING; |
| + init_completion(&ino->expire_complete); |
| + spin_unlock(&sbi->fs_lock); |
| + spin_lock(&dcache_lock); |
| + list_move(&expired->d_parent->d_subdirs, &expired->d_u.d_child); |
| spin_unlock(&dcache_lock); |
| + return expired; |
| +} |
| |
| - return NULL; |
| +int autofs4_expire_wait(struct dentry *dentry) |
| +{ |
| + struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); |
| + struct autofs_info *ino = autofs4_dentry_ino(dentry); |
| + int status; |
| + |
| + /* Block on any pending expire */ |
| + spin_lock(&sbi->fs_lock); |
| + if (ino->flags & AUTOFS_INF_EXPIRING) { |
| + spin_unlock(&sbi->fs_lock); |
| + |
| + DPRINTK("waiting for expire %p name=%.*s", |
| + dentry, dentry->d_name.len, dentry->d_name.name); |
| + |
| + status = autofs4_wait(sbi, dentry, NFY_NONE); |
| + wait_for_completion(&ino->expire_complete); |
| + |
| + DPRINTK("expire done status=%d", status); |
| + |
| + if (d_unhashed(dentry)) |
| + return -EAGAIN; |
| + |
| + return status; |
| + } |
| + spin_unlock(&sbi->fs_lock); |
| + |
| + return 0; |
| } |
| |
| /* Perform an expiry operation */ |
| @@ -392,7 +451,9 @@ int autofs4_expire_run(struct super_bloc |
| struct autofs_packet_expire __user *pkt_p) |
| { |
| struct autofs_packet_expire pkt; |
| + struct autofs_info *ino; |
| struct dentry *dentry; |
| + int ret = 0; |
| |
| memset(&pkt,0,sizeof pkt); |
| |
| @@ -408,39 +469,59 @@ int autofs4_expire_run(struct super_bloc |
| dput(dentry); |
| |
| if ( copy_to_user(pkt_p, &pkt, sizeof(struct autofs_packet_expire)) ) |
| - return -EFAULT; |
| + ret = -EFAULT; |
| |
| - return 0; |
| + spin_lock(&sbi->fs_lock); |
| + ino = autofs4_dentry_ino(dentry); |
| + ino->flags &= ~AUTOFS_INF_EXPIRING; |
| + complete_all(&ino->expire_complete); |
| + spin_unlock(&sbi->fs_lock); |
| + |
| + return ret; |
| } |
| |
| -/* 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) |
| +int autofs4_do_expire_multi(struct super_block *sb, struct vfsmount *mnt, |
| + struct autofs_sb_info *sbi, int when) |
| { |
| struct dentry *dentry; |
| int ret = -EAGAIN; |
| - int do_now = 0; |
| |
| - if (arg && get_user(do_now, arg)) |
| - return -EFAULT; |
| - |
| - if (sbi->type & AUTOFS_TYPE_DIRECT) |
| - dentry = autofs4_expire_direct(sb, mnt, sbi, do_now); |
| + if (autofs_type_trigger(sbi->type)) |
| + dentry = autofs4_expire_direct(sb, mnt, sbi, when); |
| else |
| - dentry = autofs4_expire_indirect(sb, mnt, sbi, do_now); |
| + dentry = autofs4_expire_indirect(sb, mnt, sbi, when); |
| |
| if (dentry) { |
| struct autofs_info *ino = autofs4_dentry_ino(dentry); |
| |
| /* This is synchronous because it makes the daemon a |
| little easier */ |
| - ino->flags |= AUTOFS_INF_EXPIRING; |
| ret = autofs4_wait(sbi, dentry, NFY_EXPIRE); |
| + |
| + spin_lock(&sbi->fs_lock); |
| + if (ino->flags & AUTOFS_INF_MOUNTPOINT) { |
| + sb->s_root->d_mounted++; |
| + ino->flags &= ~AUTOFS_INF_MOUNTPOINT; |
| + } |
| ino->flags &= ~AUTOFS_INF_EXPIRING; |
| + complete_all(&ino->expire_complete); |
| + spin_unlock(&sbi->fs_lock); |
| dput(dentry); |
| } |
| |
| return ret; |
| } |
| |
| +/* 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) |
| +{ |
| + int do_now = 0; |
| + |
| + if (arg && get_user(do_now, arg)) |
| + return -EFAULT; |
| + |
| + return autofs4_do_expire_multi(sb, mnt, sbi, do_now); |
| +} |
| + |
| |
| |
| @@ -14,6 +14,7 @@ |
| /* Internal header file for autofs */ |
| |
| #include <linux/auto_fs4.h> |
| +#include <linux/auto_dev-ioctl.h> |
| #include <linux/mutex.h> |
| #include <linux/list.h> |
| |
| @@ -21,6 +22,9 @@ |
| #define AUTOFS_IOC_FIRST AUTOFS_IOC_READY |
| #define AUTOFS_IOC_COUNT 32 |
| |
| +#define AUTOFS_DEV_IOCTL_IOC_FIRST (AUTOFS_DEV_IOCTL_VERSION) |
| +#define AUTOFS_DEV_IOCTL_IOC_COUNT (AUTOFS_IOC_COUNT - 11) |
| + |
| #include <linux/kernel.h> |
| #include <linux/slab.h> |
| #include <linux/time.h> |
| @@ -35,11 +39,27 @@ |
| /* #define DEBUG */ |
| |
| #ifdef DEBUG |
| -#define DPRINTK(fmt,args...) do { printk(KERN_DEBUG "pid %d: %s: " fmt "\n" , current->pid , __FUNCTION__ , ##args); } while(0) |
| +#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) |
| +#define DPRINTK(fmt, args...) do {} while (0) |
| #endif |
| |
| +#define AUTOFS_WARN(fmt, args...) \ |
| +do { \ |
| + printk(KERN_WARNING "pid %d: %s: " fmt "\n", \ |
| + current->pid, __FUNCTION__, ##args); \ |
| +} while (0) |
| + |
| +#define AUTOFS_ERROR(fmt, args...) \ |
| +do { \ |
| + printk(KERN_ERR "pid %d: %s: " fmt "\n", \ |
| + current->pid, __FUNCTION__, ##args); \ |
| +} while (0) |
| + |
| /* 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 |
| @@ -52,12 +72,18 @@ struct autofs_info { |
| |
| int flags; |
| |
| - struct list_head rehash; |
| + struct completion expire_complete; |
| + |
| + struct list_head active; |
| + struct list_head expiring; |
| |
| struct autofs_sb_info *sbi; |
| unsigned long last_used; |
| atomic_t count; |
| |
| + uid_t uid; |
| + gid_t gid; |
| + |
| mode_t mode; |
| size_t size; |
| |
| @@ -68,15 +94,14 @@ struct autofs_info { |
| }; |
| |
| #define AUTOFS_INF_EXPIRING (1<<0) /* dentry is in the process of expiring */ |
| +#define AUTOFS_INF_MOUNTPOINT (1<<1) /* mountpoint status for direct expire */ |
| |
| 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 */ |
| - unsigned int hash; |
| - unsigned int len; |
| - char *name; |
| + struct qstr name; |
| u32 dev; |
| u64 ino; |
| uid_t uid; |
| @@ -85,15 +110,11 @@ struct autofs_wait_queue { |
| pid_t tgid; |
| /* This is for status reporting upon return */ |
| int status; |
| - atomic_t wait_ctr; |
| + unsigned int wait_ctr; |
| }; |
| |
| #define AUTOFS_SBI_MAGIC 0x6d4a556d |
| |
| -#define AUTOFS_TYPE_INDIRECT 0x0001 |
| -#define AUTOFS_TYPE_DIRECT 0x0002 |
| -#define AUTOFS_TYPE_OFFSET 0x0004 |
| - |
| struct autofs_sb_info { |
| u32 magic; |
| int pipefd; |
| @@ -112,8 +133,9 @@ struct autofs_sb_info { |
| struct mutex wq_mutex; |
| spinlock_t fs_lock; |
| struct autofs_wait_queue *queues; /* Wait queue pointer */ |
| - spinlock_t rehash_lock; |
| - struct list_head rehash_list; |
| + spinlock_t lookup_lock; |
| + struct list_head active_list; |
| + struct list_head expiring_list; |
| }; |
| |
| static inline struct autofs_sb_info *autofs4_sbi(struct super_block *sb) |
| @@ -138,18 +160,14 @@ static inline int autofs4_oz_mode(struct |
| static inline int autofs4_ispending(struct dentry *dentry) |
| { |
| struct autofs_info *inf = autofs4_dentry_ino(dentry); |
| - int pending = 0; |
| |
| if (dentry->d_flags & DCACHE_AUTOFS_PENDING) |
| return 1; |
| |
| - if (inf) { |
| - spin_lock(&inf->sbi->fs_lock); |
| - pending = inf->flags & AUTOFS_INF_EXPIRING; |
| - spin_unlock(&inf->sbi->fs_lock); |
| - } |
| + if (inf->flags & AUTOFS_INF_EXPIRING) |
| + return 1; |
| |
| - return pending; |
| + return 0; |
| } |
| |
| static inline void autofs4_copy_atime(struct file *src, struct file *dst) |
| @@ -164,11 +182,25 @@ void autofs4_free_ino(struct autofs_info |
| |
| /* Expiration */ |
| int is_autofs4_dentry(struct dentry *); |
| +int autofs4_expire_wait(struct dentry *dentry); |
| int autofs4_expire_run(struct super_block *, struct vfsmount *, |
| struct autofs_sb_info *, |
| struct autofs_packet_expire __user *); |
| +int autofs4_do_expire_multi(struct super_block *sb, struct vfsmount *mnt, |
| + struct autofs_sb_info *sbi, int when); |
| int autofs4_expire_multi(struct super_block *, struct vfsmount *, |
| struct autofs_sb_info *, int __user *); |
| +struct dentry *autofs4_expire_direct(struct super_block *sb, |
| + struct vfsmount *mnt, |
| + struct autofs_sb_info *sbi, int how); |
| +struct dentry *autofs4_expire_indirect(struct super_block *sb, |
| + struct vfsmount *mnt, |
| + struct autofs_sb_info *sbi, int how); |
| + |
| +/* Device node initialization */ |
| + |
| +int autofs_dev_ioctl_init(void); |
| +void autofs_dev_ioctl_exit(void); |
| |
| /* Operations structures */ |
| |
| |
| |
| @@ -24,8 +24,10 @@ |
| |
| static void ino_lnkfree(struct autofs_info *ino) |
| { |
| - kfree(ino->u.symlink); |
| - ino->u.symlink = NULL; |
| + if (ino->u.symlink) { |
| + kfree(ino->u.symlink); |
| + ino->u.symlink = NULL; |
| + } |
| } |
| |
| struct autofs_info *autofs4_init_ino(struct autofs_info *ino, |
| @@ -41,16 +43,20 @@ struct autofs_info *autofs4_init_ino(str |
| if (ino == NULL) |
| return NULL; |
| |
| - ino->flags = 0; |
| - ino->mode = mode; |
| - ino->inode = NULL; |
| - ino->dentry = NULL; |
| - ino->size = 0; |
| - |
| - INIT_LIST_HEAD(&ino->rehash); |
| + if (!reinit) { |
| + ino->flags = 0; |
| + ino->inode = NULL; |
| + ino->dentry = NULL; |
| + ino->size = 0; |
| + INIT_LIST_HEAD(&ino->active); |
| + INIT_LIST_HEAD(&ino->expiring); |
| + atomic_set(&ino->count, 0); |
| + } |
| |
| + ino->uid = 0; |
| + ino->gid = 0; |
| + ino->mode = mode; |
| ino->last_used = jiffies; |
| - atomic_set(&ino->count, 0); |
| |
| ino->sbi = sbi; |
| |
| @@ -159,8 +165,8 @@ void autofs4_kill_sb(struct super_block |
| if (!sbi) |
| goto out_kill_sb; |
| |
| - if (!sbi->catatonic) |
| - autofs4_catatonic_mode(sbi); /* Free wait queues, close pipe */ |
| + /* Free wait queues, close pipe */ |
| + autofs4_catatonic_mode(sbi); |
| |
| /* Clean up and release dangling references */ |
| autofs4_force_release(sbi); |
| @@ -186,9 +192,9 @@ static int autofs4_show_options(struct s |
| seq_printf(m, ",minproto=%d", sbi->min_proto); |
| seq_printf(m, ",maxproto=%d", sbi->max_proto); |
| |
| - if (sbi->type & AUTOFS_TYPE_OFFSET) |
| + if (autofs_type_offset(sbi->type)) |
| seq_printf(m, ",offset"); |
| - else if (sbi->type & AUTOFS_TYPE_DIRECT) |
| + else if (autofs_type_direct(sbi->type)) |
| seq_printf(m, ",direct"); |
| else |
| seq_printf(m, ",indirect"); |
| @@ -273,13 +279,13 @@ static int parse_options(char *options, |
| *maxproto = option; |
| break; |
| case Opt_indirect: |
| - *type = AUTOFS_TYPE_INDIRECT; |
| + set_autofs_type_indirect(type); |
| break; |
| case Opt_direct: |
| - *type = AUTOFS_TYPE_DIRECT; |
| + set_autofs_type_direct(type); |
| break; |
| case Opt_offset: |
| - *type = AUTOFS_TYPE_DIRECT | AUTOFS_TYPE_OFFSET; |
| + set_autofs_type_offset(type); |
| break; |
| default: |
| return 1; |
| @@ -329,14 +335,15 @@ int autofs4_fill_super(struct super_bloc |
| sbi->sb = s; |
| sbi->version = 0; |
| sbi->sub_version = 0; |
| - sbi->type = 0; |
| + set_autofs_type_indirect(&sbi->type); |
| sbi->min_proto = 0; |
| sbi->max_proto = 0; |
| mutex_init(&sbi->wq_mutex); |
| spin_lock_init(&sbi->fs_lock); |
| sbi->queues = NULL; |
| - spin_lock_init(&sbi->rehash_lock); |
| - INIT_LIST_HEAD(&sbi->rehash_list); |
| + spin_lock_init(&sbi->lookup_lock); |
| + INIT_LIST_HEAD(&sbi->active_list); |
| + INIT_LIST_HEAD(&sbi->expiring_list); |
| s->s_blocksize = 1024; |
| s->s_blocksize_bits = 10; |
| s->s_magic = AUTOFS_SUPER_MAGIC; |
| @@ -370,7 +377,7 @@ int autofs4_fill_super(struct super_bloc |
| } |
| |
| root_inode->i_fop = &autofs4_root_operations; |
| - root_inode->i_op = sbi->type & AUTOFS_TYPE_DIRECT ? |
| + root_inode->i_op = autofs_type_trigger(sbi->type) ? |
| &autofs4_direct_root_inode_operations : |
| &autofs4_indirect_root_inode_operations; |
| |
| |
| |
| @@ -2998,8 +2998,6 @@ COMPATIBLE_IOCTL(AUTOFS_IOC_PROTOVER) |
| COMPATIBLE_IOCTL(AUTOFS_IOC_EXPIRE) |
| COMPATIBLE_IOCTL(AUTOFS_IOC_EXPIRE_MULTI) |
| COMPATIBLE_IOCTL(AUTOFS_IOC_PROTOSUBVER) |
| -COMPATIBLE_IOCTL(AUTOFS_IOC_ASKREGHOST) |
| -COMPATIBLE_IOCTL(AUTOFS_IOC_TOGGLEREGHOST) |
| COMPATIBLE_IOCTL(AUTOFS_IOC_ASKUMOUNT) |
| /* Raw devices */ |
| COMPATIBLE_IOCTL(RAW_SETBIND) |
| |
| |
| @@ -23,12 +23,71 @@ |
| #define AUTOFS_MIN_PROTO_VERSION 3 |
| #define AUTOFS_MAX_PROTO_VERSION 5 |
| |
| -#define AUTOFS_PROTO_SUBVERSION 0 |
| +#define AUTOFS_PROTO_SUBVERSION 1 |
| |
| /* Mask for expire behaviour */ |
| #define AUTOFS_EXP_IMMEDIATE 1 |
| #define AUTOFS_EXP_LEAVES 2 |
| |
| +#define AUTOFS_TYPE_ANY 0U |
| +#define AUTOFS_TYPE_INDIRECT 1U |
| +#define AUTOFS_TYPE_DIRECT 2U |
| +#define AUTOFS_TYPE_OFFSET 4U |
| + |
| +static inline void set_autofs_type_indirect(unsigned int *type) |
| +{ |
| + *type = AUTOFS_TYPE_INDIRECT; |
| + return; |
| +} |
| + |
| +static inline unsigned int autofs_type_indirect(unsigned int type) |
| +{ |
| + return (type == AUTOFS_TYPE_INDIRECT); |
| +} |
| + |
| +static inline void set_autofs_type_direct(unsigned int *type) |
| +{ |
| + *type = AUTOFS_TYPE_DIRECT; |
| + return; |
| +} |
| + |
| +static inline unsigned int autofs_type_direct(unsigned int type) |
| +{ |
| + return (type == AUTOFS_TYPE_DIRECT); |
| +} |
| + |
| +static inline void set_autofs_type_offset(unsigned int *type) |
| +{ |
| + *type = AUTOFS_TYPE_OFFSET; |
| + return; |
| +} |
| + |
| +static inline unsigned int autofs_type_offset(unsigned int type) |
| +{ |
| + return (type == AUTOFS_TYPE_OFFSET); |
| +} |
| + |
| +static inline unsigned int autofs_type_trigger(unsigned int type) |
| +{ |
| + return (type == AUTOFS_TYPE_DIRECT || type == AUTOFS_TYPE_OFFSET); |
| +} |
| + |
| +/* |
| + * This isn't really a type as we use it to say "no type set" to |
| + * indicate we want to search for "any" mount in the |
| + * autofs_dev_ioctl_ismountpoint() device ioctl function. |
| + */ |
| +static inline void set_autofs_type_any(unsigned int *type) |
| +{ |
| + *type = AUTOFS_TYPE_ANY; |
| + return; |
| +} |
| + |
| +static inline unsigned int autofs_type_any(unsigned int type) |
| +{ |
| + return (type == AUTOFS_TYPE_ANY); |
| +} |
| + |
| /* Daemon notification packet types */ |
| enum autofs_notify { |
| NFY_NONE, |
| @@ -98,8 +157,6 @@ union autofs_v5_packet_union { |
| #define AUTOFS_IOC_EXPIRE_INDIRECT AUTOFS_IOC_EXPIRE_MULTI |
| #define AUTOFS_IOC_EXPIRE_DIRECT AUTOFS_IOC_EXPIRE_MULTI |
| #define AUTOFS_IOC_PROTOSUBVER _IOR(0x93,0x67,int) |
| -#define AUTOFS_IOC_ASKREGHOST _IOR(0x93,0x68,int) |
| -#define AUTOFS_IOC_TOGGLEREGHOST _IOR(0x93,0x69,int) |
| #define AUTOFS_IOC_ASKUMOUNT _IOR(0x93,0x70,int) |
| |
| |
| |
| |
| @@ -0,0 +1,414 @@ |
| + |
| +Miscellaneous Device control operations for the autofs4 kernel module |
| +==================================================================== |
| + |
| +The problem |
| +=========== |
| + |
| +There is a problem with active restarts in autofs (that is to say |
| +restarting autofs when there are busy mounts). |
| + |
| +During normal operation autofs uses a file descriptor opened on the |
| +directory that is being managed in order to be able to issue control |
| +operations. Using a file descriptor gives ioctl operations access to |
| +autofs specific information stored in the super block. The operations |
| +are things such as setting an autofs mount catatonic, setting the |
| +expire timeout and requesting expire checks. As is explained below, |
| +certain types of autofs triggered mounts can end up covering an autofs |
| +mount itself which prevents us being able to use open(2) to obtain a |
| +file descriptor for these operations if we don't already have one open. |
| + |
| +Currently autofs uses "umount -l" (lazy umount) to clear active mounts |
| +at restart. While using lazy umount works for most cases, anything that |
| +needs to walk back up the mount tree to construct a path, such as |
| +getcwd(2) and the proc file system /proc/<pid>/cwd, no longer works |
| +because the point from which the path is constructed has been detached |
| +from the mount tree. |
| + |
| +The actual problem with autofs is that it can't reconnect to existing |
| +mounts. Immediately one thinks of just adding the ability to remount |
| +autofs file systems would solve it, but alas, that can't work. This is |
| +because autofs direct mounts and the implementation of "on demand mount |
| +and expire" of nested mount trees have the file system mounted directly |
| +on top of the mount trigger directory dentry. |
| + |
| +For example, there are two types of automount maps, direct (in the kernel |
| +module source you will see a third type called an offset, which is just |
| +a direct mount in disguise) and indirect. |
| + |
| +Here is a master map with direct and indirect map entries: |
| + |
| +/- /etc/auto.direct |
| +/test /etc/auto.indirect |
| + |
| +and the corresponding map files: |
| + |
| +/etc/auto.direct: |
| + |
| +/automount/dparse/g6 budgie:/autofs/export1 |
| +/automount/dparse/g1 shark:/autofs/export1 |
| +and so on. |
| + |
| +/etc/auto.indirect: |
| + |
| +g1 shark:/autofs/export1 |
| +g6 budgie:/autofs/export1 |
| +and so on. |
| + |
| +For the above indirect map an autofs file system is mounted on /test and |
| +mounts are triggered for each sub-directory key by the inode lookup |
| +operation. So we see a mount of shark:/autofs/export1 on /test/g1, for |
| +example. |
| + |
| +The way that direct mounts are handled is by making an autofs mount on |
| +each full path, such as /automount/dparse/g1, and using it as a mount |
| +trigger. So when we walk on the path we mount shark:/autofs/export1 "on |
| +top of this mount point". Since these are always directories we can |
| +use the follow_link inode operation to trigger the mount. |
| + |
| +But, each entry in direct and indirect maps can have offsets (making |
| +them multi-mount map entries). |
| + |
| +For example, an indirect mount map entry could also be: |
| + |
| +g1 \ |
| + / shark:/autofs/export5/testing/test \ |
| + /s1 shark:/autofs/export/testing/test/s1 \ |
| + /s2 shark:/autofs/export5/testing/test/s2 \ |
| + /s1/ss1 shark:/autofs/export1 \ |
| + /s2/ss2 shark:/autofs/export2 |
| + |
| +and a similarly a direct mount map entry could also be: |
| + |
| +/automount/dparse/g1 \ |
| + / shark:/autofs/export5/testing/test \ |
| + /s1 shark:/autofs/export/testing/test/s1 \ |
| + /s2 shark:/autofs/export5/testing/test/s2 \ |
| + /s1/ss1 shark:/autofs/export2 \ |
| + /s2/ss2 shark:/autofs/export2 |
| + |
| +One of the issues with version 4 of autofs was that, when mounting an |
| +entry with a large number of offsets, possibly with nesting, we needed |
| +to mount and umount all of the offsets as a single unit. Not really a |
| +problem, except for people with a large number of offsets in map entries. |
| +This mechanism is used for the well known "hosts" map and we have seen |
| +cases (in 2.4) where the available number of mounts are exhausted or |
| +where the number of privileged ports available is exhausted. |
| + |
| +In version 5 we mount only as we go down the tree of offsets and |
| +similarly for expiring them which resolves the above problem. There is |
| +somewhat more detail to the implementation but it isn't needed for the |
| +sake of the problem explanation. The one important detail is that these |
| +offsets are implemented using the same mechanism as the direct mounts |
| +above and so the mount points can be covered by a mount. |
| + |
| +The current autofs implementation uses an ioctl file descriptor opened |
| +on the mount point for control operations. The references held by the |
| +descriptor are accounted for in checks made to determine if a mount is |
| +in use and is also used to access autofs file system information held |
| +in the mount super block. So the use of a file handle needs to be |
| +retained. |
| + |
| + |
| +The Solution |
| +============ |
| + |
| +To be able to restart autofs leaving existing direct, indirect and |
| +offset mounts in place we need to be able to obtain a file handle |
| +for these potentially covered autofs mount points. Rather than just |
| +implement an isolated operation it was decided to re-implement the |
| +existing ioctl interface and add new operations to provide this |
| +functionality. |
| + |
| +In addition, to be able to reconstruct a mount tree that has busy mounts, |
| +the uid and gid of the last user that triggered the mount needs to be |
| +available because these can be used as macro substitution variables in |
| +autofs maps. They are recorded at mount request time and an operation |
| +has been added to retrieve them. |
| + |
| +Since we're re-implementing the control interface, a couple of other |
| +problems with the existing interface have been addressed. First, when |
| +a mount or expire operation completes a status is returned to the |
| +kernel by either a "send ready" or a "send fail" operation. The |
| +"send fail" operation of the ioctl interface could only ever send |
| +ENOENT so the re-implementation allows user space to send an actual |
| +status. Another expensive operation in user space, for those using |
| +very large maps, is discovering if a mount is present. Usually this |
| +involves scanning /proc/mounts and since it needs to be done quite |
| +often it can introduce significant overhead when there are many entries |
| +in the mount table. An operation to lookup the mount status of a mount |
| +point dentry (covered or not) has also been added. |
| + |
| +Current kernel development policy recommends avoiding the use of the |
| +ioctl mechanism in favor of systems such as Netlink. An implementation |
| +using this system was attempted to evaluate its suitability and it was |
| +found to be inadequate, in this case. The Generic Netlink system was |
| +used for this as raw Netlink would lead to a significant increase in |
| +complexity. There's no question that the Generic Netlink system is an |
| +elegant solution for common case ioctl functions but it's not a complete |
| +replacement probably because it's primary purpose in life is to be a |
| +message bus implementation rather than specifically an ioctl replacement. |
| +While it would be possible to work around this there is one concern |
| +that lead to the decision to not use it. This is that the autofs |
| +expire in the daemon has become far to complex because umount |
| +candidates are enumerated, almost for no other reason than to "count" |
| +the number of times to call the expire ioctl. This involves scanning |
| +the mount table which has proved to be a big overhead for users with |
| +large maps. The best way to improve this is try and get back to the |
| +way the expire was done long ago. That is, when an expire request is |
| +issued for a mount (file handle) we should continually call back to |
| +the daemon until we can't umount any more mounts, then return the |
| +appropriate status to the daemon. At the moment we just expire one |
| +mount at a time. A Generic Netlink implementation would exclude this |
| +possibility for future development due to the requirements of the |
| +message bus architecture. |
| + |
| + |
| +autofs4 Miscellaneous Device mount control interface |
| +==================================================== |
| + |
| +The control interface is opening a device node, typically /dev/autofs. |
| + |
| +All the ioctls use a common structure to pass the needed parameter |
| +information and return operation results: |
| + |
| +struct autofs_dev_ioctl { |
| + __u32 ver_major; |
| + __u32 ver_minor; |
| + __u32 size; /* total size of data passed in |
| + * including this struct */ |
| + __s32 ioctlfd; /* automount command fd */ |
| + |
| + /* Command parameters */ |
| + |
| + union { |
| + struct args_protover protover; |
| + struct args_protosubver protosubver; |
| + struct args_openmount openmount; |
| + struct args_ready ready; |
| + struct args_fail fail; |
| + struct args_setpipefd setpipefd; |
| + struct args_timeout timeout; |
| + struct args_requester requester; |
| + struct args_expire expire; |
| + struct args_askumount askumount; |
| + struct args_ismountpoint ismountpoint; |
| + }; |
| + |
| + char path[0]; |
| +}; |
| + |
| +The ioctlfd field is a mount point file descriptor of an autofs mount |
| +point. It is returned by the open call and is used by all calls except |
| +the check for whether a given path is a mount point, where it may |
| +optionally be used to check a specific mount corresponding to a given |
| +mount point file descriptor, and when requesting the uid and gid of the |
| +last successful mount on a directory within the autofs file system. |
| + |
| +The anonymous union is used to communicate parameters and results of calls |
| +made as described below. |
| + |
| +The path field is used to pass a path where it is needed and the size field |
| +is used account for the increased structure length when translating the |
| +structure sent from user space. |
| + |
| +This structure can be initialized before setting specific fields by using |
| +the void function call init_autofs_dev_ioctl(struct autofs_dev_ioctl *). |
| + |
| +All of the ioctls perform a copy of this structure from user space to |
| +kernel space and return -EINVAL if the size parameter is smaller than |
| +the structure size itself, -ENOMEM if the kernel memory allocation fails |
| +or -EFAULT if the copy itself fails. Other checks include a version check |
| +of the compiled in user space version against the module version and a |
| +mismatch results in a -EINVAL return. If the size field is greater than |
| +the structure size then a path is assumed to be present and is checked to |
| +ensure it begins with a "/" and is NULL terminated, otherwise -EINVAL is |
| +returned. Following these checks, for all ioctl commands except |
| +AUTOFS_DEV_IOCTL_VERSION_CMD, AUTOFS_DEV_IOCTL_OPENMOUNT_CMD and |
| +AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD the ioctlfd is validated and if it is |
| +not a valid descriptor or doesn't correspond to an autofs mount point |
| +an error of -EBADF, -ENOTTY or -EINVAL (not an autofs descriptor) is |
| +returned. |
| + |
| + |
| +The ioctls |
| +========== |
| + |
| +An example of an implementation which uses this interface can be seen |
| +in autofs version 5.0.4 and later in file lib/dev-ioctl-lib.c of the |
| +distribution tar available for download from kernel.org in directory |
| +/pub/linux/daemons/autofs/v5. |
| + |
| +The device node ioctl operations implemented by this interface are: |
| + |
| + |
| +AUTOFS_DEV_IOCTL_VERSION |
| +------------------------ |
| + |
| +Get the major and minor version of the autofs4 device ioctl kernel module |
| +implementation. It requires an initialized struct autofs_dev_ioctl as an |
| +input parameter and sets the version information in the passed in structure. |
| +It returns 0 on success or the error -EINVAL if a version mismatch is |
| +detected. |
| + |
| + |
| +AUTOFS_DEV_IOCTL_PROTOVER_CMD and AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD |
| +------------------------------------------------------------------ |
| + |
| +Get the major and minor version of the autofs4 protocol version understood |
| +by loaded module. This call requires an initialized struct autofs_dev_ioctl |
| +with the ioctlfd field set to a valid autofs mount point descriptor |
| +and sets the requested version number in structure field protover.version |
| +and ptotosubver.sub_version respectively. These commands return 0 on |
| +success or one of the negative error codes if validation fails. |
| + |
| + |
| +AUTOFS_DEV_IOCTL_OPENMOUNT_CMD and AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD |
| +------------------------------------------------------------------ |
| + |
| +Obtain and release a file descriptor for an autofs managed mount point |
| +path. The open call requires an initialized struct autofs_dev_ioctl with |
| +the the path field set and the size field adjusted appropriately as well |
| +as the openmount.devid field set to the device number of the autofs mount. |
| +The device number of an autofs mounted filesystem can be obtained by using |
| +the AUTOFS_DEV_IOCTL_ISMOUNTPOINT ioctl function by providing the path |
| +and autofs mount type, as described below. The close call requires an |
| +initialized struct autofs_dev_ioct with the ioctlfd field set to the |
| +descriptor obtained from the open call. The release of the file descriptor |
| +can also be done with close(2) so any open descriptors will also be |
| +closed at process exit. The close call is included in the implemented |
| +operations largely for completeness and to provide for a consistent |
| +user space implementation. |
| + |
| + |
| +AUTOFS_DEV_IOCTL_READY_CMD and AUTOFS_DEV_IOCTL_FAIL_CMD |
| +-------------------------------------------------------- |
| + |
| +Return mount and expire result status from user space to the kernel. |
| +Both of these calls require an initialized struct autofs_dev_ioctl |
| +with the ioctlfd field set to the descriptor obtained from the open |
| +call and the ready.token or fail.token field set to the wait queue |
| +token number, received by user space in the foregoing mount or expire |
| +request. The fail.status field is set to the status to be returned when |
| +sending a failure notification with AUTOFS_DEV_IOCTL_FAIL_CMD. |
| + |
| + |
| +AUTOFS_DEV_IOCTL_SETPIPEFD_CMD |
| +------------------------------ |
| + |
| +Set the pipe file descriptor used for kernel communication to the daemon. |
| +Normally this is set at mount time using an option but when reconnecting |
| +to a existing mount we need to use this to tell the autofs mount about |
| +the new kernel pipe descriptor. In order to protect mounts against |
| +incorrectly setting the pipe descriptor we also require that the autofs |
| +mount be catatonic (see next call). |
| + |
| +The call requires an initialized struct autofs_dev_ioctl with the |
| +ioctlfd field set to the descriptor obtained from the open call and |
| +the setpipefd.pipefd field set to descriptor of the pipe. On success |
| +the call also sets the process group id used to identify the controlling |
| +process (eg. the owning automount(8) daemon) to the process group of |
| +the caller. |
| + |
| + |
| +AUTOFS_DEV_IOCTL_CATATONIC_CMD |
| +------------------------------ |
| + |
| +Make the autofs mount point catatonic. The autofs mount will no longer |
| +issue mount requests, the kernel communication pipe descriptor is released |
| +and any remaining waits in the queue released. |
| + |
| +The call requires an initialized struct autofs_dev_ioctl with the |
| +ioctlfd field set to the descriptor obtained from the open call. |
| + |
| + |
| +AUTOFS_DEV_IOCTL_TIMEOUT_CMD |
| +---------------------------- |
| + |
| +Set the expire timeout for mounts withing an autofs mount point. |
| + |
| +The call requires an initialized struct autofs_dev_ioctl with the |
| +ioctlfd field set to the descriptor obtained from the open call. |
| +The timeout.timeout field is set to the desired timeout and this |
| +field is set to the value of the value of the current timeout of |
| +the mount upon successful completion. |
| + |
| + |
| +AUTOFS_DEV_IOCTL_REQUESTER_CMD |
| +------------------------------ |
| + |
| +Return the uid and gid of the last process to successfully trigger a the |
| +mount on the given path dentry. |
| + |
| +The call requires an initialized struct autofs_dev_ioctl with the path |
| +field set to the mount point in question and the size field adjusted |
| +appropriately as well as the ioctlfd field set to the descriptor obtained |
| +from the open call. Upon return the struct fields requester.uid and |
| +requester.gid contain the uid and gid respectively. |
| + |
| +When reconstructing an autofs mount tree with active mounts we need to |
| +re-connect to mounts that may have used the original process uid and |
| +gid (or string variations of them) for mount lookups within the map entry. |
| +This call provides the ability to obtain this uid and gid so they may be |
| +used by user space for the mount map lookups. |
| + |
| + |
| +AUTOFS_DEV_IOCTL_EXPIRE_CMD |
| +--------------------------- |
| + |
| +Issue an expire request to the kernel for an autofs mount. Typically |
| +this ioctl is called until no further expire candidates are found. |
| + |
| +The call requires an initialized struct autofs_dev_ioctl with the |
| +ioctlfd field set to the descriptor obtained from the open call. In |
| +addition an immediate expire, independent of the mount timeout, can be |
| +requested by setting the expire.how field to 1. If no expire candidates |
| +can be found the ioctl returns -1 with errno set to EAGAIN. |
| + |
| +This call causes the kernel module to check the mount corresponding |
| +to the given ioctlfd for mounts that can be expired, issues an expire |
| +request back to the daemon and waits for completion. |
| + |
| +AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD |
| +------------------------------ |
| + |
| +Checks if an autofs mount point is in use. |
| + |
| +The call requires an initialized struct autofs_dev_ioctl with the |
| +ioctlfd field set to the descriptor obtained from the open call and |
| +it returns the result in the askumount.may_umount field, 1 for busy |
| +and 0 otherwise. |
| + |
| + |
| +AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD |
| +--------------------------------- |
| + |
| +Check if the given path is a mountpoint. |
| + |
| +The call requires an initialized struct autofs_dev_ioctl. There are two |
| +possible variations. Both use the path field set to the path of the mount |
| +point to check and the size field must be adjusted appropriately. One uses |
| +the ioctlfd field to identify a specific mount point to check while the |
| +other variation uses the path and optionaly the ismountpoint.in.type |
| +field set to an autofs mount type. The call returns 1 if this is a mount |
| +point and sets the ismountpoint.out.devid field to the device number of |
| +the mount and the ismountpoint.out.magic field to the relevant super |
| +block magic number (described below) or 0 if it isn't a mountpoint. In |
| +both cases the the device number (as returned by new_encode_dev()) is |
| +returned in the ismountpoint.out.devid field. |
| + |
| +If supplied with a file descriptor we're looking for a specific mount, |
| +not necessarily at the top of the mounted stack. In this case the path |
| +the descriptor corresponds to is considered a mountpoint if it is itself |
| +a mountpoint or contains a mount, such as a multi-mount without a root |
| +mount. In this case we return 1 if the descriptor corresponds to a mount |
| +point and and also returns the super magic of the covering mount if there |
| +is one or 0 if it isn't a mountpoint. |
| + |
| +If a path is supplied (and the ioctlfd field is set to -1) then the path |
| +is looked up and is checked to see if it is the root of a mount. If a |
| +type is also given we are looking for a particular autofs mount and if |
| +a match isn't found a fail is returned. If the the located path is the |
| +root of a mount 1 is returned along with the super magic of the mount |
| +or 0 otherwise. |
| + |
| |
| |
| @@ -4,4 +4,4 @@ |
| |
| obj-$(CONFIG_AUTOFS4_FS) += autofs4.o |
| |
| -autofs4-objs := init.o inode.o root.o symlink.o waitq.o expire.o |
| +autofs4-objs := init.o inode.o root.o symlink.o waitq.o expire.o dev-ioctl.o |
| |
| |
| @@ -0,0 +1,840 @@ |
| +/* |
| + * Copyright 2008 Red Hat, Inc. All rights reserved. |
| + * Copyright 2008 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/module.h> |
| +#include <linux/vmalloc.h> |
| +#include <linux/miscdevice.h> |
| +#include <linux/init.h> |
| +#include <linux/wait.h> |
| +#include <linux/namei.h> |
| +#include <linux/fcntl.h> |
| +#include <linux/file.h> |
| +#include <linux/sched.h> |
| +#include <linux/compat.h> |
| +#include <linux/syscalls.h> |
| +#include <linux/smp_lock.h> |
| +#include <linux/magic.h> |
| +#include <linux/dcache.h> |
| +#include <linux/uaccess.h> |
| + |
| +#include "autofs_i.h" |
| + |
| +/* |
| + * This module implements an interface for routing autofs ioctl control |
| + * commands via a miscellaneous device file. |
| + * |
| + * The alternate interface is needed because we need to be able open |
| + * an ioctl file descriptor on an autofs mount that may be covered by |
| + * another mount. This situation arises when starting automount(8) |
| + * or other user space daemon which uses direct mounts or offset |
| + * mounts (used for autofs lazy mount/umount of nested mount trees), |
| + * which have been left busy at at service shutdown. |
| + */ |
| + |
| +#define AUTOFS_DEV_IOCTL_SIZE sizeof(struct autofs_dev_ioctl) |
| + |
| +typedef int (*ioctl_fn)(struct file *, |
| +struct autofs_sb_info *, struct autofs_dev_ioctl *); |
| + |
| +static int check_name(const char *name) |
| +{ |
| + if (!strchr(name, '/')) |
| + return -EINVAL; |
| + return 0; |
| +} |
| + |
| +/* |
| + * Check a string doesn't overrun the chunk of |
| + * memory we copied from user land. |
| + */ |
| +static int invalid_str(char *str, void *end) |
| +{ |
| + while ((void *) str <= end) |
| + if (!*str++) |
| + return 0; |
| + return -EINVAL; |
| +} |
| + |
| +/* |
| + * Check that the user compiled against correct version of autofs |
| + * misc device code. |
| + * |
| + * As well as checking the version compatibility this always copies |
| + * the kernel interface version out. |
| + */ |
| +static int check_dev_ioctl_version(int cmd, struct autofs_dev_ioctl *param) |
| +{ |
| + int err = 0; |
| + |
| + if ((AUTOFS_DEV_IOCTL_VERSION_MAJOR != param->ver_major) || |
| + (AUTOFS_DEV_IOCTL_VERSION_MINOR < param->ver_minor)) { |
| + AUTOFS_WARN("ioctl control interface version mismatch: " |
| + "kernel(%u.%u), user(%u.%u), cmd(%d)", |
| + AUTOFS_DEV_IOCTL_VERSION_MAJOR, |
| + AUTOFS_DEV_IOCTL_VERSION_MINOR, |
| + param->ver_major, param->ver_minor, cmd); |
| + err = -EINVAL; |
| + } |
| + |
| + /* Fill in the kernel version. */ |
| + param->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR; |
| + param->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR; |
| + |
| + return err; |
| +} |
| + |
| +/* |
| + * Copy parameter control struct, including a possible path allocated |
| + * at the end of the struct. |
| + */ |
| +static struct autofs_dev_ioctl *copy_dev_ioctl(struct autofs_dev_ioctl __user *in) |
| +{ |
| + struct autofs_dev_ioctl tmp, *ads; |
| + |
| + if (copy_from_user(&tmp, in, sizeof(tmp))) |
| + return ERR_PTR(-EFAULT); |
| + |
| + if (tmp.size < sizeof(tmp)) |
| + return ERR_PTR(-EINVAL); |
| + |
| + ads = kmalloc(tmp.size, GFP_KERNEL); |
| + if (!ads) |
| + return ERR_PTR(-ENOMEM); |
| + |
| + if (copy_from_user(ads, in, tmp.size)) { |
| + kfree(ads); |
| + return ERR_PTR(-EFAULT); |
| + } |
| + |
| + return ads; |
| +} |
| + |
| +static inline void free_dev_ioctl(struct autofs_dev_ioctl *param) |
| +{ |
| + kfree(param); |
| + return; |
| +} |
| + |
| +/* |
| + * Check sanity of parameter control fields and if a path is present |
| + * check that it is terminated and contains at least one "/". |
| + */ |
| +static int validate_dev_ioctl(int cmd, struct autofs_dev_ioctl *param) |
| +{ |
| + int err; |
| + |
| + if ((err = check_dev_ioctl_version(cmd, param))) { |
| + AUTOFS_WARN("invalid device control module version " |
| + "supplied for cmd(0x%08x)", cmd); |
| + goto out; |
| + } |
| + |
| + if (param->size > sizeof(*param)) { |
| + err = invalid_str(param->path, |
| + (void *) ((size_t) param + param->size)); |
| + if (err) { |
| + AUTOFS_WARN( |
| + "path string terminator missing for cmd(0x%08x)", |
| + cmd); |
| + goto out; |
| + } |
| + |
| + err = check_name(param->path); |
| + if (err) { |
| + AUTOFS_WARN("invalid path supplied for cmd(0x%08x)", |
| + cmd); |
| + goto out; |
| + } |
| + } |
| + |
| + err = 0; |
| +out: |
| + return err; |
| +} |
| + |
| +/* |
| + * Get the autofs super block info struct from the file opened on |
| + * the autofs mount point. |
| + */ |
| +static struct autofs_sb_info *autofs_dev_ioctl_sbi(struct file *f) |
| +{ |
| + struct autofs_sb_info *sbi = NULL; |
| + struct inode *inode; |
| + |
| + if (f) { |
| + inode = f->f_path.dentry->d_inode; |
| + sbi = autofs4_sbi(inode->i_sb); |
| + } |
| + return sbi; |
| +} |
| + |
| +/* Return autofs module protocol version */ |
| +static int autofs_dev_ioctl_protover(struct file *fp, |
| + struct autofs_sb_info *sbi, |
| + struct autofs_dev_ioctl *param) |
| +{ |
| + param->protover.version = sbi->version; |
| + return 0; |
| +} |
| + |
| +/* Return autofs module protocol sub version */ |
| +static int autofs_dev_ioctl_protosubver(struct file *fp, |
| + struct autofs_sb_info *sbi, |
| + struct autofs_dev_ioctl *param) |
| +{ |
| + param->protosubver.sub_version = sbi->sub_version; |
| + return 0; |
| +} |
| + |
| +/* |
| + * Walk down the mount stack looking for an autofs mount that |
| + * has the requested device number (aka. new_encode_dev(sb->s_dev). |
| + */ |
| +static int autofs_dev_ioctl_find_super(struct nameidata *nd, dev_t devno) |
| +{ |
| + struct dentry *dentry; |
| + struct inode *inode; |
| + struct super_block *sb; |
| + dev_t s_dev; |
| + unsigned int err; |
| + |
| + err = -ENOENT; |
| + |
| + /* Lookup the dentry name at the base of our mount point */ |
| + dentry = d_lookup(nd->dentry, &nd->last); |
| + if (!dentry) |
| + goto out; |
| + |
| + dput(nd->dentry); |
| + nd->dentry = dentry; |
| + |
| + /* And follow the mount stack looking for our autofs mount */ |
| + while (follow_down(&nd->mnt, &nd->dentry)) { |
| + inode = nd->dentry->d_inode; |
| + if (!inode) |
| + break; |
| + |
| + sb = inode->i_sb; |
| + s_dev = new_encode_dev(sb->s_dev); |
| + if (devno == s_dev) { |
| + if (sb->s_magic == AUTOFS_SUPER_MAGIC) { |
| + err = 0; |
| + break; |
| + } |
| + } |
| + } |
| +out: |
| + return err; |
| +} |
| + |
| +/* |
| + * Walk down the mount stack looking for an autofs mount that |
| + * has the requested mount type (ie. indirect, direct or offset). |
| + */ |
| +static int autofs_dev_ioctl_find_sbi_type(struct nameidata *nd, unsigned int type) |
| +{ |
| + struct dentry *dentry; |
| + struct autofs_info *ino; |
| + unsigned int err; |
| + |
| + err = -ENOENT; |
| + |
| + /* Lookup the dentry name at the base of our mount point */ |
| + dentry = d_lookup(nd->dentry, &nd->last); |
| + if (!dentry) |
| + goto out; |
| + |
| + dput(nd->dentry); |
| + nd->dentry = dentry; |
| + |
| + /* And follow the mount stack looking for our autofs mount */ |
| + while (follow_down(&nd->mnt, &nd->dentry)) { |
| + ino = autofs4_dentry_ino(nd->dentry); |
| + if (ino && ino->sbi->type & type) { |
| + err = 0; |
| + break; |
| + } |
| + } |
| +out: |
| + return err; |
| +} |
| + |
| +static void autofs_dev_ioctl_fd_install(unsigned int fd, struct file *file) |
| +{ |
| + struct files_struct *files = current->files; |
| + struct fdtable *fdt; |
| + |
| + spin_lock(&files->file_lock); |
| + fdt = files_fdtable(files); |
| + BUG_ON(fdt->fd[fd] != NULL); |
| + rcu_assign_pointer(fdt->fd[fd], file); |
| + FD_SET(fd, fdt->close_on_exec); |
| + spin_unlock(&files->file_lock); |
| +} |
| + |
| + |
| +/* |
| + * Open a file descriptor on the autofs mount point corresponding |
| + * to the given path and device number (aka. new_encode_dev(sb->s_dev)). |
| + */ |
| +static int autofs_dev_ioctl_open_mountpoint(const char *path, dev_t devid) |
| +{ |
| + struct file *filp; |
| + struct nameidata nd; |
| + int err, fd; |
| + |
| + fd = get_unused_fd(); |
| + if (likely(fd >= 0)) { |
| + /* Get nameidata of the parent directory */ |
| + err = path_lookup(path, LOOKUP_PARENT, &nd); |
| + if (err) |
| + goto out; |
| + |
| + /* |
| + * Search down, within the parent, looking for an |
| + * autofs super block that has the device number |
| + * corresponding to the autofs fs we want to open. |
| + */ |
| + err = autofs_dev_ioctl_find_super(&nd, devid); |
| + if (err) { |
| + path_release(&nd); |
| + goto out; |
| + } |
| + |
| + filp = dentry_open(nd.dentry, nd.mnt, O_RDONLY); |
| + if (IS_ERR(filp)) { |
| + err = PTR_ERR(filp); |
| + goto out; |
| + } |
| + |
| + autofs_dev_ioctl_fd_install(fd, filp); |
| + } |
| + |
| + return fd; |
| + |
| +out: |
| + put_unused_fd(fd); |
| + return err; |
| +} |
| + |
| +/* Open a file descriptor on an autofs mount point */ |
| +static int autofs_dev_ioctl_openmount(struct file *fp, |
| + struct autofs_sb_info *sbi, |
| + struct autofs_dev_ioctl *param) |
| +{ |
| + const char *path; |
| + dev_t devid; |
| + int err, fd; |
| + |
| + /* param->path has already been checked */ |
| + if (!param->openmount.devid) |
| + return -EINVAL; |
| + |
| + param->ioctlfd = -1; |
| + |
| + path = param->path; |
| + devid = param->openmount.devid; |
| + |
| + err = 0; |
| + fd = autofs_dev_ioctl_open_mountpoint(path, devid); |
| + if (unlikely(fd < 0)) { |
| + err = fd; |
| + goto out; |
| + } |
| + |
| + param->ioctlfd = fd; |
| +out: |
| + return err; |
| +} |
| + |
| +/* Close file descriptor allocated above (user can also use close(2)). */ |
| +static int autofs_dev_ioctl_closemount(struct file *fp, |
| + struct autofs_sb_info *sbi, |
| + struct autofs_dev_ioctl *param) |
| +{ |
| + return sys_close(param->ioctlfd); |
| +} |
| + |
| +/* |
| + * Send "ready" status for an existing wait (either a mount or an expire |
| + * request). |
| + */ |
| +static int autofs_dev_ioctl_ready(struct file *fp, |
| + struct autofs_sb_info *sbi, |
| + struct autofs_dev_ioctl *param) |
| +{ |
| + autofs_wqt_t token; |
| + |
| + token = (autofs_wqt_t) param->ready.token; |
| + return autofs4_wait_release(sbi, token, 0); |
| +} |
| + |
| +/* |
| + * Send "fail" status for an existing wait (either a mount or an expire |
| + * request). |
| + */ |
| +static int autofs_dev_ioctl_fail(struct file *fp, |
| + struct autofs_sb_info *sbi, |
| + struct autofs_dev_ioctl *param) |
| +{ |
| + autofs_wqt_t token; |
| + int status; |
| + |
| + token = (autofs_wqt_t) param->fail.token; |
| + status = param->fail.status ? param->fail.status : -ENOENT; |
| + return autofs4_wait_release(sbi, token, status); |
| +} |
| + |
| +/* |
| + * Set the pipe fd for kernel communication to the daemon. |
| + * |
| + * Normally this is set at mount using an option but if we |
| + * are reconnecting to a busy mount then we need to use this |
| + * to tell the autofs mount about the new kernel pipe fd. In |
| + * order to protect mounts against incorrectly setting the |
| + * pipefd we also require that the autofs mount be catatonic. |
| + * |
| + * This also sets the process group id used to identify the |
| + * controlling process (eg. the owning automount(8) daemon). |
| + */ |
| +static int autofs_dev_ioctl_setpipefd(struct file *fp, |
| + struct autofs_sb_info *sbi, |
| + struct autofs_dev_ioctl *param) |
| +{ |
| + int pipefd; |
| + int err = 0; |
| + |
| + if (param->setpipefd.pipefd == -1) |
| + return -EINVAL; |
| + |
| + pipefd = param->setpipefd.pipefd; |
| + |
| + mutex_lock(&sbi->wq_mutex); |
| + if (!sbi->catatonic) { |
| + mutex_unlock(&sbi->wq_mutex); |
| + return -EBUSY; |
| + } else { |
| + struct file *pipe = fget(pipefd); |
| + if (!pipe->f_op || !pipe->f_op->write) { |
| + err = -EPIPE; |
| + fput(pipe); |
| + goto out; |
| + } |
| + sbi->oz_pgrp = task_pgrp_nr(current); |
| + sbi->pipefd = pipefd; |
| + sbi->pipe = pipe; |
| + sbi->catatonic = 0; |
| + } |
| +out: |
| + mutex_unlock(&sbi->wq_mutex); |
| + return err; |
| +} |
| + |
| +/* |
| + * Make the autofs mount point catatonic, no longer responsive to |
| + * mount requests. Also closes the kernel pipe file descriptor. |
| + */ |
| +static int autofs_dev_ioctl_catatonic(struct file *fp, |
| + struct autofs_sb_info *sbi, |
| + struct autofs_dev_ioctl *param) |
| +{ |
| + autofs4_catatonic_mode(sbi); |
| + return 0; |
| +} |
| + |
| +/* Set the autofs mount timeout */ |
| +static int autofs_dev_ioctl_timeout(struct file *fp, |
| + struct autofs_sb_info *sbi, |
| + struct autofs_dev_ioctl *param) |
| +{ |
| + unsigned long timeout; |
| + |
| + timeout = param->timeout.timeout; |
| + param->timeout.timeout = sbi->exp_timeout / HZ; |
| + sbi->exp_timeout = timeout * HZ; |
| + return 0; |
| +} |
| + |
| +/* |
| + * Return the uid and gid of the last request for the mount |
| + * |
| + * When reconstructing an autofs mount tree with active mounts |
| + * we need to re-connect to mounts that may have used the original |
| + * process uid and gid (or string variations of them) for mount |
| + * lookups within the map entry. |
| + */ |
| +static int autofs_dev_ioctl_requester(struct file *fp, |
| + struct autofs_sb_info *sbi, |
| + struct autofs_dev_ioctl *param) |
| +{ |
| + struct autofs_info *ino; |
| + struct nameidata nd; |
| + const char *path; |
| + dev_t devid; |
| + int err = -ENOENT; |
| + |
| + if (param->size <= sizeof(*param)) { |
| + err = -EINVAL; |
| + goto out; |
| + } |
| + |
| + path = param->path; |
| + devid = sbi->sb->s_dev; |
| + |
| + param->requester.uid = param->requester.gid = -1; |
| + |
| + /* Get nameidata of the parent directory */ |
| + err = path_lookup(path, LOOKUP_PARENT, &nd); |
| + if (err) |
| + goto out; |
| + |
| + err = autofs_dev_ioctl_find_super(&nd, devid); |
| + if (err) |
| + goto out_release; |
| + |
| + ino = autofs4_dentry_ino(nd.dentry); |
| + if (ino) { |
| + err = 0; |
| + autofs4_expire_wait(nd.dentry); |
| + spin_lock(&sbi->fs_lock); |
| + param->requester.uid = ino->uid; |
| + param->requester.gid = ino->gid; |
| + spin_unlock(&sbi->fs_lock); |
| + } |
| + |
| +out_release: |
| + path_release(&nd); |
| +out: |
| + return err; |
| +} |
| + |
| +/* |
| + * Call repeatedly until it returns -EAGAIN, meaning there's nothing |
| + * more that can be done. |
| + */ |
| +static int autofs_dev_ioctl_expire(struct file *fp, |
| + struct autofs_sb_info *sbi, |
| + struct autofs_dev_ioctl *param) |
| +{ |
| + struct vfsmount *mnt; |
| + int how; |
| + |
| + how = param->expire.how; |
| + mnt = fp->f_path.mnt; |
| + |
| + return autofs4_do_expire_multi(sbi->sb, mnt, sbi, how); |
| +} |
| + |
| +/* Check if autofs mount point is in use */ |
| +static int autofs_dev_ioctl_askumount(struct file *fp, |
| + struct autofs_sb_info *sbi, |
| + struct autofs_dev_ioctl *param) |
| +{ |
| + param->askumount.may_umount = 0; |
| + if (may_umount(fp->f_path.mnt)) |
| + param->askumount.may_umount = 1; |
| + return 0; |
| +} |
| + |
| +/* |
| + * Check if the given path is a mountpoint. |
| + * |
| + * If we are supplied with the file descriptor of an autofs |
| + * mount we're looking for a specific mount. In this case |
| + * the path is considered a mountpoint if it is itself a |
| + * mountpoint or contains a mount, such as a multi-mount |
| + * without a root mount. In this case we return 1 if the |
| + * path is a mount point and the super magic of the covering |
| + * mount if there is one or 0 if it isn't a mountpoint. |
| + * |
| + * If we aren't supplied with a file descriptor then we |
| + * lookup the nameidata of the path and check if it is the |
| + * root of a mount. If a type is given we are looking for |
| + * a particular autofs mount and if we don't find a match |
| + * we return fail. If the located nameidata path is the |
| + * root of a mount we return 1 along with the super magic |
| + * of the mount or 0 otherwise. |
| + * |
| + * In both cases the the device number (as returned by |
| + * new_encode_dev()) is also returned. |
| + */ |
| +static int autofs_dev_ioctl_ismountpoint(struct file *fp, |
| + struct autofs_sb_info *sbi, |
| + struct autofs_dev_ioctl *param) |
| +{ |
| + struct nameidata nd; |
| + const char *path; |
| + unsigned int type; |
| + unsigned int devid, magic; |
| + int err = -ENOENT; |
| + |
| + if (param->size <= sizeof(*param)) { |
| + err = -EINVAL; |
| + goto out; |
| + } |
| + |
| + path = param->path; |
| + type = param->ismountpoint.in.type; |
| + |
| + param->ismountpoint.out.devid = devid = 0; |
| + param->ismountpoint.out.magic = magic = 0; |
| + |
| + if (!fp || param->ioctlfd == -1) { |
| + if (autofs_type_any(type)) { |
| + struct super_block *sb; |
| + |
| + err = path_lookup(path, LOOKUP_FOLLOW, &nd); |
| + if (err) |
| + goto out; |
| + |
| + sb = nd.dentry->d_sb; |
| + devid = new_encode_dev(sb->s_dev); |
| + } else { |
| + struct autofs_info *ino; |
| + |
| + err = path_lookup(path, LOOKUP_PARENT, &nd); |
| + if (err) |
| + goto out; |
| + |
| + err = autofs_dev_ioctl_find_sbi_type(&nd, type); |
| + if (err) |
| + goto out_release; |
| + |
| + ino = autofs4_dentry_ino(nd.dentry); |
| + devid = autofs4_get_dev(ino->sbi); |
| + } |
| + |
| + err = 0; |
| + if (nd.dentry->d_inode && |
| + nd.mnt->mnt_root == nd.dentry) { |
| + err = 1; |
| + magic = nd.dentry->d_inode->i_sb->s_magic; |
| + } |
| + } else { |
| + dev_t dev = autofs4_get_dev(sbi); |
| + |
| + err = path_lookup(path, LOOKUP_PARENT, &nd); |
| + if (err) |
| + goto out; |
| + |
| + err = autofs_dev_ioctl_find_super(&nd, dev); |
| + if (err) |
| + goto out_release; |
| + |
| + devid = dev; |
| + |
| + err = have_submounts(nd.dentry); |
| + |
| + if (nd.mnt->mnt_mountpoint != nd.mnt->mnt_root) { |
| + if (follow_down(&nd.mnt, &nd.dentry)) { |
| + struct inode *inode = nd.dentry->d_inode; |
| + magic = inode->i_sb->s_magic; |
| + } |
| + } |
| + } |
| + |
| + param->ismountpoint.out.devid = devid; |
| + param->ismountpoint.out.magic = magic; |
| + |
| +out_release: |
| + path_release(&nd); |
| +out: |
| + return err; |
| +} |
| + |
| +/* |
| + * Our range of ioctl numbers isn't 0 based so we need to shift |
| + * the array index by _IOC_NR(AUTOFS_CTL_IOC_FIRST) for the table |
| + * lookup. |
| + */ |
| +#define cmd_idx(cmd) (cmd - _IOC_NR(AUTOFS_DEV_IOCTL_IOC_FIRST)) |
| + |
| +static ioctl_fn lookup_dev_ioctl(unsigned int cmd) |
| +{ |
| + static struct { |
| + int cmd; |
| + ioctl_fn fn; |
| + } _ioctls[] = { |
| + {cmd_idx(AUTOFS_DEV_IOCTL_VERSION_CMD), NULL}, |
| + {cmd_idx(AUTOFS_DEV_IOCTL_PROTOVER_CMD), |
| + autofs_dev_ioctl_protover}, |
| + {cmd_idx(AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD), |
| + autofs_dev_ioctl_protosubver}, |
| + {cmd_idx(AUTOFS_DEV_IOCTL_OPENMOUNT_CMD), |
| + autofs_dev_ioctl_openmount}, |
| + {cmd_idx(AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD), |
| + autofs_dev_ioctl_closemount}, |
| + {cmd_idx(AUTOFS_DEV_IOCTL_READY_CMD), |
| + autofs_dev_ioctl_ready}, |
| + {cmd_idx(AUTOFS_DEV_IOCTL_FAIL_CMD), |
| + autofs_dev_ioctl_fail}, |
| + {cmd_idx(AUTOFS_DEV_IOCTL_SETPIPEFD_CMD), |
| + autofs_dev_ioctl_setpipefd}, |
| + {cmd_idx(AUTOFS_DEV_IOCTL_CATATONIC_CMD), |
| + autofs_dev_ioctl_catatonic}, |
| + {cmd_idx(AUTOFS_DEV_IOCTL_TIMEOUT_CMD), |
| + autofs_dev_ioctl_timeout}, |
| + {cmd_idx(AUTOFS_DEV_IOCTL_REQUESTER_CMD), |
| + autofs_dev_ioctl_requester}, |
| + {cmd_idx(AUTOFS_DEV_IOCTL_EXPIRE_CMD), |
| + autofs_dev_ioctl_expire}, |
| + {cmd_idx(AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD), |
| + autofs_dev_ioctl_askumount}, |
| + {cmd_idx(AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD), |
| + autofs_dev_ioctl_ismountpoint} |
| + }; |
| + unsigned int idx = cmd_idx(cmd); |
| + |
| + return (idx >= ARRAY_SIZE(_ioctls)) ? NULL : _ioctls[idx].fn; |
| +} |
| + |
| +/* ioctl dispatcher */ |
| +static int _autofs_dev_ioctl(unsigned int command, struct autofs_dev_ioctl __user *user) |
| +{ |
| + struct autofs_dev_ioctl *param; |
| + struct file *fp; |
| + struct autofs_sb_info *sbi; |
| + unsigned int cmd_first, cmd; |
| + ioctl_fn fn = NULL; |
| + int err = 0; |
| + |
| + /* only root can play with this */ |
| + if (!capable(CAP_SYS_ADMIN)) |
| + return -EPERM; |
| + |
| + cmd_first = _IOC_NR(AUTOFS_DEV_IOCTL_IOC_FIRST); |
| + cmd = _IOC_NR(command); |
| + |
| + if (_IOC_TYPE(command) != _IOC_TYPE(AUTOFS_DEV_IOCTL_IOC_FIRST) || |
| + cmd - cmd_first >= AUTOFS_DEV_IOCTL_IOC_COUNT) { |
| + return -ENOTTY; |
| + } |
| + |
| + /* Copy the parameters into kernel space. */ |
| + param = copy_dev_ioctl(user); |
| + if (IS_ERR(param)) |
| + return PTR_ERR(param); |
| + |
| + err = validate_dev_ioctl(command, param); |
| + if (err) |
| + goto out; |
| + |
| + /* The validate routine above always sets the version */ |
| + if (cmd == AUTOFS_DEV_IOCTL_VERSION_CMD) |
| + goto done; |
| + |
| + fn = lookup_dev_ioctl(cmd); |
| + if (!fn) { |
| + AUTOFS_WARN("unknown command 0x%08x", command); |
| + return -ENOTTY; |
| + } |
| + |
| + fp = NULL; |
| + sbi = NULL; |
| + |
| + /* |
| + * For obvious reasons the openmount can't have a file |
| + * descriptor yet. We don't take a reference to the |
| + * file during close to allow for immediate release. |
| + */ |
| + if (cmd != AUTOFS_DEV_IOCTL_OPENMOUNT_CMD && |
| + cmd != AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD) { |
| + fp = fget(param->ioctlfd); |
| + if (!fp) { |
| + if (cmd == AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD) |
| + goto cont; |
| + err = -EBADF; |
| + goto out; |
| + } |
| + |
| + if (!fp->f_op) { |
| + err = -ENOTTY; |
| + fput(fp); |
| + goto out; |
| + } |
| + |
| + sbi = autofs_dev_ioctl_sbi(fp); |
| + if (!sbi || sbi->magic != AUTOFS_SBI_MAGIC) { |
| + err = -EINVAL; |
| + fput(fp); |
| + goto out; |
| + } |
| + |
| + /* |
| + * Admin needs to be able to set the mount catatonic in |
| + * order to be able to perform the re-open. |
| + */ |
| + if (!autofs4_oz_mode(sbi) && |
| + cmd != AUTOFS_DEV_IOCTL_CATATONIC_CMD) { |
| + err = -EACCES; |
| + fput(fp); |
| + goto out; |
| + } |
| + } |
| +cont: |
| + err = fn(fp, sbi, param); |
| + |
| + if (fp) |
| + fput(fp); |
| +done: |
| + if (err >= 0 && copy_to_user(user, param, AUTOFS_DEV_IOCTL_SIZE)) |
| + err = -EFAULT; |
| +out: |
| + free_dev_ioctl(param); |
| + return err; |
| +} |
| + |
| +static long autofs_dev_ioctl(struct file *file, uint command, ulong u) |
| +{ |
| + int err; |
| + err = _autofs_dev_ioctl(command, (struct autofs_dev_ioctl __user *) u); |
| + return (long) err; |
| +} |
| + |
| +#ifdef CONFIG_COMPAT |
| +static long autofs_dev_ioctl_compat(struct file *file, uint command, ulong u) |
| +{ |
| + return (long) autofs_dev_ioctl(file, command, (ulong) compat_ptr(u)); |
| +} |
| +#else |
| +#define autofs_dev_ioctl_compat NULL |
| +#endif |
| + |
| +static const struct file_operations _dev_ioctl_fops = { |
| + .unlocked_ioctl = autofs_dev_ioctl, |
| + .compat_ioctl = autofs_dev_ioctl_compat, |
| + .owner = THIS_MODULE, |
| +}; |
| + |
| +static struct miscdevice _autofs_dev_ioctl_misc = { |
| + .minor = MISC_DYNAMIC_MINOR, |
| + .name = AUTOFS_DEVICE_NAME, |
| + .fops = &_dev_ioctl_fops |
| +}; |
| + |
| +/* Register/deregister misc character device */ |
| +int autofs_dev_ioctl_init(void) |
| +{ |
| + int r; |
| + |
| + r = misc_register(&_autofs_dev_ioctl_misc); |
| + if (r) { |
| + AUTOFS_ERROR("misc_register failed for control device"); |
| + return r; |
| + } |
| + |
| + return 0; |
| +} |
| + |
| +void autofs_dev_ioctl_exit(void) |
| +{ |
| + misc_deregister(&_autofs_dev_ioctl_misc); |
| + return; |
| +} |
| + |
| |
| |
| @@ -29,11 +29,20 @@ static struct file_system_type autofs_fs |
| |
| static int __init init_autofs4_fs(void) |
| { |
| - return register_filesystem(&autofs_fs_type); |
| + int err; |
| + |
| + err = register_filesystem(&autofs_fs_type); |
| + if (err) |
| + return err; |
| + |
| + autofs_dev_ioctl_init(); |
| + |
| + return err; |
| } |
| |
| static void __exit exit_autofs4_fs(void) |
| { |
| + autofs_dev_ioctl_exit(); |
| unregister_filesystem(&autofs_fs_type); |
| } |
| |
| |
| |
| @@ -0,0 +1,229 @@ |
| +/* |
| + * Copyright 2008 Red Hat, Inc. All rights reserved. |
| + * Copyright 2008 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. |
| + */ |
| + |
| +#ifndef _LINUX_AUTO_DEV_IOCTL_H |
| +#define _LINUX_AUTO_DEV_IOCTL_H |
| + |
| +#include <linux/auto_fs.h> |
| + |
| +#ifdef __KERNEL__ |
| +#include <linux/string.h> |
| +#else |
| +#include <string.h> |
| +#endif /* __KERNEL__ */ |
| + |
| +#define AUTOFS_DEVICE_NAME "autofs" |
| + |
| +#define AUTOFS_DEV_IOCTL_VERSION_MAJOR 1 |
| +#define AUTOFS_DEV_IOCTL_VERSION_MINOR 0 |
| + |
| +#define AUTOFS_DEVID_LEN 16 |
| + |
| +#define AUTOFS_DEV_IOCTL_SIZE sizeof(struct autofs_dev_ioctl) |
| + |
| +/* |
| + * An ioctl interface for autofs mount point control. |
| + */ |
| + |
| +struct args_protover { |
| + __u32 version; |
| +}; |
| + |
| +struct args_protosubver { |
| + __u32 sub_version; |
| +}; |
| + |
| +struct args_openmount { |
| + __u32 devid; |
| +}; |
| + |
| +struct args_ready { |
| + __u32 token; |
| +}; |
| + |
| +struct args_fail { |
| + __u32 token; |
| + __s32 status; |
| +}; |
| + |
| +struct args_setpipefd { |
| + __s32 pipefd; |
| +}; |
| + |
| +struct args_timeout { |
| + __u64 timeout; |
| +}; |
| + |
| +struct args_requester { |
| + __u32 uid; |
| + __u32 gid; |
| +}; |
| + |
| +struct args_expire { |
| + __u32 how; |
| +}; |
| + |
| +struct args_askumount { |
| + __u32 may_umount; |
| +}; |
| + |
| +struct args_ismountpoint { |
| + union { |
| + struct args_in { |
| + __u32 type; |
| + } in; |
| + struct args_out { |
| + __u32 devid; |
| + __u32 magic; |
| + } out; |
| + }; |
| +}; |
| + |
| +/* |
| + * All the ioctls use this structure. |
| + * When sending a path size must account for the total length |
| + * of the chunk of memory otherwise is is the size of the |
| + * structure. |
| + */ |
| + |
| +struct autofs_dev_ioctl { |
| + __u32 ver_major; |
| + __u32 ver_minor; |
| + __u32 size; /* total size of data passed in |
| + * including this struct */ |
| + __s32 ioctlfd; /* automount command fd */ |
| + |
| + /* Command parameters */ |
| + |
| + union { |
| + struct args_protover protover; |
| + struct args_protosubver protosubver; |
| + struct args_openmount openmount; |
| + struct args_ready ready; |
| + struct args_fail fail; |
| + struct args_setpipefd setpipefd; |
| + struct args_timeout timeout; |
| + struct args_requester requester; |
| + struct args_expire expire; |
| + struct args_askumount askumount; |
| + struct args_ismountpoint ismountpoint; |
| + }; |
| + |
| + char path[0]; |
| +}; |
| + |
| +static inline void init_autofs_dev_ioctl(struct autofs_dev_ioctl *in) |
| +{ |
| + memset(in, 0, sizeof(struct autofs_dev_ioctl)); |
| + in->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR; |
| + in->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR; |
| + in->size = sizeof(struct autofs_dev_ioctl); |
| + in->ioctlfd = -1; |
| + return; |
| +} |
| + |
| +/* |
| + * If you change this make sure you make the corresponding change |
| + * to autofs-dev-ioctl.c:lookup_ioctl() |
| + */ |
| +enum { |
| + /* Get various version info */ |
| + AUTOFS_DEV_IOCTL_VERSION_CMD = 0x71, |
| + AUTOFS_DEV_IOCTL_PROTOVER_CMD, |
| + AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, |
| + |
| + /* Open mount ioctl fd */ |
| + AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, |
| + |
| + /* Close mount ioctl fd */ |
| + AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, |
| + |
| + /* Mount/expire status returns */ |
| + AUTOFS_DEV_IOCTL_READY_CMD, |
| + AUTOFS_DEV_IOCTL_FAIL_CMD, |
| + |
| + /* Activate/deactivate autofs mount */ |
| + AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, |
| + AUTOFS_DEV_IOCTL_CATATONIC_CMD, |
| + |
| + /* Expiry timeout */ |
| + AUTOFS_DEV_IOCTL_TIMEOUT_CMD, |
| + |
| + /* Get mount last requesting uid and gid */ |
| + AUTOFS_DEV_IOCTL_REQUESTER_CMD, |
| + |
| + /* Check for eligible expire candidates */ |
| + AUTOFS_DEV_IOCTL_EXPIRE_CMD, |
| + |
| + /* Request busy status */ |
| + AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, |
| + |
| + /* Check if path is a mountpoint */ |
| + AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, |
| +}; |
| + |
| +#define AUTOFS_IOCTL 0x93 |
| + |
| +#define AUTOFS_DEV_IOCTL_VERSION \ |
| + _IOWR(AUTOFS_IOCTL, \ |
| + AUTOFS_DEV_IOCTL_VERSION_CMD, struct autofs_dev_ioctl) |
| + |
| +#define AUTOFS_DEV_IOCTL_PROTOVER \ |
| + _IOWR(AUTOFS_IOCTL, \ |
| + AUTOFS_DEV_IOCTL_PROTOVER_CMD, struct autofs_dev_ioctl) |
| + |
| +#define AUTOFS_DEV_IOCTL_PROTOSUBVER \ |
| + _IOWR(AUTOFS_IOCTL, \ |
| + AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, struct autofs_dev_ioctl) |
| + |
| +#define AUTOFS_DEV_IOCTL_OPENMOUNT \ |
| + _IOWR(AUTOFS_IOCTL, \ |
| + AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, struct autofs_dev_ioctl) |
| + |
| +#define AUTOFS_DEV_IOCTL_CLOSEMOUNT \ |
| + _IOWR(AUTOFS_IOCTL, \ |
| + AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, struct autofs_dev_ioctl) |
| + |
| +#define AUTOFS_DEV_IOCTL_READY \ |
| + _IOWR(AUTOFS_IOCTL, \ |
| + AUTOFS_DEV_IOCTL_READY_CMD, struct autofs_dev_ioctl) |
| + |
| +#define AUTOFS_DEV_IOCTL_FAIL \ |
| + _IOWR(AUTOFS_IOCTL, \ |
| + AUTOFS_DEV_IOCTL_FAIL_CMD, struct autofs_dev_ioctl) |
| + |
| +#define AUTOFS_DEV_IOCTL_SETPIPEFD \ |
| + _IOWR(AUTOFS_IOCTL, \ |
| + AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, struct autofs_dev_ioctl) |
| + |
| +#define AUTOFS_DEV_IOCTL_CATATONIC \ |
| + _IOWR(AUTOFS_IOCTL, \ |
| + AUTOFS_DEV_IOCTL_CATATONIC_CMD, struct autofs_dev_ioctl) |
| + |
| +#define AUTOFS_DEV_IOCTL_TIMEOUT \ |
| + _IOWR(AUTOFS_IOCTL, \ |
| + AUTOFS_DEV_IOCTL_TIMEOUT_CMD, struct autofs_dev_ioctl) |
| + |
| +#define AUTOFS_DEV_IOCTL_REQUESTER \ |
| + _IOWR(AUTOFS_IOCTL, \ |
| + AUTOFS_DEV_IOCTL_REQUESTER_CMD, struct autofs_dev_ioctl) |
| + |
| +#define AUTOFS_DEV_IOCTL_EXPIRE \ |
| + _IOWR(AUTOFS_IOCTL, \ |
| + AUTOFS_DEV_IOCTL_EXPIRE_CMD, struct autofs_dev_ioctl) |
| + |
| +#define AUTOFS_DEV_IOCTL_ASKUMOUNT \ |
| + _IOWR(AUTOFS_IOCTL, \ |
| + AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, struct autofs_dev_ioctl) |
| + |
| +#define AUTOFS_DEV_IOCTL_ISMOUNTPOINT \ |
| + _IOWR(AUTOFS_IOCTL, \ |
| + AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, struct autofs_dev_ioctl) |
| + |
| +#endif /* _LINUX_AUTO_DEV_IOCTL_H */ |
| |
| |
| @@ -17,11 +17,13 @@ |
| #ifdef __KERNEL__ |
| #include <linux/fs.h> |
| #include <linux/limits.h> |
| +#include <linux/types.h> |
| +#include <linux/ioctl.h> |
| +#else |
| #include <asm/types.h> |
| +#include <sys/ioctl.h> |
| #endif /* __KERNEL__ */ |
| |
| -#include <linux/ioctl.h> |
| - |
| /* This file describes autofs v3 */ |
| #define AUTOFS_PROTO_VERSION 3 |
| |