static int au_wbr_fd(struct path *path) { int err, fd; aufs_bindex_t wbi, bindex, bend; struct file *h_file; struct super_block *sb; struct dentry *root; struct au_branch *wbr; err = get_unused_fd(); if (unlikely(err < 0)) goto out; fd = err; wbi = 0; sb = path->dentry->d_sb; root = sb->s_root; aufs_read_lock(root, AuLock_IR); wbr = au_sbr(sb, wbi); if (!(path->mnt->mnt_flags & MNT_READONLY) && !au_br_writable(wbr->br_perm)) { bend = au_sbend(sb); for (bindex = 1; bindex <= bend; bindex++) { wbr = au_sbr(sb, bindex); if (au_br_writable(wbr->br_perm)) { wbi = bindex; break; } } wbr = au_sbr(sb, wbi); } AuDbg("wbi %d\n", wbi); h_file = au_h_open(root, wbi, O_RDONLY | O_DIRECTORY | O_LARGEFILE, NULL); aufs_read_unlock(root, AuLock_IR); err = PTR_ERR(h_file); if (IS_ERR(h_file)) goto out_fd; atomic_dec(&wbr->br_count); /* cf. au_h_open() */ fd_install(fd, h_file); err = fd; goto out; /* success */ out_fd: put_unused_fd(fd); out: return err; }
static ssize_t aufs_aio_write_sp(struct kiocb *kio, const struct iovec *iov, unsigned long nv, loff_t pos) { ssize_t err; aufs_bindex_t bstart; unsigned char wbr; struct super_block *sb; struct file *file, *h_file; file = kio->ki_filp; sb = file->f_dentry->d_sb; si_read_lock(sb, AuLock_FLUSH); fi_read_lock(file); bstart = au_fbstart(file); h_file = au_hf_top(file); fi_read_unlock(file); wbr = !!au_br_writable(au_sbr(sb, bstart)->br_perm); si_read_unlock(sb); /* do not change the file in kio */ AuDebugOn(!h_file->f_op || !h_file->f_op->aio_write); err = h_file->f_op->aio_write(kio, iov, nv, pos); if (err > 0 && wbr) file_update_time(h_file); return err; }
static void au_do_dir_ts(void *arg) { struct au_dir_ts_arg *a = arg; struct au_dtime dt; struct path h_path; struct inode *dir, *h_dir; struct super_block *sb; struct au_branch *br; struct au_hinode *hdir; int err; aufs_bindex_t btop, bindex; sb = a->dentry->d_sb; if (d_really_is_negative(a->dentry)) goto out; /* no dir->i_mutex lock */ aufs_read_lock(a->dentry, AuLock_DW); /* noflush */ dir = d_inode(a->dentry); btop = au_ibtop(dir); bindex = au_br_index(sb, a->brid); if (bindex < btop) goto out_unlock; br = au_sbr(sb, bindex); h_path.dentry = au_h_dptr(a->dentry, bindex); if (!h_path.dentry) goto out_unlock; h_path.mnt = au_br_mnt(br); au_dtime_store(&dt, a->dentry, &h_path); br = au_sbr(sb, btop); if (!au_br_writable(br->br_perm)) goto out_unlock; h_path.dentry = au_h_dptr(a->dentry, btop); h_path.mnt = au_br_mnt(br); err = vfsub_mnt_want_write(h_path.mnt); if (err) goto out_unlock; hdir = au_hi(dir, btop); au_hn_inode_lock_nested(hdir, AuLsc_I_PARENT); h_dir = au_h_iptr(dir, btop); if (h_dir->i_nlink && timespec_compare(&h_dir->i_mtime, &dt.dt_mtime) < 0) { dt.dt_h_path = h_path; au_dtime_revert(&dt); } au_hn_inode_unlock(hdir); vfsub_mnt_drop_write(h_path.mnt); au_cpup_attr_timesizes(dir); out_unlock: aufs_read_unlock(a->dentry, AuLock_DW); out: dput(a->dentry); au_nwt_done(&au_sbi(sb)->si_nowait); kfree(arg); }
static int h_permission(struct inode *h_inode, int mask, struct vfsmount *h_mnt, int brperm) { int err; const unsigned char write_mask = !!(mask & (MAY_WRITE | MAY_APPEND)); err = -EACCES; if ((write_mask && IS_IMMUTABLE(h_inode)) || ((mask & MAY_EXEC) && S_ISREG(h_inode->i_mode) && ((h_mnt->mnt_flags & MNT_NOEXEC) || !(h_inode->i_mode & S_IXUGO)))) goto out; /* * - skip the lower fs test in the case of write to ro branch. * - nfs dir permission write check is optimized, but a policy for * link/rename requires a real check. * - nfs always sets MS_POSIXACL regardless its mount option 'noacl.' * in this case, generic_permission() returns -EOPNOTSUPP. */ if ((write_mask && !au_br_writable(brperm)) || (au_test_nfs(h_inode->i_sb) && S_ISDIR(h_inode->i_mode) && write_mask && !(mask & MAY_READ)) || !h_inode->i_op->permission) { /* AuLabel(generic_permission); */ /* AuDbg("get_acl %pf\n", h_inode->i_op->get_acl); */ err = generic_permission(h_inode, mask); if (err == -EOPNOTSUPP && au_test_nfs_noacl(h_inode)) err = h_inode->i_op->permission(h_inode, mask); AuTraceErr(err); } else { /* AuLabel(h_inode->permission); */ err = h_inode->i_op->permission(h_inode, mask); AuTraceErr(err); } if (!err) err = devcgroup_inode_permission(h_inode, mask); if (!err) err = security_inode_permission(h_inode, mask); #if 0 if (!err) { /* todo: do we need to call ima_path_check()? */ struct path h_path = { .dentry = .mnt = h_mnt }; err = ima_path_check(&h_path, mask & (MAY_READ | MAY_WRITE | MAY_EXEC), IMA_COUNT_LEAVE); } #endif out: return err; }
/* * test if the branch permission is legal or not. */ static int test_br(struct inode *inode, int brperm, char *path) { int err; err = (au_br_writable(brperm) && IS_RDONLY(inode)); if (!err) goto out; err = -EINVAL; pr_err("write permission for readonly mount or inode, %s\n", path); out: return err; }
/* * returns a newly allocated branch. @new_nbranch is a number of branches * after adding a branch. */ static struct au_branch *au_br_alloc(struct super_block *sb, int new_nbranch, int perm) { struct au_branch *add_branch; struct dentry *root; int err; err = -ENOMEM; root = sb->s_root; add_branch = kmalloc(sizeof(*add_branch), GFP_NOFS); if (unlikely(!add_branch)) goto out; err = au_hnotify_init_br(add_branch, perm); if (unlikely(err)) goto out_br; add_branch->br_wbr = NULL; if (au_br_writable(perm)) { /* may be freed separately at changing the branch permission */ add_branch->br_wbr = kmalloc(sizeof(*add_branch->br_wbr), GFP_NOFS); if (unlikely(!add_branch->br_wbr)) goto out_hnotify; } err = au_sbr_realloc(au_sbi(sb), new_nbranch); if (!err) err = au_di_realloc(au_di(root), new_nbranch); if (!err) err = au_ii_realloc(au_ii(root->d_inode), new_nbranch); if (!err) return add_branch; /* success */ kfree(add_branch->br_wbr); out_hnotify: au_hnotify_fin_br(add_branch); out_br: kfree(add_branch); out: return ERR_PTR(err); }
/* intialize a new branch */ static int au_br_init(struct au_branch *br, struct super_block *sb, struct au_opt_add *add) { int err; err = 0; memset(&br->br_xino, 0, sizeof(br->br_xino)); mutex_init(&br->br_xino.xi_nondir_mtx); br->br_perm = add->perm; br->br_mnt = add->path.mnt; /* set first, mntget() later */ spin_lock_init(&br->br_dykey_lock); memset(br->br_dykey, 0, sizeof(br->br_dykey)); atomic_set(&br->br_count, 0); br->br_xino_upper = AUFS_XINO_TRUNC_INIT; atomic_set(&br->br_xino_running, 0); br->br_id = au_new_br_id(sb); AuDebugOn(br->br_id < 0); if (au_br_writable(add->perm)) { err = au_wbr_init(br, sb, add->perm, &add->path); if (unlikely(err)) goto out_err; } if (au_opt_test(au_mntflags(sb), XINO)) { err = au_xino_br(sb, br, add->path.dentry->d_inode->i_ino, au_sbr(sb, 0)->br_xino.xi_file, /*do_test*/1); if (unlikely(err)) { AuDebugOn(br->br_xino.xi_file); goto out_err; } } sysaufs_br_init(br); mntget(add->path.mnt); goto out; /* success */ out_err: br->br_mnt = NULL; out: return err; }
/* * initialize or clean the whiteouts for an adding branch */ static int au_br_init_wh(struct super_block *sb, struct au_branch *br, int new_perm, struct dentry *h_root) { int err, old_perm; aufs_bindex_t bindex; struct mutex *h_mtx; struct au_wbr *wbr; struct au_hinode *hdir; wbr = br->br_wbr; old_perm = br->br_perm; br->br_perm = new_perm; hdir = NULL; h_mtx = NULL; bindex = au_br_index(sb, br->br_id); if (0 <= bindex) { hdir = au_hi(sb->s_root->d_inode, bindex); au_hn_imtx_lock_nested(hdir, AuLsc_I_PARENT); } else { h_mtx = &h_root->d_inode->i_mutex; mutex_lock_nested(h_mtx, AuLsc_I_PARENT); } if (!wbr) err = au_wh_init(h_root, br, sb); else { wbr_wh_write_lock(wbr); err = au_wh_init(h_root, br, sb); wbr_wh_write_unlock(wbr); } if (hdir) au_hn_imtx_unlock(hdir); else mutex_unlock(h_mtx); br->br_perm = old_perm; if (!err && wbr && !au_br_writable(new_perm)) { kfree(wbr); br->br_wbr = NULL; } return err; }
void au_dir_ts(struct inode *dir, aufs_bindex_t bindex) { int perm, wkq_err; aufs_bindex_t btop; struct au_dir_ts_arg *arg; struct dentry *dentry; struct super_block *sb; IMustLock(dir); dentry = d_find_any_alias(dir); AuDebugOn(!dentry); sb = dentry->d_sb; btop = au_ibtop(dir); if (btop == bindex) { au_cpup_attr_timesizes(dir); goto out; } perm = au_sbr_perm(sb, btop); if (!au_br_writable(perm)) goto out; arg = kmalloc(sizeof(*arg), GFP_NOFS); if (!arg) goto out; arg->dentry = dget(dentry); /* will be dput-ted by au_do_dir_ts() */ arg->brid = au_sbr_id(sb, bindex); wkq_err = au_wkq_nowait(au_do_dir_ts, arg, sb, /*flags*/0); if (unlikely(wkq_err)) { pr_err("wkq %d\n", wkq_err); dput(dentry); kfree(arg); } out: dput(dentry); }
static int au_wbr_fd(struct path *path, struct aufs_wbr_fd __user *arg) { int err, fd; aufs_bindex_t wbi, bindex, bend; struct file *h_file; struct super_block *sb; struct dentry *root; struct au_branch *br; struct aufs_wbr_fd wbrfd = { .oflags = au_dir_roflags, .brid = -1 }; const int valid = O_RDONLY | O_NONBLOCK | O_LARGEFILE | O_DIRECTORY | O_NOATIME | O_CLOEXEC; AuDebugOn(wbrfd.oflags & ~valid); if (arg) { err = copy_from_user(&wbrfd, arg, sizeof(wbrfd)); if (unlikely(err)) { err = -EFAULT; goto out; } err = -EINVAL; AuDbg("wbrfd{0%o, %d}\n", wbrfd.oflags, wbrfd.brid); wbrfd.oflags |= au_dir_roflags; AuDbg("0%o\n", wbrfd.oflags); if (unlikely(wbrfd.oflags & ~valid)) goto out; } fd = get_unused_fd_flags(0); err = fd; if (unlikely(fd < 0)) goto out; h_file = ERR_PTR(-EINVAL); wbi = 0; br = NULL; sb = path->dentry->d_sb; root = sb->s_root; aufs_read_lock(root, AuLock_IR); bend = au_sbend(sb); if (wbrfd.brid >= 0) { wbi = au_br_index(sb, wbrfd.brid); if (unlikely(wbi < 0 || wbi > bend)) goto out_unlock; } h_file = ERR_PTR(-ENOENT); br = au_sbr(sb, wbi); if (!au_br_writable(br->br_perm)) { if (arg) goto out_unlock; bindex = wbi + 1; wbi = -1; for (; bindex <= bend; bindex++) { br = au_sbr(sb, bindex); if (au_br_writable(br->br_perm)) { wbi = bindex; br = au_sbr(sb, wbi); break; } } } AuDbg("wbi %d\n", wbi); if (wbi >= 0) h_file = au_h_open(root, wbi, wbrfd.oflags, NULL, /*force_wr*/0); out_unlock: aufs_read_unlock(root, AuLock_IR); err = PTR_ERR(h_file); if (IS_ERR(h_file)) goto out_fd; atomic_dec(&br->br_count); /* cf. au_h_open() */ fd_install(fd, h_file); err = fd; goto out; /* success */ out_fd: put_unused_fd(fd); out: AuTraceErr(err); return err; } /* ---------------------------------------------------------------------- */ long aufs_ioctl_dir(struct file *file, unsigned int cmd, unsigned long arg) { long err; struct dentry *dentry; switch (cmd) { case AUFS_CTL_RDU: case AUFS_CTL_RDU_INO: err = au_rdu_ioctl(file, cmd, arg); break; case AUFS_CTL_WBR_FD: err = au_wbr_fd(&file->f_path, (void __user *)arg); break; case AUFS_CTL_IBUSY: err = au_ibusy_ioctl(file, arg); break; case AUFS_CTL_BRINFO: err = au_brinfo_ioctl(file, arg); break; case AUFS_CTL_FHSM_FD: dentry = file->f_path.dentry; if (IS_ROOT(dentry)) err = au_fhsm_fd(dentry->d_sb, arg); else err = -ENOTTY; break; default: /* do not call the lower */ AuDbg("0x%x\n", cmd); err = -ENOTTY; } AuTraceErr(err); return err; }
int au_opts_verify(struct super_block *sb, unsigned long sb_flags, unsigned int pending) { int err; aufs_bindex_t bindex, bend; unsigned char do_plink, skip, do_free; struct au_branch *br; struct au_wbr *wbr; struct dentry *root; struct inode *dir, *h_dir; struct au_sbinfo *sbinfo; struct au_hinode *hdir; SiMustAnyLock(sb); sbinfo = au_sbi(sb); AuDebugOn(!(sbinfo->si_mntflags & AuOptMask_UDBA)); if (!(sb_flags & MS_RDONLY)) { if (unlikely(!au_br_writable(au_sbr_perm(sb, 0)))) pr_warn("first branch should be rw\n"); if (unlikely(au_opt_test(sbinfo->si_mntflags, SHWH))) pr_warn("shwh should be used with ro\n"); } if (au_opt_test((sbinfo->si_mntflags | pending), UDBA_HNOTIFY) && !au_opt_test(sbinfo->si_mntflags, XINO)) pr_warn("udba=*notify requires xino\n"); err = 0; root = sb->s_root; dir = root->d_inode; do_plink = !!au_opt_test(sbinfo->si_mntflags, PLINK); bend = au_sbend(sb); for (bindex = 0; !err && bindex <= bend; bindex++) { skip = 0; h_dir = au_h_iptr(dir, bindex); br = au_sbr(sb, bindex); do_free = 0; wbr = br->br_wbr; if (wbr) wbr_wh_read_lock(wbr); if (!au_br_writable(br->br_perm)) { do_free = !!wbr; skip = (!wbr || (!wbr->wbr_whbase && !wbr->wbr_plink && !wbr->wbr_orph)); } else if (!au_br_wh_linkable(br->br_perm)) { /* skip = (!br->br_whbase && !br->br_orph); */ skip = (!wbr || !wbr->wbr_whbase); if (skip && wbr) { if (do_plink) skip = !!wbr->wbr_plink; else skip = !wbr->wbr_plink; } } else { /* skip = (br->br_whbase && br->br_ohph); */ skip = (wbr && wbr->wbr_whbase); if (skip) { if (do_plink) skip = !!wbr->wbr_plink; else skip = !wbr->wbr_plink; } } if (wbr) wbr_wh_read_unlock(wbr); if (skip) continue; hdir = au_hi(dir, bindex); au_hn_imtx_lock_nested(hdir, AuLsc_I_PARENT); if (wbr) wbr_wh_write_lock(wbr); err = au_wh_init(br, sb); if (wbr) wbr_wh_write_unlock(wbr); au_hn_imtx_unlock(hdir); if (!err && do_free) { kfree(wbr); br->br_wbr = NULL; } } return err; }
int au_br_add(struct super_block *sb, struct au_opt_add *add, int remount) { int err; aufs_bindex_t bend, add_bindex; struct dentry *root, *h_dentry; struct inode *root_inode; struct au_branch *add_branch; root = sb->s_root; root_inode = root->d_inode; IMustLock(root_inode); err = test_add(sb, add, remount); if (unlikely(err < 0)) goto out; if (err) { err = 0; goto out; /* success */ } bend = au_sbend(sb); add_branch = au_br_alloc(sb, bend + 2, add->perm); err = PTR_ERR(add_branch); if (IS_ERR(add_branch)) goto out; err = au_br_init(add_branch, sb, add); if (unlikely(err)) { au_br_do_free(add_branch); goto out; } add_bindex = add->bindex; h_dentry = add->path.dentry; if (!remount) au_br_do_add(sb, h_dentry, add_branch, add_bindex); else { sysaufs_brs_del(sb, add_bindex); au_br_do_add(sb, h_dentry, add_branch, add_bindex); sysaufs_brs_add(sb, add_bindex); } if (!add_bindex) { au_cpup_attr_all(root_inode, /*force*/1); sb->s_maxbytes = h_dentry->d_sb->s_maxbytes; } else au_add_nlink(root_inode, h_dentry->d_inode); /* * this test/set prevents aufs from handling unnecesary notify events * of xino files, in case of re-adding a writable branch which was * once detached from aufs. */ if (au_xino_brid(sb) < 0 && au_br_writable(add_branch->br_perm) && !au_test_fs_bad_xino(h_dentry->d_sb) && add_branch->br_xino.xi_file && add_branch->br_xino.xi_file->f_dentry->d_parent == h_dentry) au_xino_brid_set(sb, add_branch->br_id); out: return err; }
int au_opts_verify(struct super_block *sb, unsigned long sb_flags, unsigned int pending) { int err, fhsm; aufs_bindex_t bindex, bend; unsigned char do_plink, skip, do_free; struct au_branch *br; struct au_wbr *wbr; struct dentry *root; struct inode *dir, *h_dir; struct au_sbinfo *sbinfo; struct au_hinode *hdir; SiMustAnyLock(sb); sbinfo = au_sbi(sb); AuDebugOn(!(sbinfo->si_mntflags & AuOptMask_UDBA)); if (!(sb_flags & MS_RDONLY)) { if (unlikely(!au_br_writable(au_sbr_perm(sb, 0)))) pr_warn("first branch should be rw\n"); if (unlikely(au_opt_test(sbinfo->si_mntflags, SHWH))) pr_warn("shwh should be used with ro\n"); } if (au_opt_test((sbinfo->si_mntflags | pending), UDBA_HNOTIFY) && !au_opt_test(sbinfo->si_mntflags, XINO)) pr_warn("udba=*notify requires xino\n"); if (au_opt_test(sbinfo->si_mntflags, DIRPERM1)) pr_warn("dirperm1 breaks the protection" " by the permission bits on the lower branch\n"); err = 0; fhsm = 0; root = sb->s_root; dir = root->d_inode; do_plink = !!au_opt_test(sbinfo->si_mntflags, PLINK); bend = au_sbend(sb); for (bindex = 0; !err && bindex <= bend; bindex++) { skip = 0; h_dir = au_h_iptr(dir, bindex); br = au_sbr(sb, bindex); if ((br->br_perm & AuBrAttr_ICEX) && !h_dir->i_op->listxattr) br->br_perm &= ~AuBrAttr_ICEX; #if 0 if ((br->br_perm & AuBrAttr_ICEX_SEC) && (au_br_sb(br)->s_flags & MS_NOSEC)) br->br_perm &= ~AuBrAttr_ICEX_SEC; #endif do_free = 0; wbr = br->br_wbr; if (wbr) wbr_wh_read_lock(wbr); if (!au_br_writable(br->br_perm)) { do_free = !!wbr; skip = (!wbr || (!wbr->wbr_whbase && !wbr->wbr_plink && !wbr->wbr_orph)); } else if (!au_br_wh_linkable(br->br_perm)) { /* skip = (!br->br_whbase && !br->br_orph); */ skip = (!wbr || !wbr->wbr_whbase); if (skip && wbr) { if (do_plink) skip = !!wbr->wbr_plink; else skip = !wbr->wbr_plink; } } else { /* skip = (br->br_whbase && br->br_ohph); */ skip = (wbr && wbr->wbr_whbase); if (skip) { if (do_plink) skip = !!wbr->wbr_plink; else skip = !wbr->wbr_plink; } } if (wbr) wbr_wh_read_unlock(wbr); if (au_br_fhsm(br->br_perm)) { fhsm++; AuDebugOn(!br->br_fhsm); } if (skip) continue; hdir = au_hi(dir, bindex); au_hn_imtx_lock_nested(hdir, AuLsc_I_PARENT); if (wbr) wbr_wh_write_lock(wbr); err = au_wh_init(br, sb); if (wbr) wbr_wh_write_unlock(wbr); au_hn_imtx_unlock(hdir); if (!err && do_free) { kfree(wbr); br->br_wbr = NULL; } } if (fhsm >= 2) { au_fset_si(sbinfo, FHSM); for (bindex = bend; bindex >= 0; bindex--) { br = au_sbr(sb, bindex); if (au_br_fhsm(br->br_perm)) { au_fhsm_set_bottom(sb, bindex); break; } } } else { au_fclr_si(sbinfo, FHSM); au_fhsm_set_bottom(sb, -1); } return err; }