// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2011-2020 Junjiro R. Okajima */ /* * File-based Hierarchy Storage Management */ #include #include #include #include #include "aufs.h" static aufs_bindex_t au_fhsm_bottom(struct super_block *sb) { struct au_sbinfo *sbinfo; struct au_fhsm *fhsm; SiMustAnyLock(sb); sbinfo = au_sbi(sb); fhsm = &sbinfo->si_fhsm; AuDebugOn(!fhsm); return fhsm->fhsm_bottom; } void au_fhsm_set_bottom(struct super_block *sb, aufs_bindex_t bindex) { struct au_sbinfo *sbinfo; struct au_fhsm *fhsm; SiMustWriteLock(sb); sbinfo = au_sbi(sb); fhsm = &sbinfo->si_fhsm; AuDebugOn(!fhsm); fhsm->fhsm_bottom = bindex; } /* ---------------------------------------------------------------------- */ static int au_fhsm_test_jiffy(struct au_sbinfo *sbinfo, struct au_branch *br) { struct au_br_fhsm *bf; bf = br->br_fhsm; MtxMustLock(&bf->bf_lock); return !bf->bf_readable || time_after(jiffies, bf->bf_jiffy + sbinfo->si_fhsm.fhsm_expire); } /* ---------------------------------------------------------------------- */ static void au_fhsm_notify(struct super_block *sb, int val) { struct au_sbinfo *sbinfo; struct au_fhsm *fhsm; SiMustAnyLock(sb); sbinfo = au_sbi(sb); fhsm = &sbinfo->si_fhsm; if (au_fhsm_pid(fhsm) && atomic_read(&fhsm->fhsm_readable) != -1) { atomic_set(&fhsm->fhsm_readable, val); if (val) wake_up(&fhsm->fhsm_wqh); } } static int au_fhsm_stfs(struct super_block *sb, aufs_bindex_t bindex, struct aufs_stfs *rstfs, int do_lock, int do_notify) { int err; struct au_branch *br; struct au_br_fhsm *bf; br = au_sbr(sb, bindex); AuDebugOn(au_br_rdonly(br)); bf = br->br_fhsm; AuDebugOn(!bf); if (do_lock) mutex_lock(&bf->bf_lock); else MtxMustLock(&bf->bf_lock); /* sb->s_root for NFS is unreliable */ err = au_br_stfs(br, &bf->bf_stfs); if (unlikely(err)) { AuErr1("FHSM failed (%d), b%d, ignored.\n", bindex, err); goto out; } bf->bf_jiffy = jiffies; bf->bf_readable = 1; if (do_notify) au_fhsm_notify(sb, /*val*/1); if (rstfs) *rstfs = bf->bf_stfs; out: if (do_lock) mutex_unlock(&bf->bf_lock); au_fhsm_notify(sb, /*val*/1); return err; } void au_fhsm_wrote(struct super_block *sb, aufs_bindex_t bindex, int force) { int err; struct au_sbinfo *sbinfo; struct au_fhsm *fhsm; struct au_branch *br; struct au_br_fhsm *bf; AuDbg("b%d, force %d\n", bindex, force); SiMustAnyLock(sb); sbinfo = au_sbi(sb); fhsm = &sbinfo->si_fhsm; if (!au_ftest_si(sbinfo, FHSM) || fhsm->fhsm_bottom == bindex) return; br = au_sbr(sb, bindex); bf = br->br_fhsm; AuDebugOn(!bf); mutex_lock(&bf->bf_lock); if (force || au_fhsm_pid(fhsm) || au_fhsm_test_jiffy(sbinfo, br)) err = au_fhsm_stfs(sb, bindex, /*rstfs*/NULL, /*do_lock*/0, /*do_notify*/1); mutex_unlock(&bf->bf_lock); } void au_fhsm_wrote_all(struct super_block *sb, int force) { aufs_bindex_t bindex, bbot; struct au_branch *br; /* exclude the bottom */ bbot = au_fhsm_bottom(sb); for (bindex = 0; bindex < bbot; bindex++) { br = au_sbr(sb, bindex); if (au_br_fhsm(br->br_perm)) au_fhsm_wrote(sb, bindex, force); } } /* ---------------------------------------------------------------------- */ static __poll_t au_fhsm_poll(struct file *file, struct poll_table_struct *wait) { __poll_t mask; struct au_sbinfo *sbinfo; struct au_fhsm *fhsm; mask = 0; sbinfo = file->private_data; fhsm = &sbinfo->si_fhsm; poll_wait(file, &fhsm->fhsm_wqh, wait); if (atomic_read(&fhsm->fhsm_readable)) mask = EPOLLIN /* | EPOLLRDNORM */; if (!mask) AuDbg("mask 0x%x\n", mask); return mask; } static int au_fhsm_do_read_one(struct aufs_stbr __user *stbr, struct aufs_stfs *stfs, __s16 brid) { int err; err = copy_to_user(&stbr->stfs, stfs, sizeof(*stfs)); if (!err) err = __put_user(brid, &stbr->brid); if (unlikely(err)) err = -EFAULT; return err; } static ssize_t au_fhsm_do_read(struct super_block *sb, struct aufs_stbr __user *stbr, size_t count) { ssize_t err; int nstbr; aufs_bindex_t bindex, bbot; struct au_branch *br; struct au_br_fhsm *bf; /* except the bottom branch */ err = 0; nstbr = 0; bbot = au_fhsm_bottom(sb); for (bindex = 0; !err && bindex < bbot; bindex++) { br = au_sbr(sb, bindex); if (!au_br_fhsm(br->br_perm)) continue; bf = br->br_fhsm; mutex_lock(&bf->bf_lock); if (bf->bf_readable) { err = -EFAULT; if (count >= sizeof(*stbr)) err = au_fhsm_do_read_one(stbr++, &bf->bf_stfs, br->br_id); if (!err) { bf->bf_readable = 0; count -= sizeof(*stbr); nstbr++; } } mutex_unlock(&bf->bf_lock); } if (!err) err = sizeof(*stbr) * nstbr; return err; } static ssize_t au_fhsm_read(struct file *file, char __user *buf, size_t count, loff_t *pos) { ssize_t err; int readable; aufs_bindex_t nfhsm, bindex, bbot; struct au_sbinfo *sbinfo; struct au_fhsm *fhsm; struct au_branch *br; struct super_block *sb; err = 0; sbinfo = file->private_data; fhsm = &sbinfo->si_fhsm; need_data: spin_lock_irq(&fhsm->fhsm_wqh.lock); if (!atomic_read(&fhsm->fhsm_readable)) { if (vfsub_file_flags(file) & O_NONBLOCK) err = -EAGAIN; else err = wait_event_interruptible_locked_irq (fhsm->fhsm_wqh, atomic_read(&fhsm->fhsm_readable)); } spin_unlock_irq(&fhsm->fhsm_wqh.lock); if (unlikely(err)) goto out; /* sb may already be dead */ au_rw_read_lock(&sbinfo->si_rwsem); readable = atomic_read(&fhsm->fhsm_readable); if (readable > 0) { sb = sbinfo->si_sb; AuDebugOn(!sb); /* exclude the bottom branch */ nfhsm = 0; bbot = au_fhsm_bottom(sb); for (bindex = 0; bindex < bbot; bindex++) { br = au_sbr(sb, bindex); if (au_br_fhsm(br->br_perm)) nfhsm++; } err = -EMSGSIZE; if (nfhsm * sizeof(struct aufs_stbr) <= count) { atomic_set(&fhsm->fhsm_readable, 0); err = au_fhsm_do_read(sbinfo->si_sb, (void __user *)buf, count); } } au_rw_read_unlock(&sbinfo->si_rwsem); if (!readable) goto need_data; out: return err; } static int au_fhsm_release(struct inode *inode, struct file *file) { struct au_sbinfo *sbinfo; struct au_fhsm *fhsm; /* sb may already be dead */ sbinfo = file->private_data; fhsm = &sbinfo->si_fhsm; spin_lock(&fhsm->fhsm_spin); fhsm->fhsm_pid = 0; spin_unlock(&fhsm->fhsm_spin); kobject_put(&sbinfo->si_kobj); return 0; } static const struct file_operations au_fhsm_fops = { .owner = THIS_MODULE, .llseek = noop_llseek, .read = au_fhsm_read, .poll = au_fhsm_poll, .release = au_fhsm_release }; int au_fhsm_fd(struct super_block *sb, int oflags) { int err, fd; struct au_sbinfo *sbinfo; struct au_fhsm *fhsm; err = -EPERM; if (unlikely(!capable(CAP_SYS_ADMIN))) goto out; err = -EINVAL; if (unlikely(oflags & ~(O_CLOEXEC | O_NONBLOCK))) goto out; err = 0; sbinfo = au_sbi(sb); fhsm = &sbinfo->si_fhsm; spin_lock(&fhsm->fhsm_spin); if (!fhsm->fhsm_pid) fhsm->fhsm_pid = current->pid; else err = -EBUSY; spin_unlock(&fhsm->fhsm_spin); if (unlikely(err)) goto out; oflags |= O_RDONLY; /* oflags |= FMODE_NONOTIFY; */ fd = anon_inode_getfd("[aufs_fhsm]", &au_fhsm_fops, sbinfo, oflags); err = fd; if (unlikely(fd < 0)) goto out_pid; /* succeed regardless 'fhsm' status */ kobject_get(&sbinfo->si_kobj); si_noflush_read_lock(sb); if (au_ftest_si(sbinfo, FHSM)) au_fhsm_wrote_all(sb, /*force*/0); si_read_unlock(sb); goto out; /* success */ out_pid: spin_lock(&fhsm->fhsm_spin); fhsm->fhsm_pid = 0; spin_unlock(&fhsm->fhsm_spin); out: AuTraceErr(err); return err; } /* ---------------------------------------------------------------------- */ int au_fhsm_br_alloc(struct au_branch *br) { int err; err = 0; br->br_fhsm = kmalloc(sizeof(*br->br_fhsm), GFP_NOFS); if (br->br_fhsm) au_br_fhsm_init(br->br_fhsm); else err = -ENOMEM; return err; } /* ---------------------------------------------------------------------- */ void au_fhsm_fin(struct super_block *sb) { au_fhsm_notify(sb, /*val*/-1); } void au_fhsm_init(struct au_sbinfo *sbinfo) { struct au_fhsm *fhsm; fhsm = &sbinfo->si_fhsm; spin_lock_init(&fhsm->fhsm_spin); init_waitqueue_head(&fhsm->fhsm_wqh); atomic_set(&fhsm->fhsm_readable, 0); fhsm->fhsm_expire = msecs_to_jiffies(AUFS_FHSM_CACHE_DEF_SEC * MSEC_PER_SEC); fhsm->fhsm_bottom = -1; } void au_fhsm_set(struct au_sbinfo *sbinfo, unsigned int sec) { sbinfo->si_fhsm.fhsm_expire = msecs_to_jiffies(sec * MSEC_PER_SEC); } void au_fhsm_show(struct seq_file *seq, struct au_sbinfo *sbinfo) { unsigned int u; if (!au_ftest_si(sbinfo, FHSM)) return; u = jiffies_to_msecs(sbinfo->si_fhsm.fhsm_expire) / MSEC_PER_SEC; if (u != AUFS_FHSM_CACHE_DEF_SEC) seq_printf(seq, ",fhsm_sec=%u", u); }