forked from 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.
529 lines
12 KiB
529 lines
12 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/>. |
|
*/ |
|
|
|
/* |
|
* inode functions |
|
*/ |
|
|
|
#include <linux/iversion.h> |
|
#include "aufs.h" |
|
|
|
struct inode *au_igrab(struct inode *inode) |
|
{ |
|
if (inode) { |
|
AuDebugOn(!atomic_read(&inode->i_count)); |
|
ihold(inode); |
|
} |
|
return inode; |
|
} |
|
|
|
static void au_refresh_hinode_attr(struct inode *inode, int do_version) |
|
{ |
|
au_cpup_attr_all(inode, /*force*/0); |
|
au_update_iigen(inode, /*half*/1); |
|
if (do_version) |
|
inode_inc_iversion(inode); |
|
} |
|
|
|
static int au_ii_refresh(struct inode *inode, int *update) |
|
{ |
|
int err, e, nbr; |
|
umode_t type; |
|
aufs_bindex_t bindex, new_bindex; |
|
struct super_block *sb; |
|
struct au_iinfo *iinfo; |
|
struct au_hinode *p, *q, tmp; |
|
|
|
AuDebugOn(au_is_bad_inode(inode)); |
|
IiMustWriteLock(inode); |
|
|
|
*update = 0; |
|
sb = inode->i_sb; |
|
nbr = au_sbbot(sb) + 1; |
|
type = inode->i_mode & S_IFMT; |
|
iinfo = au_ii(inode); |
|
err = au_hinode_realloc(iinfo, nbr, /*may_shrink*/0); |
|
if (unlikely(err)) |
|
goto out; |
|
|
|
AuDebugOn(iinfo->ii_btop < 0); |
|
p = au_hinode(iinfo, iinfo->ii_btop); |
|
for (bindex = iinfo->ii_btop; bindex <= iinfo->ii_bbot; |
|
bindex++, p++) { |
|
if (!p->hi_inode) |
|
continue; |
|
|
|
AuDebugOn(type != (p->hi_inode->i_mode & S_IFMT)); |
|
new_bindex = au_br_index(sb, p->hi_id); |
|
if (new_bindex == bindex) |
|
continue; |
|
|
|
if (new_bindex < 0) { |
|
*update = 1; |
|
au_hiput(p); |
|
p->hi_inode = NULL; |
|
continue; |
|
} |
|
|
|
if (new_bindex < iinfo->ii_btop) |
|
iinfo->ii_btop = new_bindex; |
|
if (iinfo->ii_bbot < new_bindex) |
|
iinfo->ii_bbot = new_bindex; |
|
/* swap two lower inode, and loop again */ |
|
q = au_hinode(iinfo, new_bindex); |
|
tmp = *q; |
|
*q = *p; |
|
*p = tmp; |
|
if (tmp.hi_inode) { |
|
bindex--; |
|
p--; |
|
} |
|
} |
|
au_update_ibrange(inode, /*do_put_zero*/0); |
|
au_hinode_realloc(iinfo, nbr, /*may_shrink*/1); /* harmless if err */ |
|
e = au_dy_irefresh(inode); |
|
if (unlikely(e && !err)) |
|
err = e; |
|
|
|
out: |
|
AuTraceErr(err); |
|
return err; |
|
} |
|
|
|
void au_refresh_iop(struct inode *inode, int force_getattr) |
|
{ |
|
int type; |
|
struct au_sbinfo *sbi = au_sbi(inode->i_sb); |
|
const struct inode_operations *iop |
|
= force_getattr ? aufs_iop : sbi->si_iop_array; |
|
|
|
if (inode->i_op == iop) |
|
return; |
|
|
|
switch (inode->i_mode & S_IFMT) { |
|
case S_IFDIR: |
|
type = AuIop_DIR; |
|
break; |
|
case S_IFLNK: |
|
type = AuIop_SYMLINK; |
|
break; |
|
default: |
|
type = AuIop_OTHER; |
|
break; |
|
} |
|
|
|
inode->i_op = iop + type; |
|
/* unnecessary smp_wmb() */ |
|
} |
|
|
|
int au_refresh_hinode_self(struct inode *inode) |
|
{ |
|
int err, update; |
|
|
|
err = au_ii_refresh(inode, &update); |
|
if (!err) |
|
au_refresh_hinode_attr(inode, update && S_ISDIR(inode->i_mode)); |
|
|
|
AuTraceErr(err); |
|
return err; |
|
} |
|
|
|
int au_refresh_hinode(struct inode *inode, struct dentry *dentry) |
|
{ |
|
int err, e, update; |
|
unsigned int flags; |
|
umode_t mode; |
|
aufs_bindex_t bindex, bbot; |
|
unsigned char isdir; |
|
struct au_hinode *p; |
|
struct au_iinfo *iinfo; |
|
|
|
err = au_ii_refresh(inode, &update); |
|
if (unlikely(err)) |
|
goto out; |
|
|
|
update = 0; |
|
iinfo = au_ii(inode); |
|
p = au_hinode(iinfo, iinfo->ii_btop); |
|
mode = (inode->i_mode & S_IFMT); |
|
isdir = S_ISDIR(mode); |
|
flags = au_hi_flags(inode, isdir); |
|
bbot = au_dbbot(dentry); |
|
for (bindex = au_dbtop(dentry); bindex <= bbot; bindex++) { |
|
struct inode *h_i, *h_inode; |
|
struct dentry *h_d; |
|
|
|
h_d = au_h_dptr(dentry, bindex); |
|
if (!h_d || d_is_negative(h_d)) |
|
continue; |
|
|
|
h_inode = d_inode(h_d); |
|
AuDebugOn(mode != (h_inode->i_mode & S_IFMT)); |
|
if (iinfo->ii_btop <= bindex && bindex <= iinfo->ii_bbot) { |
|
h_i = au_h_iptr(inode, bindex); |
|
if (h_i) { |
|
if (h_i == h_inode) |
|
continue; |
|
err = -EIO; |
|
break; |
|
} |
|
} |
|
if (bindex < iinfo->ii_btop) |
|
iinfo->ii_btop = bindex; |
|
if (iinfo->ii_bbot < bindex) |
|
iinfo->ii_bbot = bindex; |
|
au_set_h_iptr(inode, bindex, au_igrab(h_inode), flags); |
|
update = 1; |
|
} |
|
au_update_ibrange(inode, /*do_put_zero*/0); |
|
e = au_dy_irefresh(inode); |
|
if (unlikely(e && !err)) |
|
err = e; |
|
if (!err) |
|
au_refresh_hinode_attr(inode, update && isdir); |
|
|
|
out: |
|
AuTraceErr(err); |
|
return err; |
|
} |
|
|
|
static int set_inode(struct inode *inode, struct dentry *dentry) |
|
{ |
|
int err; |
|
unsigned int flags; |
|
umode_t mode; |
|
aufs_bindex_t bindex, btop, btail; |
|
unsigned char isdir; |
|
struct dentry *h_dentry; |
|
struct inode *h_inode; |
|
struct au_iinfo *iinfo; |
|
const struct inode_operations *iop; |
|
|
|
IiMustWriteLock(inode); |
|
|
|
err = 0; |
|
isdir = 0; |
|
iop = au_sbi(inode->i_sb)->si_iop_array; |
|
btop = au_dbtop(dentry); |
|
h_dentry = au_h_dptr(dentry, btop); |
|
h_inode = d_inode(h_dentry); |
|
mode = h_inode->i_mode; |
|
switch (mode & S_IFMT) { |
|
case S_IFREG: |
|
btail = au_dbtail(dentry); |
|
inode->i_op = iop + AuIop_OTHER; |
|
inode->i_fop = &aufs_file_fop; |
|
err = au_dy_iaop(inode, btop, h_inode); |
|
if (unlikely(err)) |
|
goto out; |
|
break; |
|
case S_IFDIR: |
|
isdir = 1; |
|
btail = au_dbtaildir(dentry); |
|
inode->i_op = iop + AuIop_DIR; |
|
inode->i_fop = &aufs_dir_fop; |
|
break; |
|
case S_IFLNK: |
|
btail = au_dbtail(dentry); |
|
inode->i_op = iop + AuIop_SYMLINK; |
|
break; |
|
case S_IFBLK: |
|
case S_IFCHR: |
|
case S_IFIFO: |
|
case S_IFSOCK: |
|
btail = au_dbtail(dentry); |
|
inode->i_op = iop + AuIop_OTHER; |
|
init_special_inode(inode, mode, h_inode->i_rdev); |
|
break; |
|
default: |
|
AuIOErr("Unknown file type 0%o\n", mode); |
|
err = -EIO; |
|
goto out; |
|
} |
|
|
|
/* do not set hnotify for whiteouted dirs (SHWH mode) */ |
|
flags = au_hi_flags(inode, isdir); |
|
if (au_opt_test(au_mntflags(dentry->d_sb), SHWH) |
|
&& au_ftest_hi(flags, HNOTIFY) |
|
&& dentry->d_name.len > AUFS_WH_PFX_LEN |
|
&& !memcmp(dentry->d_name.name, AUFS_WH_PFX, AUFS_WH_PFX_LEN)) |
|
au_fclr_hi(flags, HNOTIFY); |
|
iinfo = au_ii(inode); |
|
iinfo->ii_btop = btop; |
|
iinfo->ii_bbot = btail; |
|
for (bindex = btop; bindex <= btail; bindex++) { |
|
h_dentry = au_h_dptr(dentry, bindex); |
|
if (h_dentry) |
|
au_set_h_iptr(inode, bindex, |
|
au_igrab(d_inode(h_dentry)), flags); |
|
} |
|
au_cpup_attr_all(inode, /*force*/1); |
|
/* |
|
* to force calling aufs_get_acl() every time, |
|
* do not call cache_no_acl() for aufs inode. |
|
*/ |
|
|
|
out: |
|
return err; |
|
} |
|
|
|
/* |
|
* successful returns with iinfo write_locked |
|
* minus: errno |
|
* zero: success, matched |
|
* plus: no error, but unmatched |
|
*/ |
|
static int reval_inode(struct inode *inode, struct dentry *dentry) |
|
{ |
|
int err; |
|
unsigned int gen, igflags; |
|
aufs_bindex_t bindex, bbot; |
|
struct inode *h_inode, *h_dinode; |
|
struct dentry *h_dentry; |
|
|
|
/* |
|
* before this function, if aufs got any iinfo lock, it must be only |
|
* one, the parent dir. |
|
* it can happen by UDBA and the obsoleted inode number. |
|
*/ |
|
err = -EIO; |
|
if (unlikely(inode->i_ino == parent_ino(dentry))) |
|
goto out; |
|
|
|
err = 1; |
|
ii_write_lock_new_child(inode); |
|
h_dentry = au_h_dptr(dentry, au_dbtop(dentry)); |
|
h_dinode = d_inode(h_dentry); |
|
bbot = au_ibbot(inode); |
|
for (bindex = au_ibtop(inode); bindex <= bbot; bindex++) { |
|
h_inode = au_h_iptr(inode, bindex); |
|
if (!h_inode || h_inode != h_dinode) |
|
continue; |
|
|
|
err = 0; |
|
gen = au_iigen(inode, &igflags); |
|
if (gen == au_digen(dentry) |
|
&& !au_ig_ftest(igflags, HALF_REFRESHED)) |
|
break; |
|
|
|
/* fully refresh inode using dentry */ |
|
err = au_refresh_hinode(inode, dentry); |
|
if (!err) |
|
au_update_iigen(inode, /*half*/0); |
|
break; |
|
} |
|
|
|
if (unlikely(err)) |
|
ii_write_unlock(inode); |
|
out: |
|
return err; |
|
} |
|
|
|
int au_ino(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino, |
|
unsigned int d_type, ino_t *ino) |
|
{ |
|
int err, idx; |
|
const int isnondir = d_type != DT_DIR; |
|
|
|
/* prevent hardlinked inode number from race condition */ |
|
if (isnondir) { |
|
err = au_xinondir_enter(sb, bindex, h_ino, &idx); |
|
if (unlikely(err)) |
|
goto out; |
|
} |
|
|
|
err = au_xino_read(sb, bindex, h_ino, ino); |
|
if (unlikely(err)) |
|
goto out_xinondir; |
|
|
|
if (!*ino) { |
|
err = -EIO; |
|
*ino = au_xino_new_ino(sb); |
|
if (unlikely(!*ino)) |
|
goto out_xinondir; |
|
err = au_xino_write(sb, bindex, h_ino, *ino); |
|
if (unlikely(err)) |
|
goto out_xinondir; |
|
} |
|
|
|
out_xinondir: |
|
if (isnondir && idx >= 0) |
|
au_xinondir_leave(sb, bindex, h_ino, idx); |
|
out: |
|
return err; |
|
} |
|
|
|
/* successful returns with iinfo write_locked */ |
|
/* todo: return with unlocked? */ |
|
struct inode *au_new_inode(struct dentry *dentry, int must_new) |
|
{ |
|
struct inode *inode, *h_inode; |
|
struct dentry *h_dentry; |
|
struct super_block *sb; |
|
ino_t h_ino, ino; |
|
int err, idx, hlinked; |
|
aufs_bindex_t btop; |
|
|
|
sb = dentry->d_sb; |
|
btop = au_dbtop(dentry); |
|
h_dentry = au_h_dptr(dentry, btop); |
|
h_inode = d_inode(h_dentry); |
|
h_ino = h_inode->i_ino; |
|
hlinked = !d_is_dir(h_dentry) && h_inode->i_nlink > 1; |
|
|
|
new_ino: |
|
/* |
|
* stop 'race'-ing between hardlinks under different |
|
* parents. |
|
*/ |
|
if (hlinked) { |
|
err = au_xinondir_enter(sb, btop, h_ino, &idx); |
|
inode = ERR_PTR(err); |
|
if (unlikely(err)) |
|
goto out; |
|
} |
|
|
|
err = au_xino_read(sb, btop, h_ino, &ino); |
|
inode = ERR_PTR(err); |
|
if (unlikely(err)) |
|
goto out_xinondir; |
|
|
|
if (!ino) { |
|
ino = au_xino_new_ino(sb); |
|
if (unlikely(!ino)) { |
|
inode = ERR_PTR(-EIO); |
|
goto out_xinondir; |
|
} |
|
} |
|
|
|
AuDbg("i%lu\n", (unsigned long)ino); |
|
inode = au_iget_locked(sb, ino); |
|
err = PTR_ERR(inode); |
|
if (IS_ERR(inode)) |
|
goto out_xinondir; |
|
|
|
AuDbg("%lx, new %d\n", inode->i_state, !!(inode->i_state & I_NEW)); |
|
if (inode->i_state & I_NEW) { |
|
ii_write_lock_new_child(inode); |
|
err = set_inode(inode, dentry); |
|
if (!err) { |
|
unlock_new_inode(inode); |
|
goto out_xinondir; /* success */ |
|
} |
|
|
|
/* |
|
* iget_failed() calls iput(), but we need to call |
|
* ii_write_unlock() after iget_failed(). so dirty hack for |
|
* i_count. |
|
*/ |
|
atomic_inc(&inode->i_count); |
|
iget_failed(inode); |
|
ii_write_unlock(inode); |
|
au_xino_write(sb, btop, h_ino, /*ino*/0); |
|
/* ignore this error */ |
|
goto out_iput; |
|
} else if (!must_new && !IS_DEADDIR(inode) && inode->i_nlink) { |
|
/* |
|
* horrible race condition between lookup, readdir and copyup |
|
* (or something). |
|
*/ |
|
if (hlinked && idx >= 0) |
|
au_xinondir_leave(sb, btop, h_ino, idx); |
|
err = reval_inode(inode, dentry); |
|
if (unlikely(err < 0)) { |
|
hlinked = 0; |
|
goto out_iput; |
|
} |
|
if (!err) |
|
goto out; /* success */ |
|
else if (hlinked && idx >= 0) { |
|
err = au_xinondir_enter(sb, btop, h_ino, &idx); |
|
if (unlikely(err)) { |
|
iput(inode); |
|
inode = ERR_PTR(err); |
|
goto out; |
|
} |
|
} |
|
} |
|
|
|
if (unlikely(au_test_fs_unique_ino(h_inode))) |
|
AuWarn1("Warning: Un-notified UDBA or repeatedly renamed dir," |
|
" b%d, %s, %pd, hi%lu, i%lu.\n", |
|
btop, au_sbtype(h_dentry->d_sb), dentry, |
|
(unsigned long)h_ino, (unsigned long)ino); |
|
ino = 0; |
|
err = au_xino_write(sb, btop, h_ino, /*ino*/0); |
|
if (!err) { |
|
iput(inode); |
|
if (hlinked && idx >= 0) |
|
au_xinondir_leave(sb, btop, h_ino, idx); |
|
goto new_ino; |
|
} |
|
|
|
out_iput: |
|
iput(inode); |
|
inode = ERR_PTR(err); |
|
out_xinondir: |
|
if (hlinked && idx >= 0) |
|
au_xinondir_leave(sb, btop, h_ino, idx); |
|
out: |
|
return inode; |
|
} |
|
|
|
/* ---------------------------------------------------------------------- */ |
|
|
|
int au_test_ro(struct super_block *sb, aufs_bindex_t bindex, |
|
struct inode *inode) |
|
{ |
|
int err; |
|
struct inode *hi; |
|
|
|
err = au_br_rdonly(au_sbr(sb, bindex)); |
|
|
|
/* pseudo-link after flushed may happen out of bounds */ |
|
if (!err |
|
&& inode |
|
&& au_ibtop(inode) <= bindex |
|
&& bindex <= au_ibbot(inode)) { |
|
/* |
|
* permission check is unnecessary since vfsub routine |
|
* will be called later |
|
*/ |
|
hi = au_h_iptr(inode, bindex); |
|
if (hi) |
|
err = IS_IMMUTABLE(hi) ? -EROFS : 0; |
|
} |
|
|
|
return err; |
|
} |
|
|
|
int au_test_h_perm(struct inode *h_inode, int mask) |
|
{ |
|
if (uid_eq(current_fsuid(), GLOBAL_ROOT_UID)) |
|
return 0; |
|
return inode_permission(h_inode, mask); |
|
} |
|
|
|
int au_test_h_perm_sio(struct inode *h_inode, int mask) |
|
{ |
|
if (au_test_nfs(h_inode->i_sb) |
|
&& (mask & MAY_WRITE) |
|
&& S_ISDIR(h_inode->i_mode)) |
|
mask |= MAY_READ; /* force permission check */ |
|
return au_test_h_perm(h_inode, mask); |
|
}
|
|
|