/* * final procedure of adding a new entry, except link(2). * remove whiteout, instantiate, copyup the parent dir's times and size * and update version. * if it failed, re-create the removed whiteout. */ static int epilog(struct inode *dir, aufs_bindex_t bindex, struct dentry *wh_dentry, struct dentry *dentry) { int err, rerr; aufs_bindex_t bwh; struct path h_path; struct inode *inode, *h_dir; struct dentry *wh; bwh = -1; if (wh_dentry) { h_dir = wh_dentry->d_parent->d_inode; /* dir inode is locked */ IMustLock(h_dir); AuDebugOn(au_h_iptr(dir, bindex) != h_dir); bwh = au_dbwh(dentry); h_path.dentry = wh_dentry; h_path.mnt = au_sbr_mnt(dir->i_sb, bindex); err = au_wh_unlink_dentry(au_h_iptr(dir, bindex), &h_path, dentry); if (unlikely(err)) goto out; } inode = au_new_inode(dentry, /*must_new*/1); if (!IS_ERR(inode)) { d_instantiate(dentry, inode); dir = dentry->d_parent->d_inode; /* dir inode is locked */ IMustLock(dir); if (au_ibstart(dir) == au_dbstart(dentry)) au_cpup_attr_timesizes(dir); dir->i_version++; return 0; /* success */ } err = PTR_ERR(inode); if (!wh_dentry) goto out; /* revert */ /* dir inode is locked */ wh = au_wh_create(dentry, bwh, wh_dentry->d_parent); rerr = PTR_ERR(wh); if (IS_ERR(wh)) { AuIOErr("%.*s reverting whiteout failed(%d, %d)\n", AuDLNPair(dentry), err, rerr); err = -EIO; } else dput(wh); out: return err; }
/* * rename the @h_dentry on @br to the whiteouted temporary name. */ int au_whtmp_ren(struct dentry *h_dentry, struct au_branch *br) { int err; struct path h_path = { .mnt = au_br_mnt(br) }; struct inode *h_dir; struct dentry *h_parent; h_parent = h_dentry->d_parent; /* dir inode is locked */ h_dir = h_parent->d_inode; IMustLock(h_dir); h_path.dentry = au_whtmp_lkup(h_parent, br, &h_dentry->d_name); err = PTR_ERR(h_path.dentry); if (IS_ERR(h_path.dentry)) goto out; /* under the same dir, no need to lock_rename() */ err = vfsub_rename(h_dir, h_dentry, h_dir, &h_path); AuTraceErr(err); dput(h_path.dentry); out: AuTraceErr(err); return err; }
int vfsub_create(struct inode *dir, struct path *path, int mode, bool want_excl) { int err; struct dentry *d; IMustLock(dir); d = path->dentry; path->dentry = d->d_parent; err = security_path_mknod(path, d, mode, 0); path->dentry = d; if (unlikely(err)) goto out; lockdep_off(); err = vfs_create(dir, path->dentry, mode, want_excl); lockdep_on(); if (!err) { struct path tmp = *path; int did; vfsub_update_h_iattr(&tmp, &did); if (did) { tmp.dentry = path->dentry->d_parent; vfsub_update_h_iattr(&tmp, /*did*/NULL); } /*ignore*/ } out: return err; }
int vfsub_symlink(struct inode *dir, struct path *path, const char *symname) { int err; struct dentry *d; IMustLock(dir); d = path->dentry; path->dentry = d->d_parent; err = security_path_symlink(path, d, symname); path->dentry = d; if (unlikely(err)) goto out; err = vfs_symlink(dir, path->dentry, symname); if (!err) { struct path tmp = *path; int did; vfsub_update_h_iattr(&tmp, &did); if (did) { tmp.dentry = path->dentry->d_parent; vfsub_update_h_iattr(&tmp, /*did*/NULL); } /*ignore*/ } out: return err; }
int vfsub_mknod(struct inode *dir, struct path *path, int mode, dev_t dev) { int err; struct dentry *d; IMustLock(dir); d = path->dentry; path->dentry = d->d_parent; err = security_path_mknod(path, d, mode, new_encode_dev(dev)); path->dentry = d; if (unlikely(err)) goto out; err = vfs_mknod(dir, path->dentry, mode, dev); if (!err) { struct path tmp = *path; int did; vfsub_update_h_iattr(&tmp, &did); if (did) { tmp.dentry = path->dentry->d_parent; vfsub_update_h_iattr(&tmp, /*did*/NULL); } /*ignore*/ } out: return err; }
struct dentry *vfsub_lookup_one_len(const char *name, struct dentry *parent, int len) { struct dentry *d; LKTRTrace("%.*s/%.*s\n", AuDLNPair(parent), len, name); IMustLock(parent->d_inode); d = lookup_one_len(name, parent, len); if (!IS_ERR(d)) au_update_fuse_h_inode(NULL, d); /*ignore*/ return d; }
int do_vfsub_rename(struct inode *src_dir, struct dentry *src_dentry, struct inode *dir, struct dentry *dentry) { int err; LKTRTrace("i%lu, %.*s, i%lu, %.*s\n", src_dir->i_ino, AuDLNPair(src_dentry), dir->i_ino, AuDLNPair(dentry)); IMustLock(dir); IMustLock(src_dir); AuDebugOn(src_dir != dir && !vfsub_is_rename_mutex_locked(dir->i_sb)); lockdep_off(); err = vfs_rename(src_dir, src_dentry, dir, dentry); lockdep_on(); if (!err) { /* dir inode is locked */ au_update_fuse_h_inode(NULL, dentry->d_parent); /*ignore*/ au_update_fuse_h_inode(NULL, src_dentry->d_parent); /*ignore*/ au_update_fuse_h_inode(NULL, src_dentry); /*ignore*/ } return err; }
int au_h_verify(struct dentry *h_dentry, unsigned int udba, struct inode *h_dir, struct dentry *h_parent, struct au_branch *br) { int err; err = 0; if (udba == AuOpt_UDBA_REVAL) { IMustLock(h_dir); err = (h_dentry->d_parent->d_inode != h_dir); } else if (udba == AuOpt_UDBA_HNOTIFY) err = au_h_verify_dentry(h_dentry, h_parent, br); return err; }
int au_h_verify(struct dentry *h_dentry, unsigned int udba, struct inode *h_dir, struct dentry *h_parent, struct au_branch *br) { int err; err = 0; if (udba == AuOpt_UDBA_REVAL && !au_test_fs_remote(h_dentry->d_sb)) { IMustLock(h_dir); err = (d_inode(h_dentry->d_parent) != h_dir); } else if (udba != AuOpt_UDBA_NONE) err = au_h_verify_dentry(h_dentry, h_parent, br); return err; }
int do_vfsub_rmdir(struct inode *dir, struct dentry *dentry) { int err; LKTRTrace("i%lu, %.*s\n", dir->i_ino, AuDLNPair(dentry)); IMustLock(dir); lockdep_off(); err = vfs_rmdir(dir, dentry); lockdep_on(); /* dir inode is locked */ if (!err) au_update_fuse_h_inode(NULL, dentry->d_parent); /*ignore*/ return err; }
int do_vfsub_mkdir(struct inode *dir, struct dentry *dentry, int mode) { int err; LKTRTrace("i%lu, %.*s, 0x%x\n", dir->i_ino, AuDLNPair(dentry), mode); IMustLock(dir); err = vfs_mkdir(dir, dentry, mode); if (!err) { /* dir inode is locked */ au_update_fuse_h_inode(NULL, dentry->d_parent); /*ignore*/ au_update_fuse_h_inode(NULL, dentry); /*ignore*/ } return err; }
static void call_notify_change(void *args) { struct notify_change_args *a = args; struct inode *h_inode; h_inode = a->path->dentry->d_inode; IMustLock(h_inode); *a->errp = -EPERM; if (!IS_IMMUTABLE(h_inode) && !IS_APPEND(h_inode)) { *a->errp = notify_change(a->path->dentry, a->ia); if (!*a->errp) vfsub_update_h_iattr(a->path, /*did*/NULL); /*ignore*/ } AuTraceErr(*a->errp); }
struct dentry *vfsub__lookup_hash(struct qstr *name, struct dentry *parent, struct nameidata *nd) { struct dentry *d; LKTRTrace("%.*s/%.*s, nd %d\n", AuDLNPair(parent), AuLNPair(name), !!nd); if (nd) LKTRTrace("nd{0x%x}\n", nd->flags); IMustLock(parent->d_inode); d = __lookup_hash(name, parent, nd); if (!IS_ERR(d)) au_update_fuse_h_inode(NULL, d); /*ignore*/ return d; }
static struct dentry *aufs_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd) { struct dentry *ret, *parent; struct inode *inode; struct super_block *sb; int err, npositive; IMustLock(dir); sb = dir->i_sb; si_read_lock(sb, AuLock_FLUSH); ret = ERR_PTR(-ENAMETOOLONG); if (unlikely(dentry->d_name.len > AUFS_MAX_NAMELEN)) goto out; err = au_di_init(dentry); ret = ERR_PTR(err); if (unlikely(err)) goto out; parent = dentry->d_parent; /* dir inode is locked */ di_read_lock_parent(parent, AuLock_IR); npositive = au_lkup_dentry(dentry, au_dbstart(parent), /*type*/0, nd); di_read_unlock(parent, AuLock_IR); err = npositive; ret = ERR_PTR(err); if (unlikely(err < 0)) goto out_unlock; inode = NULL; if (npositive) { inode = au_new_inode(dentry, /*must_new*/0); ret = (void *)inode; } if (IS_ERR(inode)) goto out_unlock; ret = d_splice_alias(inode, dentry); if (unlikely(IS_ERR(ret) && inode)) ii_write_unlock(inode); out_unlock: di_write_unlock(dentry); out: si_read_unlock(sb); return ret; }
int vfsub_create(struct inode *dir, struct path *path, int mode) { int err; struct dentry *d; IMustLock(dir); d = path->dentry; path->dentry = d->d_parent; err = security_path_mknod(path, d, mode, 0); path->dentry = d; if (unlikely(err)) goto out; if (au_test_fs_null_nd(dir->i_sb)) err = vfs_create(dir, path->dentry, mode, NULL); else { struct nameidata h_nd; memset(&h_nd, 0, sizeof(h_nd)); h_nd.flags = LOOKUP_CREATE; h_nd.intent.open.flags = O_CREAT | vfsub_fmode_to_uint(FMODE_READ); h_nd.intent.open.create_mode = mode; h_nd.path.dentry = path->dentry->d_parent; h_nd.path.mnt = path->mnt; path_get(&h_nd.path); err = vfs_create(dir, path->dentry, mode, &h_nd); path_put(&h_nd.path); } if (!err) { struct path tmp = *path; int did; vfsub_update_h_iattr(&tmp, &did); if (did) { tmp.dentry = path->dentry->d_parent; vfsub_update_h_iattr(&tmp, /*did*/NULL); } /*ignore*/ } out: return err; }
struct dentry *vfsub_lookup_hash(struct nameidata *nd) { struct path path = { .mnt = nd->path.mnt }; IMustLock(nd->path.dentry->d_inode); path.dentry = lookup_hash(nd); if (IS_ERR(path.dentry)) goto out; if (path.dentry->d_inode) vfsub_update_h_iattr(&path, /*did*/NULL); /*ignore*/ out: AuTraceErrPtr(path.dentry); return path.dentry; } /* ---------------------------------------------------------------------- */ struct dentry *vfsub_lock_rename(struct dentry *d1, struct au_hinode *hdir1, struct dentry *d2, struct au_hinode *hdir2) { struct dentry *d; lockdep_off(); d = lock_rename(d1, d2); lockdep_on(); au_hn_suspend(hdir1); if (hdir1 != hdir2) au_hn_suspend(hdir2); return d; } void vfsub_unlock_rename(struct dentry *d1, struct au_hinode *hdir1, struct dentry *d2, struct au_hinode *hdir2) { au_hn_resume(hdir1); if (hdir1 != hdir2) au_hn_resume(hdir2); lockdep_off(); unlock_rename(d1, d2); lockdep_on(); }
int vfsub_rmdir(struct inode *dir, struct path *path) { int err; struct dentry *d; IMustLock(dir); d = path->dentry; path->dentry = d->d_parent; err = security_path_rmdir(path, d); path->dentry = d; if (unlikely(err)) goto out; lockdep_off(); err = vfs_rmdir(dir, path->dentry); lockdep_on(); if (!err) { struct path tmp = { .dentry = path->dentry->d_parent, .mnt = path->mnt }; vfsub_update_h_iattr(&tmp, /*did*/NULL); /*ignore*/ } out: return err; } /* ---------------------------------------------------------------------- */ /* todo: support mmap_sem? */ ssize_t vfsub_read_u(struct file *file, char __user *ubuf, size_t count, loff_t *ppos) { ssize_t err; lockdep_off(); err = vfs_read(file, ubuf, count, ppos); lockdep_on(); if (err >= 0) vfsub_update_h_iattr(&file->f_path, /*did*/NULL); /*ignore*/ return err; }
static int au_wr_dir_cpup(struct dentry *dentry, struct dentry *parent, const unsigned char add_entry, aufs_bindex_t bcpup, aufs_bindex_t bstart) { int err; struct dentry *h_parent; struct inode *h_dir; if (add_entry) IMustLock(parent->d_inode); else di_write_lock_parent(parent); err = 0; if (!au_h_dptr(parent, bcpup)) { if (bstart < bcpup) err = au_cpdown_dirs(dentry, bcpup); else err = au_cpup_dirs(dentry, bcpup); } if (!err && add_entry) { h_parent = au_h_dptr(parent, bcpup); h_dir = h_parent->d_inode; mutex_lock_nested(&h_dir->i_mutex, AuLsc_I_PARENT); err = au_lkup_neg(dentry, bcpup); /* todo: no unlock here */ mutex_unlock(&h_dir->i_mutex); AuDbg("bcpup %d\n", bcpup); if (!err) { if (!dentry->d_inode) au_set_h_dptr(dentry, bstart, NULL); au_update_dbrange(dentry, /*do_put_zero*/0); } } if (!add_entry) di_write_unlock(parent); if (!err) err = bcpup; /* success */ AuTraceErr(err); return err; }
/* * Ideally this function should call VFS:do_last() in order to keep all its * checkings. But it is very hard for aufs to regenerate several VFS internal * structure such as nameidata. This is a second (or third) best approach. * cf. linux/fs/namei.c:do_last(), lookup_open() and atomic_open(). */ int vfsub_atomic_open(struct inode *dir, struct dentry *dentry, struct vfsub_aopen_args *args, struct au_branch *br) { int err; struct file *file = args->file; /* copied from linux/fs/namei.c:atomic_open() */ struct dentry *const DENTRY_NOT_SET = (void *)-1UL; IMustLock(dir); AuDebugOn(!dir->i_op->atomic_open); err = au_br_test_oflag(args->open_flag, br); if (unlikely(err)) goto out; args->file->f_path.dentry = DENTRY_NOT_SET; args->file->f_path.mnt = au_br_mnt(br); err = dir->i_op->atomic_open(dir, dentry, file, args->open_flag, args->create_mode, args->opened); if (err >= 0) { /* some filesystems don't set FILE_CREATED while succeeded? */ if (*args->opened & FILE_CREATED) fsnotify_create(dir, dentry); } else goto out; if (!err) { /* todo: call VFS:may_open() here */ err = open_check_o_direct(file); /* todo: ima_file_check() too? */ if (!err && (args->open_flag & __FMODE_EXEC)) err = deny_write_access(file); if (unlikely(err)) /* note that the file is created and still opened */ goto out; } atomic_inc(&br->br_count); fsnotify_open(file); out: return err; }
struct dentry *vfsub_lookup_one_len(const char *name, struct dentry *parent, int len) { struct path path = { .mnt = NULL }; IMustLock(parent->d_inode); path.dentry = lookup_one_len(name, parent, len); if (IS_ERR(path.dentry)) goto out; if (path.dentry->d_inode) vfsub_update_h_iattr(&path, /*did*/NULL); /*ignore*/ out: AuTraceErrPtr(path.dentry); return path.dentry; }
struct dentry *vfsub_lookup_one_len(const char *name, struct dentry *parent, int len) { struct path path = { .mnt = NULL }; /* VFS checks it too, but by WARN_ON_ONCE() */ IMustLock(d_inode(parent)); path.dentry = lookup_one_len(name, parent, len); if (IS_ERR(path.dentry)) goto out; if (d_is_positive(path.dentry)) vfsub_update_h_iattr(&path, /*did*/NULL); /*ignore*/ out: AuTraceErrPtr(path.dentry); return path.dentry; }
int do_vfsub_create(struct inode *dir, struct dentry *dentry, int mode, struct nameidata *nd) { int err; struct vfsmount *mnt; LKTRTrace("i%lu, %.*s, 0x%x\n", dir->i_ino, AuDLNPair(dentry), mode); IMustLock(dir); err = vfs_create(dir, dentry, mode, nd); if (!err) { mnt = NULL; if (nd) mnt = nd->mnt; /* dir inode is locked */ au_update_fuse_h_inode(mnt, dentry->d_parent); /*ignore*/ au_update_fuse_h_inode(mnt, dentry); /*ignore*/ } return err; }
/* mainly for link(2) and rename(2) */ int au_wbr(struct dentry *dentry, aufs_bindex_t btgt) { aufs_bindex_t bdiropq, bwh; struct dentry *parent; struct au_branch *br; parent = dentry->d_parent; IMustLock(parent->d_inode); /* dir is locked */ bdiropq = au_dbdiropq(parent); bwh = au_dbwh(dentry); br = au_sbr(dentry->d_sb, btgt); if (au_br_rdonly(br) || (0 <= bdiropq && bdiropq < btgt) || (0 <= bwh && bwh < btgt)) btgt = -1; AuDbg("btgt %d\n", btgt); return btgt; }
int vfsub_link(struct dentry *src_dentry, struct inode *dir, struct path *path, struct inode **delegated_inode) { int err; struct dentry *d; IMustLock(dir); err = au_test_nlink(d_inode(src_dentry)); if (unlikely(err)) return err; /* we don't call may_linkat() */ d = path->dentry; path->dentry = d->d_parent; err = security_path_link(src_dentry, path, d); path->dentry = d; if (unlikely(err)) goto out; lockdep_off(); err = vfs_link(src_dentry, dir, path->dentry, delegated_inode); lockdep_on(); if (!err) { struct path tmp = *path; int did; /* fuse has different memory inode for the same inumber */ vfsub_update_h_iattr(&tmp, &did); if (did) { tmp.dentry = path->dentry->d_parent; vfsub_update_h_iattr(&tmp, /*did*/NULL); tmp.dentry = src_dentry; vfsub_update_h_iattr(&tmp, /*did*/NULL); } /*ignore*/ } out: 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 void au_hin_ctl(struct au_hinode *hinode, int do_set) { struct inode *h_inode; struct inotify_watch *watch; h_inode = hinode->hi_inode; IMustLock(h_inode); /* todo: try inotify_find_update_watch()? */ watch = &hinode->hi_notify->hn_watch; mutex_lock(&h_inode->inotify_mutex); /* mutex_lock(&watch->ih->mutex); */ if (do_set) { AuDebugOn(watch->mask & AuHinMask); watch->mask |= AuHinMask; } else { AuDebugOn(!(watch->mask & AuHinMask)); watch->mask &= ~AuHinMask; } /* mutex_unlock(&watch->ih->mutex); */ mutex_unlock(&h_inode->inotify_mutex); }
int do_vfsub_link(struct dentry *src_dentry, struct inode *dir, struct dentry *dentry) { int err; LKTRTrace("%.*s, i%lu, %.*s\n", AuDLNPair(src_dentry), dir->i_ino, AuDLNPair(dentry)); IMustLock(dir); lockdep_off(); err = vfs_link(src_dentry, dir, dentry); lockdep_on(); if (!err) { LKTRTrace("src_i %p, dst_i %p\n", src_dentry->d_inode, dentry->d_inode); /* fuse has different memory inode for the same inumber */ au_update_fuse_h_inode(NULL, src_dentry); /*ignore*/ /* dir inode is locked */ au_update_fuse_h_inode(NULL, dentry->d_parent); /*ignore*/ au_update_fuse_h_inode(NULL, dentry); /*ignore*/ } return err; }
static void call_notify_change(void *args) { struct notify_change_args *a = args; struct inode *h_inode; LKTRTrace("%.*s, ia_valid 0x%x\n", AuDLNPair(a->h_dentry), a->ia->ia_valid); h_inode = a->h_dentry->d_inode; IMustLock(h_inode); *a->errp = -EPERM; if (!IS_IMMUTABLE(h_inode) && !IS_APPEND(h_inode)) { vfsub_ignore(a->vargs); lockdep_off(); *a->errp = notify_change(a->h_dentry, a->ia); lockdep_on(); if (!*a->errp) au_update_fuse_h_inode(NULL, a->h_dentry); /*ignore*/ else vfsub_unignore(a->vargs); au_dbg_hin_list(a->vargs); } AuTraceErr(*a->errp); }
int aufs_rmdir(struct inode *dir, struct dentry *dentry) { int err, rmdir_later; aufs_bindex_t bwh, bindex, bstart; struct au_dtime dt; struct au_pin pin; struct inode *inode; struct dentry *parent, *wh_dentry, *h_dentry; struct au_whtmp_rmdir *args; IMustLock(dir); err = aufs_read_lock(dentry, AuLock_DW | AuLock_FLUSH | AuLock_GEN); if (unlikely(err)) goto out; err = au_alive_dir(dentry); if (unlikely(err)) goto out_unlock; inode = dentry->d_inode; IMustLock(inode); err = -ENOTDIR; if (unlikely(!S_ISDIR(inode->i_mode))) goto out_unlock; /* possible? */ err = -ENOMEM; args = au_whtmp_rmdir_alloc(dir->i_sb, GFP_NOFS); if (unlikely(!args)) goto out_unlock; parent = dentry->d_parent; /* dir inode is locked */ di_write_lock_parent(parent); err = au_test_empty(dentry, &args->whlist); if (unlikely(err)) goto out_parent; bstart = au_dbstart(dentry); bwh = au_dbwh(dentry); bindex = -1; wh_dentry = lock_hdir_create_wh(dentry, /*isdir*/1, &bindex, &dt, &pin); err = PTR_ERR(wh_dentry); if (IS_ERR(wh_dentry)) goto out_parent; h_dentry = au_h_dptr(dentry, bstart); dget(h_dentry); rmdir_later = 0; if (bindex == bstart) { err = renwh_and_rmdir(dentry, bstart, &args->whlist, dir); if (err > 0) { rmdir_later = err; err = 0; } } else { /* stop monitoring */ au_hn_free(au_hi(inode, bstart)); /* dir inode is locked */ IMustLock(wh_dentry->d_parent->d_inode); err = 0; } if (!err) { vfsub_dead_dir(inode); au_set_dbdiropq(dentry, -1); epilog(dir, dentry, bindex); if (rmdir_later) { au_whtmp_kick_rmdir(dir, bstart, h_dentry, args); args = NULL; } goto out_unpin; /* success */ } /* revert */ AuLabel(revert); if (wh_dentry) { int rerr; rerr = do_revert(err, dir, bindex, bwh, wh_dentry, dentry, &dt); if (rerr) err = rerr; } out_unpin: au_unpin(&pin); dput(wh_dentry); dput(h_dentry); out_parent: di_write_unlock(parent); if (args) au_whtmp_rmdir_free(args); out_unlock: aufs_read_unlock(dentry, AuLock_DW); out: AuTraceErr(err); return err; }
/* * when an error happened, remove the created whiteout and revert everything. */ static int do_revert(int err, struct inode *dir, aufs_bindex_t bindex, aufs_bindex_t bwh, struct dentry *wh_dentry, struct dentry *dentry, struct au_dtime *dt) { int rerr; struct path h_path = { .dentry = wh_dentry, .mnt = au_sbr_mnt(dir->i_sb, bindex) }; rerr = au_wh_unlink_dentry(au_h_iptr(dir, bindex), &h_path, dentry); if (!rerr) { au_set_dbwh(dentry, bwh); au_dtime_revert(dt); return 0; } AuIOErr("%.*s reverting whiteout failed(%d, %d)\n", AuDLNPair(dentry), err, rerr); return -EIO; } /* ---------------------------------------------------------------------- */ int aufs_unlink(struct inode *dir, struct dentry *dentry) { int err; aufs_bindex_t bwh, bindex, bstart; struct au_dtime dt; struct au_pin pin; struct path h_path; struct inode *inode, *h_dir; struct dentry *parent, *wh_dentry; IMustLock(dir); err = aufs_read_lock(dentry, AuLock_DW | AuLock_GEN); if (unlikely(err)) goto out; err = au_d_hashed_positive(dentry); if (unlikely(err)) goto out_unlock; inode = dentry->d_inode; IMustLock(inode); err = -EISDIR; if (unlikely(S_ISDIR(inode->i_mode))) goto out_unlock; /* possible? */ bstart = au_dbstart(dentry); bwh = au_dbwh(dentry); bindex = -1; parent = dentry->d_parent; /* dir inode is locked */ di_write_lock_parent(parent); wh_dentry = lock_hdir_create_wh(dentry, /*isdir*/0, &bindex, &dt, &pin); err = PTR_ERR(wh_dentry); if (IS_ERR(wh_dentry)) goto out_parent; h_path.mnt = au_sbr_mnt(dentry->d_sb, bstart); h_path.dentry = au_h_dptr(dentry, bstart); dget(h_path.dentry); if (bindex == bstart) { h_dir = au_pinned_h_dir(&pin); err = vfsub_unlink(h_dir, &h_path, /*force*/0); } else { /* dir inode is locked */ h_dir = wh_dentry->d_parent->d_inode; IMustLock(h_dir); err = 0; } if (!err) { vfsub_drop_nlink(inode); epilog(dir, dentry, bindex); /* update target timestamps */ if (bindex == bstart) { vfsub_update_h_iattr(&h_path, /*did*/NULL); /*ignore*/ inode->i_ctime = h_path.dentry->d_inode->i_ctime; } else /* todo: this timestamp may be reverted later */ inode->i_ctime = h_dir->i_ctime; goto out_unpin; /* success */ } /* revert */ if (wh_dentry) { int rerr; rerr = do_revert(err, dir, bindex, bwh, wh_dentry, dentry, &dt); if (rerr) err = rerr; } out_unpin: au_unpin(&pin); dput(wh_dentry); dput(h_path.dentry); out_parent: di_write_unlock(parent); out_unlock: aufs_read_unlock(dentry, AuLock_DW); out: return err; }