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.
838 lines
18 KiB
838 lines
18 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
/* |
|
* Copyright (C) 2005-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, see <http://www.gnu.org/licenses/>. |
|
*/ |
|
|
|
/* |
|
* export via nfs |
|
*/ |
|
|
|
#include <linux/exportfs.h> |
|
#include <linux/fs_struct.h> |
|
#include <linux/namei.h> |
|
#include <linux/nsproxy.h> |
|
#include <linux/random.h> |
|
#include <linux/writeback.h> |
|
#include "aufs.h" |
|
|
|
union conv { |
|
#ifdef CONFIG_AUFS_INO_T_64 |
|
__u32 a[2]; |
|
#else |
|
__u32 a[1]; |
|
#endif |
|
ino_t ino; |
|
}; |
|
|
|
static ino_t decode_ino(__u32 *a) |
|
{ |
|
union conv u; |
|
|
|
BUILD_BUG_ON(sizeof(u.ino) != sizeof(u.a)); |
|
u.a[0] = a[0]; |
|
#ifdef CONFIG_AUFS_INO_T_64 |
|
u.a[1] = a[1]; |
|
#endif |
|
return u.ino; |
|
} |
|
|
|
static void encode_ino(__u32 *a, ino_t ino) |
|
{ |
|
union conv u; |
|
|
|
u.ino = ino; |
|
a[0] = u.a[0]; |
|
#ifdef CONFIG_AUFS_INO_T_64 |
|
a[1] = u.a[1]; |
|
#endif |
|
} |
|
|
|
/* NFS file handle */ |
|
enum { |
|
Fh_br_id, |
|
Fh_sigen, |
|
#ifdef CONFIG_AUFS_INO_T_64 |
|
/* support 64bit inode number */ |
|
Fh_ino1, |
|
Fh_ino2, |
|
Fh_dir_ino1, |
|
Fh_dir_ino2, |
|
#else |
|
Fh_ino1, |
|
Fh_dir_ino1, |
|
#endif |
|
Fh_igen, |
|
Fh_h_type, |
|
Fh_tail, |
|
|
|
Fh_ino = Fh_ino1, |
|
Fh_dir_ino = Fh_dir_ino1 |
|
}; |
|
|
|
static int au_test_anon(struct dentry *dentry) |
|
{ |
|
/* note: read d_flags without d_lock */ |
|
return !!(dentry->d_flags & DCACHE_DISCONNECTED); |
|
} |
|
|
|
int au_test_nfsd(void) |
|
{ |
|
int ret; |
|
struct task_struct *tsk = current; |
|
char comm[sizeof(tsk->comm)]; |
|
|
|
ret = 0; |
|
if (tsk->flags & PF_KTHREAD) { |
|
get_task_comm(comm, tsk); |
|
ret = !strcmp(comm, "nfsd"); |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
/* ---------------------------------------------------------------------- */ |
|
/* inode generation external table */ |
|
|
|
void au_xigen_inc(struct inode *inode) |
|
{ |
|
loff_t pos; |
|
ssize_t sz; |
|
__u32 igen; |
|
struct super_block *sb; |
|
struct au_sbinfo *sbinfo; |
|
|
|
sb = inode->i_sb; |
|
AuDebugOn(!au_opt_test(au_mntflags(sb), XINO)); |
|
|
|
sbinfo = au_sbi(sb); |
|
pos = inode->i_ino; |
|
pos *= sizeof(igen); |
|
igen = inode->i_generation + 1; |
|
sz = xino_fwrite(sbinfo->si_xwrite, sbinfo->si_xigen, &igen, |
|
sizeof(igen), &pos); |
|
if (sz == sizeof(igen)) |
|
return; /* success */ |
|
|
|
if (unlikely(sz >= 0)) |
|
AuIOErr("xigen error (%zd)\n", sz); |
|
} |
|
|
|
int au_xigen_new(struct inode *inode) |
|
{ |
|
int err; |
|
loff_t pos; |
|
ssize_t sz; |
|
struct super_block *sb; |
|
struct au_sbinfo *sbinfo; |
|
struct file *file; |
|
|
|
err = 0; |
|
/* todo: dirty, at mount time */ |
|
if (inode->i_ino == AUFS_ROOT_INO) |
|
goto out; |
|
sb = inode->i_sb; |
|
SiMustAnyLock(sb); |
|
if (unlikely(!au_opt_test(au_mntflags(sb), XINO))) |
|
goto out; |
|
|
|
err = -EFBIG; |
|
pos = inode->i_ino; |
|
if (unlikely(au_loff_max / sizeof(inode->i_generation) - 1 < pos)) { |
|
AuIOErr1("too large i%lld\n", pos); |
|
goto out; |
|
} |
|
pos *= sizeof(inode->i_generation); |
|
|
|
err = 0; |
|
sbinfo = au_sbi(sb); |
|
file = sbinfo->si_xigen; |
|
BUG_ON(!file); |
|
|
|
if (vfsub_f_size_read(file) |
|
< pos + sizeof(inode->i_generation)) { |
|
inode->i_generation = atomic_inc_return(&sbinfo->si_xigen_next); |
|
sz = xino_fwrite(sbinfo->si_xwrite, file, &inode->i_generation, |
|
sizeof(inode->i_generation), &pos); |
|
} else |
|
sz = xino_fread(sbinfo->si_xread, file, &inode->i_generation, |
|
sizeof(inode->i_generation), &pos); |
|
if (sz == sizeof(inode->i_generation)) |
|
goto out; /* success */ |
|
|
|
err = sz; |
|
if (unlikely(sz >= 0)) { |
|
err = -EIO; |
|
AuIOErr("xigen error (%zd)\n", sz); |
|
} |
|
|
|
out: |
|
return err; |
|
} |
|
|
|
int au_xigen_set(struct super_block *sb, struct path *path) |
|
{ |
|
int err; |
|
struct au_sbinfo *sbinfo; |
|
struct file *file; |
|
|
|
SiMustWriteLock(sb); |
|
|
|
sbinfo = au_sbi(sb); |
|
file = au_xino_create2(sb, path, sbinfo->si_xigen); |
|
err = PTR_ERR(file); |
|
if (IS_ERR(file)) |
|
goto out; |
|
err = 0; |
|
if (sbinfo->si_xigen) |
|
fput(sbinfo->si_xigen); |
|
sbinfo->si_xigen = file; |
|
|
|
out: |
|
AuTraceErr(err); |
|
return err; |
|
} |
|
|
|
void au_xigen_clr(struct super_block *sb) |
|
{ |
|
struct au_sbinfo *sbinfo; |
|
|
|
SiMustWriteLock(sb); |
|
|
|
sbinfo = au_sbi(sb); |
|
if (sbinfo->si_xigen) { |
|
fput(sbinfo->si_xigen); |
|
sbinfo->si_xigen = NULL; |
|
} |
|
} |
|
|
|
/* ---------------------------------------------------------------------- */ |
|
|
|
static struct dentry *decode_by_ino(struct super_block *sb, ino_t ino, |
|
ino_t dir_ino) |
|
{ |
|
struct dentry *dentry, *d; |
|
struct inode *inode; |
|
unsigned int sigen; |
|
|
|
dentry = NULL; |
|
inode = ilookup(sb, ino); |
|
if (!inode) |
|
goto out; |
|
|
|
dentry = ERR_PTR(-ESTALE); |
|
sigen = au_sigen(sb); |
|
if (unlikely(au_is_bad_inode(inode) |
|
|| IS_DEADDIR(inode) |
|
|| sigen != au_iigen(inode, NULL))) |
|
goto out_iput; |
|
|
|
dentry = NULL; |
|
if (!dir_ino || S_ISDIR(inode->i_mode)) |
|
dentry = d_find_alias(inode); |
|
else { |
|
spin_lock(&inode->i_lock); |
|
hlist_for_each_entry(d, &inode->i_dentry, d_u.d_alias) { |
|
spin_lock(&d->d_lock); |
|
if (!au_test_anon(d) |
|
&& d_inode(d->d_parent)->i_ino == dir_ino) { |
|
dentry = dget_dlock(d); |
|
spin_unlock(&d->d_lock); |
|
break; |
|
} |
|
spin_unlock(&d->d_lock); |
|
} |
|
spin_unlock(&inode->i_lock); |
|
} |
|
if (unlikely(dentry && au_digen_test(dentry, sigen))) { |
|
/* need to refresh */ |
|
dput(dentry); |
|
dentry = NULL; |
|
} |
|
|
|
out_iput: |
|
iput(inode); |
|
out: |
|
AuTraceErrPtr(dentry); |
|
return dentry; |
|
} |
|
|
|
/* ---------------------------------------------------------------------- */ |
|
|
|
/* todo: dirty? */ |
|
/* if exportfs_decode_fh() passed vfsmount*, we could be happy */ |
|
|
|
struct au_compare_mnt_args { |
|
/* input */ |
|
struct super_block *sb; |
|
|
|
/* output */ |
|
struct vfsmount *mnt; |
|
}; |
|
|
|
static int au_compare_mnt(struct vfsmount *mnt, void *arg) |
|
{ |
|
struct au_compare_mnt_args *a = arg; |
|
|
|
if (mnt->mnt_sb != a->sb) |
|
return 0; |
|
a->mnt = mntget(mnt); |
|
return 1; |
|
} |
|
|
|
static struct vfsmount *au_mnt_get(struct super_block *sb) |
|
{ |
|
int err; |
|
struct path root; |
|
struct au_compare_mnt_args args = { |
|
.sb = sb |
|
}; |
|
|
|
get_fs_root(current->fs, &root); |
|
rcu_read_lock(); |
|
err = iterate_mounts(au_compare_mnt, &args, root.mnt); |
|
rcu_read_unlock(); |
|
path_put(&root); |
|
AuDebugOn(!err); |
|
AuDebugOn(!args.mnt); |
|
return args.mnt; |
|
} |
|
|
|
struct au_nfsd_si_lock { |
|
unsigned int sigen; |
|
aufs_bindex_t bindex, br_id; |
|
unsigned char force_lock; |
|
}; |
|
|
|
static int si_nfsd_read_lock(struct super_block *sb, |
|
struct au_nfsd_si_lock *nsi_lock) |
|
{ |
|
int err; |
|
aufs_bindex_t bindex; |
|
|
|
si_read_lock(sb, AuLock_FLUSH); |
|
|
|
/* branch id may be wrapped around */ |
|
err = 0; |
|
bindex = au_br_index(sb, nsi_lock->br_id); |
|
if (bindex >= 0 && nsi_lock->sigen + AUFS_BRANCH_MAX > au_sigen(sb)) |
|
goto out; /* success */ |
|
|
|
err = -ESTALE; |
|
bindex = -1; |
|
if (!nsi_lock->force_lock) |
|
si_read_unlock(sb); |
|
|
|
out: |
|
nsi_lock->bindex = bindex; |
|
return err; |
|
} |
|
|
|
struct find_name_by_ino { |
|
struct dir_context ctx; |
|
int called, found; |
|
ino_t ino; |
|
char *name; |
|
int namelen; |
|
}; |
|
|
|
static int |
|
find_name_by_ino(struct dir_context *ctx, const char *name, int namelen, |
|
loff_t offset, u64 ino, unsigned int d_type) |
|
{ |
|
struct find_name_by_ino *a = container_of(ctx, struct find_name_by_ino, |
|
ctx); |
|
|
|
a->called++; |
|
if (a->ino != ino) |
|
return 0; |
|
|
|
memcpy(a->name, name, namelen); |
|
a->namelen = namelen; |
|
a->found = 1; |
|
return 1; |
|
} |
|
|
|
static struct dentry *au_lkup_by_ino(struct path *path, ino_t ino, |
|
struct au_nfsd_si_lock *nsi_lock) |
|
{ |
|
struct dentry *dentry, *parent; |
|
struct file *file; |
|
struct inode *dir; |
|
struct find_name_by_ino arg = { |
|
.ctx = { |
|
.actor = find_name_by_ino |
|
} |
|
}; |
|
int err; |
|
|
|
parent = path->dentry; |
|
if (nsi_lock) |
|
si_read_unlock(parent->d_sb); |
|
file = vfsub_dentry_open(path, au_dir_roflags); |
|
dentry = (void *)file; |
|
if (IS_ERR(file)) |
|
goto out; |
|
|
|
dentry = ERR_PTR(-ENOMEM); |
|
arg.name = (void *)__get_free_page(GFP_NOFS); |
|
if (unlikely(!arg.name)) |
|
goto out_file; |
|
arg.ino = ino; |
|
arg.found = 0; |
|
do { |
|
arg.called = 0; |
|
/* smp_mb(); */ |
|
err = vfsub_iterate_dir(file, &arg.ctx); |
|
} while (!err && !arg.found && arg.called); |
|
dentry = ERR_PTR(err); |
|
if (unlikely(err)) |
|
goto out_name; |
|
/* instead of ENOENT */ |
|
dentry = ERR_PTR(-ESTALE); |
|
if (!arg.found) |
|
goto out_name; |
|
|
|
/* do not call vfsub_lkup_one() */ |
|
dir = d_inode(parent); |
|
dentry = vfsub_lookup_one_len_unlocked(arg.name, parent, arg.namelen); |
|
AuTraceErrPtr(dentry); |
|
if (IS_ERR(dentry)) |
|
goto out_name; |
|
AuDebugOn(au_test_anon(dentry)); |
|
if (unlikely(d_really_is_negative(dentry))) { |
|
dput(dentry); |
|
dentry = ERR_PTR(-ENOENT); |
|
} |
|
|
|
out_name: |
|
free_page((unsigned long)arg.name); |
|
out_file: |
|
fput(file); |
|
out: |
|
if (unlikely(nsi_lock |
|
&& si_nfsd_read_lock(parent->d_sb, nsi_lock) < 0)) |
|
if (!IS_ERR(dentry)) { |
|
dput(dentry); |
|
dentry = ERR_PTR(-ESTALE); |
|
} |
|
AuTraceErrPtr(dentry); |
|
return dentry; |
|
} |
|
|
|
static struct dentry *decode_by_dir_ino(struct super_block *sb, ino_t ino, |
|
ino_t dir_ino, |
|
struct au_nfsd_si_lock *nsi_lock) |
|
{ |
|
struct dentry *dentry; |
|
struct path path; |
|
|
|
if (dir_ino != AUFS_ROOT_INO) { |
|
path.dentry = decode_by_ino(sb, dir_ino, 0); |
|
dentry = path.dentry; |
|
if (!path.dentry || IS_ERR(path.dentry)) |
|
goto out; |
|
AuDebugOn(au_test_anon(path.dentry)); |
|
} else |
|
path.dentry = dget(sb->s_root); |
|
|
|
path.mnt = au_mnt_get(sb); |
|
dentry = au_lkup_by_ino(&path, ino, nsi_lock); |
|
path_put(&path); |
|
|
|
out: |
|
AuTraceErrPtr(dentry); |
|
return dentry; |
|
} |
|
|
|
/* ---------------------------------------------------------------------- */ |
|
|
|
static int h_acceptable(void *expv, struct dentry *dentry) |
|
{ |
|
return 1; |
|
} |
|
|
|
static char *au_build_path(struct dentry *h_parent, struct path *h_rootpath, |
|
char *buf, int len, struct super_block *sb) |
|
{ |
|
char *p; |
|
int n; |
|
struct path path; |
|
|
|
p = d_path(h_rootpath, buf, len); |
|
if (IS_ERR(p)) |
|
goto out; |
|
n = strlen(p); |
|
|
|
path.mnt = h_rootpath->mnt; |
|
path.dentry = h_parent; |
|
p = d_path(&path, buf, len); |
|
if (IS_ERR(p)) |
|
goto out; |
|
if (n != 1) |
|
p += n; |
|
|
|
path.mnt = au_mnt_get(sb); |
|
path.dentry = sb->s_root; |
|
p = d_path(&path, buf, len - strlen(p)); |
|
mntput(path.mnt); |
|
if (IS_ERR(p)) |
|
goto out; |
|
if (n != 1) |
|
p[strlen(p)] = '/'; |
|
|
|
out: |
|
AuTraceErrPtr(p); |
|
return p; |
|
} |
|
|
|
static |
|
struct dentry *decode_by_path(struct super_block *sb, ino_t ino, __u32 *fh, |
|
int fh_len, struct au_nfsd_si_lock *nsi_lock) |
|
{ |
|
struct dentry *dentry, *h_parent, *root; |
|
struct super_block *h_sb; |
|
char *pathname, *p; |
|
struct vfsmount *h_mnt; |
|
struct au_branch *br; |
|
int err; |
|
struct path path; |
|
|
|
br = au_sbr(sb, nsi_lock->bindex); |
|
h_mnt = au_br_mnt(br); |
|
h_sb = h_mnt->mnt_sb; |
|
/* todo: call lower fh_to_dentry()? fh_to_parent()? */ |
|
lockdep_off(); |
|
h_parent = exportfs_decode_fh(h_mnt, (void *)(fh + Fh_tail), |
|
fh_len - Fh_tail, fh[Fh_h_type], |
|
h_acceptable, /*context*/NULL); |
|
lockdep_on(); |
|
dentry = h_parent; |
|
if (unlikely(!h_parent || IS_ERR(h_parent))) { |
|
AuWarn1("%s decode_fh failed, %ld\n", |
|
au_sbtype(h_sb), PTR_ERR(h_parent)); |
|
goto out; |
|
} |
|
dentry = NULL; |
|
if (unlikely(au_test_anon(h_parent))) { |
|
AuWarn1("%s decode_fh returned a disconnected dentry\n", |
|
au_sbtype(h_sb)); |
|
goto out_h_parent; |
|
} |
|
|
|
dentry = ERR_PTR(-ENOMEM); |
|
pathname = (void *)__get_free_page(GFP_NOFS); |
|
if (unlikely(!pathname)) |
|
goto out_h_parent; |
|
|
|
root = sb->s_root; |
|
path.mnt = h_mnt; |
|
di_read_lock_parent(root, !AuLock_IR); |
|
path.dentry = au_h_dptr(root, nsi_lock->bindex); |
|
di_read_unlock(root, !AuLock_IR); |
|
p = au_build_path(h_parent, &path, pathname, PAGE_SIZE, sb); |
|
dentry = (void *)p; |
|
if (IS_ERR(p)) |
|
goto out_pathname; |
|
|
|
si_read_unlock(sb); |
|
err = vfsub_kern_path(p, LOOKUP_FOLLOW | LOOKUP_DIRECTORY, &path); |
|
dentry = ERR_PTR(err); |
|
if (unlikely(err)) |
|
goto out_relock; |
|
|
|
dentry = ERR_PTR(-ENOENT); |
|
AuDebugOn(au_test_anon(path.dentry)); |
|
if (unlikely(d_really_is_negative(path.dentry))) |
|
goto out_path; |
|
|
|
if (ino != d_inode(path.dentry)->i_ino) |
|
dentry = au_lkup_by_ino(&path, ino, /*nsi_lock*/NULL); |
|
else |
|
dentry = dget(path.dentry); |
|
|
|
out_path: |
|
path_put(&path); |
|
out_relock: |
|
if (unlikely(si_nfsd_read_lock(sb, nsi_lock) < 0)) |
|
if (!IS_ERR(dentry)) { |
|
dput(dentry); |
|
dentry = ERR_PTR(-ESTALE); |
|
} |
|
out_pathname: |
|
free_page((unsigned long)pathname); |
|
out_h_parent: |
|
dput(h_parent); |
|
out: |
|
AuTraceErrPtr(dentry); |
|
return dentry; |
|
} |
|
|
|
/* ---------------------------------------------------------------------- */ |
|
|
|
static struct dentry * |
|
aufs_fh_to_dentry(struct super_block *sb, struct fid *fid, int fh_len, |
|
int fh_type) |
|
{ |
|
struct dentry *dentry; |
|
__u32 *fh = fid->raw; |
|
struct au_branch *br; |
|
ino_t ino, dir_ino; |
|
struct au_nfsd_si_lock nsi_lock = { |
|
.force_lock = 0 |
|
}; |
|
|
|
dentry = ERR_PTR(-ESTALE); |
|
/* it should never happen, but the file handle is unreliable */ |
|
if (unlikely(fh_len < Fh_tail)) |
|
goto out; |
|
nsi_lock.sigen = fh[Fh_sigen]; |
|
nsi_lock.br_id = fh[Fh_br_id]; |
|
|
|
/* branch id may be wrapped around */ |
|
br = NULL; |
|
if (unlikely(si_nfsd_read_lock(sb, &nsi_lock))) |
|
goto out; |
|
nsi_lock.force_lock = 1; |
|
|
|
/* is this inode still cached? */ |
|
ino = decode_ino(fh + Fh_ino); |
|
/* it should never happen */ |
|
if (unlikely(ino == AUFS_ROOT_INO)) |
|
goto out_unlock; |
|
|
|
dir_ino = decode_ino(fh + Fh_dir_ino); |
|
dentry = decode_by_ino(sb, ino, dir_ino); |
|
if (IS_ERR(dentry)) |
|
goto out_unlock; |
|
if (dentry) |
|
goto accept; |
|
|
|
/* is the parent dir cached? */ |
|
br = au_sbr(sb, nsi_lock.bindex); |
|
au_lcnt_inc(&br->br_nfiles); |
|
dentry = decode_by_dir_ino(sb, ino, dir_ino, &nsi_lock); |
|
if (IS_ERR(dentry)) |
|
goto out_unlock; |
|
if (dentry) |
|
goto accept; |
|
|
|
/* lookup path */ |
|
dentry = decode_by_path(sb, ino, fh, fh_len, &nsi_lock); |
|
if (IS_ERR(dentry)) |
|
goto out_unlock; |
|
if (unlikely(!dentry)) |
|
/* todo?: make it ESTALE */ |
|
goto out_unlock; |
|
|
|
accept: |
|
if (!au_digen_test(dentry, au_sigen(sb)) |
|
&& d_inode(dentry)->i_generation == fh[Fh_igen]) |
|
goto out_unlock; /* success */ |
|
|
|
dput(dentry); |
|
dentry = ERR_PTR(-ESTALE); |
|
out_unlock: |
|
if (br) |
|
au_lcnt_dec(&br->br_nfiles); |
|
si_read_unlock(sb); |
|
out: |
|
AuTraceErrPtr(dentry); |
|
return dentry; |
|
} |
|
|
|
#if 0 /* reserved for future use */ |
|
/* support subtreecheck option */ |
|
static struct dentry *aufs_fh_to_parent(struct super_block *sb, struct fid *fid, |
|
int fh_len, int fh_type) |
|
{ |
|
struct dentry *parent; |
|
__u32 *fh = fid->raw; |
|
ino_t dir_ino; |
|
|
|
dir_ino = decode_ino(fh + Fh_dir_ino); |
|
parent = decode_by_ino(sb, dir_ino, 0); |
|
if (IS_ERR(parent)) |
|
goto out; |
|
if (!parent) |
|
parent = decode_by_path(sb, au_br_index(sb, fh[Fh_br_id]), |
|
dir_ino, fh, fh_len); |
|
|
|
out: |
|
AuTraceErrPtr(parent); |
|
return parent; |
|
} |
|
#endif |
|
|
|
/* ---------------------------------------------------------------------- */ |
|
|
|
static int aufs_encode_fh(struct inode *inode, __u32 *fh, int *max_len, |
|
struct inode *dir) |
|
{ |
|
int err; |
|
aufs_bindex_t bindex; |
|
struct super_block *sb, *h_sb; |
|
struct dentry *dentry, *parent, *h_parent; |
|
struct inode *h_dir; |
|
struct au_branch *br; |
|
|
|
err = -ENOSPC; |
|
if (unlikely(*max_len <= Fh_tail)) { |
|
AuWarn1("NFSv2 client (max_len %d)?\n", *max_len); |
|
goto out; |
|
} |
|
|
|
err = FILEID_ROOT; |
|
if (inode->i_ino == AUFS_ROOT_INO) { |
|
AuDebugOn(inode->i_ino != AUFS_ROOT_INO); |
|
goto out; |
|
} |
|
|
|
h_parent = NULL; |
|
sb = inode->i_sb; |
|
err = si_read_lock(sb, AuLock_FLUSH); |
|
if (unlikely(err)) |
|
goto out; |
|
|
|
#ifdef CONFIG_AUFS_DEBUG |
|
if (unlikely(!au_opt_test(au_mntflags(sb), XINO))) |
|
AuWarn1("NFS-exporting requires xino\n"); |
|
#endif |
|
err = -EIO; |
|
parent = NULL; |
|
ii_read_lock_child(inode); |
|
bindex = au_ibtop(inode); |
|
if (!dir) { |
|
dentry = d_find_any_alias(inode); |
|
if (unlikely(!dentry)) |
|
goto out_unlock; |
|
AuDebugOn(au_test_anon(dentry)); |
|
parent = dget_parent(dentry); |
|
dput(dentry); |
|
if (unlikely(!parent)) |
|
goto out_unlock; |
|
if (d_really_is_positive(parent)) |
|
dir = d_inode(parent); |
|
} |
|
|
|
ii_read_lock_parent(dir); |
|
h_dir = au_h_iptr(dir, bindex); |
|
ii_read_unlock(dir); |
|
if (unlikely(!h_dir)) |
|
goto out_parent; |
|
h_parent = d_find_any_alias(h_dir); |
|
if (unlikely(!h_parent)) |
|
goto out_hparent; |
|
|
|
err = -EPERM; |
|
br = au_sbr(sb, bindex); |
|
h_sb = au_br_sb(br); |
|
if (unlikely(!h_sb->s_export_op)) { |
|
AuErr1("%s branch is not exportable\n", au_sbtype(h_sb)); |
|
goto out_hparent; |
|
} |
|
|
|
fh[Fh_br_id] = br->br_id; |
|
fh[Fh_sigen] = au_sigen(sb); |
|
encode_ino(fh + Fh_ino, inode->i_ino); |
|
encode_ino(fh + Fh_dir_ino, dir->i_ino); |
|
fh[Fh_igen] = inode->i_generation; |
|
|
|
*max_len -= Fh_tail; |
|
fh[Fh_h_type] = exportfs_encode_fh(h_parent, (void *)(fh + Fh_tail), |
|
max_len, |
|
/*connectable or subtreecheck*/0); |
|
err = fh[Fh_h_type]; |
|
*max_len += Fh_tail; |
|
/* todo: macros? */ |
|
if (err != FILEID_INVALID) |
|
err = 99; |
|
else |
|
AuWarn1("%s encode_fh failed\n", au_sbtype(h_sb)); |
|
|
|
out_hparent: |
|
dput(h_parent); |
|
out_parent: |
|
dput(parent); |
|
out_unlock: |
|
ii_read_unlock(inode); |
|
si_read_unlock(sb); |
|
out: |
|
if (unlikely(err < 0)) |
|
err = FILEID_INVALID; |
|
return err; |
|
} |
|
|
|
/* ---------------------------------------------------------------------- */ |
|
|
|
static int aufs_commit_metadata(struct inode *inode) |
|
{ |
|
int err; |
|
aufs_bindex_t bindex; |
|
struct super_block *sb; |
|
struct inode *h_inode; |
|
int (*f)(struct inode *inode); |
|
|
|
sb = inode->i_sb; |
|
si_read_lock(sb, AuLock_FLUSH | AuLock_NOPLMW); |
|
ii_write_lock_child(inode); |
|
bindex = au_ibtop(inode); |
|
AuDebugOn(bindex < 0); |
|
h_inode = au_h_iptr(inode, bindex); |
|
|
|
f = h_inode->i_sb->s_export_op->commit_metadata; |
|
if (f) |
|
err = f(h_inode); |
|
else { |
|
struct writeback_control wbc = { |
|
.sync_mode = WB_SYNC_ALL, |
|
.nr_to_write = 0 /* metadata only */ |
|
}; |
|
|
|
err = sync_inode(h_inode, &wbc); |
|
} |
|
|
|
au_cpup_attr_timesizes(inode); |
|
ii_write_unlock(inode); |
|
si_read_unlock(sb); |
|
return err; |
|
} |
|
|
|
/* ---------------------------------------------------------------------- */ |
|
|
|
static struct export_operations aufs_export_op = { |
|
.fh_to_dentry = aufs_fh_to_dentry, |
|
/* .fh_to_parent = aufs_fh_to_parent, */ |
|
.encode_fh = aufs_encode_fh, |
|
.commit_metadata = aufs_commit_metadata |
|
}; |
|
|
|
void au_export_init(struct super_block *sb) |
|
{ |
|
struct au_sbinfo *sbinfo; |
|
__u32 u; |
|
|
|
BUILD_BUG_ON_MSG(IS_BUILTIN(CONFIG_AUFS_FS) |
|
&& IS_MODULE(CONFIG_EXPORTFS), |
|
AUFS_NAME ": unsupported configuration " |
|
"CONFIG_EXPORTFS=m and CONFIG_AUFS_FS=y"); |
|
|
|
sb->s_export_op = &aufs_export_op; |
|
sbinfo = au_sbi(sb); |
|
sbinfo->si_xigen = NULL; |
|
get_random_bytes(&u, sizeof(u)); |
|
BUILD_BUG_ON(sizeof(u) != sizeof(int)); |
|
atomic_set(&sbinfo->si_xigen_next, u); |
|
}
|
|
|