#include "autofs_i.h"
static inline int autofs_can_expire(struct dentry *dentry,
unsigned long timeout, unsigned int how)
{
struct autofs_info *ino = autofs_dentry_ino(dentry);
if (ino == NULL)
return 0;
if (!(how & AUTOFS_EXP_IMMEDIATE)) {
if (!timeout || time_after(ino->last_used + timeout, jiffies))
return 0;
}
return 1;
}
static int autofs_mount_busy(struct vfsmount *mnt,
struct dentry *dentry, unsigned int how)
{
struct dentry *top = dentry;
struct path path = {.mnt = mnt, .dentry = dentry};
int status = 1;
pr_debug("dentry %p %pd\n", dentry, dentry);
path_get(&path);
if (!follow_down_one(&path))
goto done;
if (is_autofs_dentry(path.dentry)) {
struct autofs_sb_info *sbi = autofs_sbi(path.dentry->d_sb);
if (autofs_type_indirect(sbi->type))
goto done;
}
if (how & AUTOFS_EXP_FORCED) {
status = 0;
goto done;
}
if (!may_umount_tree(path.mnt)) {
struct autofs_info *ino;
ino = autofs_dentry_ino(top);
ino->last_used = jiffies;
goto done;
}
status = 0;
done:
pr_debug("returning = %d\n", status);
path_put(&path);
return status;
}
static struct dentry *positive_after(struct dentry *p, struct dentry *child)
{
if (child)
child = list_next_entry(child, d_child);
else
child = list_first_entry(&p->d_subdirs, struct dentry, d_child);
list_for_each_entry_from(child, &p->d_subdirs, d_child) {
spin_lock_nested(&child->d_lock, DENTRY_D_LOCK_NESTED);
if (simple_positive(child)) {
dget_dlock(child);
spin_unlock(&child->d_lock);
return child;
}
spin_unlock(&child->d_lock);
}
return NULL;
}
static struct dentry *get_next_positive_subdir(struct dentry *prev,
struct dentry *root)
{
struct autofs_sb_info *sbi = autofs_sbi(root->d_sb);
struct dentry *q;
spin_lock(&sbi->lookup_lock);
spin_lock(&root->d_lock);
q = positive_after(root, prev);
spin_unlock(&root->d_lock);
spin_unlock(&sbi->lookup_lock);
dput(prev);
return q;
}
static struct dentry *get_next_positive_dentry(struct dentry *prev,
struct dentry *root)
{
struct autofs_sb_info *sbi = autofs_sbi(root->d_sb);
struct dentry *p = prev, *ret = NULL, *d = NULL;
if (prev == NULL)
return dget(root);
spin_lock(&sbi->lookup_lock);
spin_lock(&p->d_lock);
while (1) {
struct dentry *parent;
ret = positive_after(p, d);
if (ret || p == root)
break;
parent = p->d_parent;
spin_unlock(&p->d_lock);
spin_lock(&parent->d_lock);
d = p;
p = parent;
}
spin_unlock(&p->d_lock);
spin_unlock(&sbi->lookup_lock);
dput(prev);
return ret;
}
static int autofs_direct_busy(struct vfsmount *mnt,
struct dentry *top,
unsigned long timeout,
unsigned int how)
{
pr_debug("top %p %pd\n", top, top);
if (how & AUTOFS_EXP_FORCED)
return 0;
if (!may_umount_tree(mnt)) {
struct autofs_info *ino;
ino = autofs_dentry_ino(top);
if (ino)
ino->last_used = jiffies;
return 1;
}
if (!autofs_can_expire(top, timeout, how))
return 1;
return 0;
}
static int autofs_tree_busy(struct vfsmount *mnt,
struct dentry *top,
unsigned long timeout,
unsigned int how)
{
struct autofs_info *top_ino = autofs_dentry_ino(top);
struct dentry *p;
pr_debug("top %p %pd\n", top, top);
if (!simple_positive(top))
return 1;
p = NULL;
while ((p = get_next_positive_dentry(p, top))) {
pr_debug("dentry %p %pd\n", p, p);
if (d_mountpoint(p)) {
if (autofs_mount_busy(mnt, p, how)) {
top_ino->last_used = jiffies;
dput(p);
return 1;
}
} else {
struct autofs_info *ino = autofs_dentry_ino(p);
unsigned int ino_count = READ_ONCE(ino->count);
if (p == top)
ino_count += 2;
else
ino_count++;
if (d_count(p) > ino_count) {
top_ino->last_used = jiffies;
dput(p);
return 1;
}
}
}
if (how & AUTOFS_EXP_FORCED)
return 0;
if (!autofs_can_expire(top, timeout, how))
return 1;
return 0;
}
static struct dentry *autofs_check_leaves(struct vfsmount *mnt,
struct dentry *parent,
unsigned long timeout,
unsigned int how)
{
struct dentry *p;
pr_debug("parent %p %pd\n", parent, parent);
p = NULL;
while ((p = get_next_positive_dentry(p, parent))) {
pr_debug("dentry %p %pd\n", p, p);
if (d_mountpoint(p)) {
if (autofs_mount_busy(mnt, p, how))
continue;
if (how & AUTOFS_EXP_FORCED)
return p;
if (autofs_can_expire(p, timeout, how))
return p;
}
}
return NULL;
}
static struct dentry *autofs_expire_direct(struct super_block *sb,
struct vfsmount *mnt,
struct autofs_sb_info *sbi,
unsigned int how)
{
struct dentry *root = dget(sb->s_root);
struct autofs_info *ino;
unsigned long timeout;
if (!root)
return NULL;
timeout = sbi->exp_timeout;
if (!autofs_direct_busy(mnt, root, timeout, how)) {
spin_lock(&sbi->fs_lock);
ino = autofs_dentry_ino(root);
if (ino->flags & AUTOFS_INF_PENDING) {
spin_unlock(&sbi->fs_lock);
goto out;
}
ino->flags |= AUTOFS_INF_WANT_EXPIRE;
spin_unlock(&sbi->fs_lock);
synchronize_rcu();
if (!autofs_direct_busy(mnt, root, timeout, how)) {
spin_lock(&sbi->fs_lock);
ino->flags |= AUTOFS_INF_EXPIRING;
init_completion(&ino->expire_complete);
spin_unlock(&sbi->fs_lock);
return root;
}
spin_lock(&sbi->fs_lock);
ino->flags &= ~AUTOFS_INF_WANT_EXPIRE;
spin_unlock(&sbi->fs_lock);
}
out:
dput(root);
return NULL;
}
static struct dentry *should_expire(struct dentry *dentry,
struct vfsmount *mnt,
unsigned long timeout,
unsigned int how)
{
struct autofs_info *ino = autofs_dentry_ino(dentry);
unsigned int ino_count;
if (ino->flags & AUTOFS_INF_PENDING)
return NULL;
if (d_mountpoint(dentry)) {
pr_debug("checking mountpoint %p %pd\n", dentry, dentry);
if (autofs_mount_busy(mnt, dentry, how))
return NULL;
if (how & AUTOFS_EXP_FORCED)
return dentry;
if (autofs_can_expire(dentry, timeout, how))
return dentry;
return NULL;
}
if (d_is_symlink(dentry)) {
pr_debug("checking symlink %p %pd\n", dentry, dentry);
if (how & AUTOFS_EXP_FORCED)
return dentry;
if (autofs_can_expire(dentry, timeout, how))
return dentry;
return NULL;
}
if (autofs_empty(ino))
return NULL;
if (!(how & AUTOFS_EXP_LEAVES)) {
if (!(how & AUTOFS_EXP_FORCED)) {
ino_count = READ_ONCE(ino->count) + 1;
if (d_count(dentry) > ino_count)
return NULL;
}
if (!autofs_tree_busy(mnt, dentry, timeout, how))
return dentry;
} else {
struct dentry *expired;
if (!(how & AUTOFS_EXP_FORCED)) {
ino_count = READ_ONCE(ino->count) + 1;
if (d_count(dentry) > ino_count)
return NULL;
}
expired = autofs_check_leaves(mnt, dentry, timeout, how);
if (expired) {
if (expired == dentry)
dput(dentry);
return expired;
}
}
return NULL;
}
static struct dentry *autofs_expire_indirect(struct super_block *sb,
struct vfsmount *mnt,
struct autofs_sb_info *sbi,
unsigned int how)
{
unsigned long timeout;
struct dentry *root = sb->s_root;
struct dentry *dentry;
struct dentry *expired;
struct dentry *found;
struct autofs_info *ino;
if (!root)
return NULL;
timeout = sbi->exp_timeout;
dentry = NULL;
while ((dentry = get_next_positive_subdir(dentry, root))) {
spin_lock(&sbi->fs_lock);
ino = autofs_dentry_ino(dentry);
if (ino->flags & AUTOFS_INF_WANT_EXPIRE) {
spin_unlock(&sbi->fs_lock);
continue;
}
spin_unlock(&sbi->fs_lock);
expired = should_expire(dentry, mnt, timeout, how);
if (!expired)
continue;
spin_lock(&sbi->fs_lock);
ino = autofs_dentry_ino(expired);
ino->flags |= AUTOFS_INF_WANT_EXPIRE;
spin_unlock(&sbi->fs_lock);
synchronize_rcu();
how &= ~AUTOFS_EXP_LEAVES;
found = should_expire(expired, mnt, timeout, how);
if (found != expired) {
dput(found);
goto next;
}
if (expired != dentry)
dput(dentry);
spin_lock(&sbi->fs_lock);
goto found;
next:
spin_lock(&sbi->fs_lock);
ino->flags &= ~AUTOFS_INF_WANT_EXPIRE;
spin_unlock(&sbi->fs_lock);
if (expired != dentry)
dput(expired);
}
return NULL;
found:
pr_debug("returning %p %pd\n", expired, expired);
ino->flags |= AUTOFS_INF_EXPIRING;
init_completion(&ino->expire_complete);
spin_unlock(&sbi->fs_lock);
return expired;
}
int autofs_expire_wait(const struct path *path, int rcu_walk)
{
struct dentry *dentry = path->dentry;
struct autofs_sb_info *sbi = autofs_sbi(dentry->d_sb);
struct autofs_info *ino = autofs_dentry_ino(dentry);
int status;
int state;
if (!(ino->flags & AUTOFS_INF_WANT_EXPIRE))
return 0;
if (rcu_walk)
return -ECHILD;
retry:
spin_lock(&sbi->fs_lock);
state = ino->flags & (AUTOFS_INF_WANT_EXPIRE | AUTOFS_INF_EXPIRING);
if (state == AUTOFS_INF_WANT_EXPIRE) {
spin_unlock(&sbi->fs_lock);
schedule_timeout_uninterruptible(HZ/10);
goto retry;
}
if (state & AUTOFS_INF_EXPIRING) {
spin_unlock(&sbi->fs_lock);
pr_debug("waiting for expire %p name=%pd\n", dentry, dentry);
status = autofs_wait(sbi, path, NFY_NONE);
wait_for_completion(&ino->expire_complete);
pr_debug("expire done status=%d\n", status);
if (d_unhashed(dentry))
return -EAGAIN;
return status;
}
spin_unlock(&sbi->fs_lock);
return 0;
}
int autofs_expire_run(struct super_block *sb,
struct vfsmount *mnt,
struct autofs_sb_info *sbi,
struct autofs_packet_expire __user *pkt_p)
{
struct autofs_packet_expire pkt;
struct autofs_info *ino;
struct dentry *dentry;
int ret = 0;
memset(&pkt, 0, sizeof(pkt));
pkt.hdr.proto_version = sbi->version;
pkt.hdr.type = autofs_ptype_expire;
dentry = autofs_expire_indirect(sb, mnt, sbi, 0);
if (!dentry)
return -EAGAIN;
pkt.len = dentry->d_name.len;
memcpy(pkt.name, dentry->d_name.name, pkt.len);
pkt.name[pkt.len] = '\0';
if (copy_to_user(pkt_p, &pkt, sizeof(struct autofs_packet_expire)))
ret = -EFAULT;
spin_lock(&sbi->fs_lock);
ino = autofs_dentry_ino(dentry);
ino->last_used = jiffies;
ino->flags &= ~(AUTOFS_INF_EXPIRING|AUTOFS_INF_WANT_EXPIRE);
complete_all(&ino->expire_complete);
spin_unlock(&sbi->fs_lock);
dput(dentry);
return ret;
}
int autofs_do_expire_multi(struct super_block *sb, struct vfsmount *mnt,
struct autofs_sb_info *sbi, unsigned int how)
{
struct dentry *dentry;
int ret = -EAGAIN;
if (autofs_type_trigger(sbi->type))
dentry = autofs_expire_direct(sb, mnt, sbi, how);
else
dentry = autofs_expire_indirect(sb, mnt, sbi, how);
if (dentry) {
struct autofs_info *ino = autofs_dentry_ino(dentry);
const struct path path = { .mnt = mnt, .dentry = dentry };
ret = autofs_wait(sbi, &path, NFY_EXPIRE);
spin_lock(&sbi->fs_lock);
ino->last_used = jiffies;
ino->flags &= ~(AUTOFS_INF_EXPIRING|AUTOFS_INF_WANT_EXPIRE);
complete_all(&ino->expire_complete);
spin_unlock(&sbi->fs_lock);
dput(dentry);
}
return ret;
}
int autofs_expire_multi(struct super_block *sb, struct vfsmount *mnt,
struct autofs_sb_info *sbi, int __user *arg)
{
unsigned int how = 0;
if (arg && get_user(how, arg))
return -EFAULT;
return autofs_do_expire_multi(sb, mnt, sbi, how);
}