mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
427 lines
9.3 KiB
427 lines
9.3 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
/* |
|
* Copyright (C) 2011-2020 Junjiro R. Okajima |
|
* |
|
* This program, aufs is free software; you can redistribute it and/or modify |
|
* it under the terms of the GNU General Public License as published by |
|
* the Free Software Foundation; either version 2 of the License, or |
|
* (at your option) any later version. |
|
* |
|
* This program is distributed in the hope that it will be useful, |
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
* GNU General Public License for more details. |
|
* |
|
* You should have received a copy of the GNU General Public License |
|
* along with this program; if not, write to the Free Software |
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
|
*/ |
|
|
|
/* |
|
* File-based Hierarchy Storage Management |
|
*/ |
|
|
|
#include <linux/anon_inodes.h> |
|
#include <linux/poll.h> |
|
#include <linux/seq_file.h> |
|
#include <linux/statfs.h> |
|
#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); |
|
}
|
|
|