UBUNTU: ubuntu: AUFS -- update to c5021514085a5d96364e096dbd34cadb2251abfd
[linux-flexiantxendom0-natty.git] / ubuntu / aufs / dentry.c
index 2743e26..8daf4a3 100644 (file)
@@ -267,9 +267,11 @@ int au_lkup_dentry(struct dentry *dentry, aufs_bindex_t bstart, mode_t type,
        }
        err = npositive;
        if (unlikely(!au_opt_test(au_mntflags(dentry->d_sb), UDBA_NONE)
-                    && au_dbstart(dentry) < 0))
-               /* both of real entry and whiteout found */
+                    && au_dbstart(dentry) < 0)) {
                err = -EIO;
+               AuIOErr("both of real entry and whiteout found, %.*s, err %d\n",
+                       AuDLNPair(dentry), err);
+       }
 
 out_parent:
        dput(parent);
@@ -320,8 +322,8 @@ int au_lkup_neg(struct dentry *dentry, aufs_bindex_t bindex)
                goto out;
        if (unlikely(h_dentry->d_inode)) {
                err = -EIO;
-               AuIOErr("b%d %.*s should be negative.\n",
-                       bindex, AuDLNPair(h_dentry));
+               AuIOErr("%.*s should be negative on b%d.\n",
+                       AuDLNPair(h_dentry), bindex);
                dput(h_dentry);
                goto out;
        }
@@ -437,32 +439,30 @@ int au_h_verify(struct dentry *h_dentry, unsigned int udba, struct inode *h_dir,
 
 /* ---------------------------------------------------------------------- */
 
-static void au_do_refresh_hdentry(struct au_hdentry *p, struct au_dinfo *dinfo,
-                                 struct dentry *parent)
+static int au_do_refresh_hdentry(struct dentry *dentry, struct dentry *parent)
 {
-       struct dentry *h_d, *h_dp;
-       struct au_hdentry tmp, *q;
-       struct super_block *sb;
+       int err;
        aufs_bindex_t new_bindex, bindex, bend, bwh, bdiropq;
+       struct au_hdentry tmp, *p, *q;
+       struct au_dinfo *dinfo;
+       struct super_block *sb;
 
-       AuRwMustWriteLock(&dinfo->di_rwsem);
+       DiMustWriteLock(dentry);
 
+       sb = dentry->d_sb;
+       dinfo = au_di(dentry);
        bend = dinfo->di_bend;
        bwh = dinfo->di_bwh;
        bdiropq = dinfo->di_bdiropq;
+       p = dinfo->di_hdentry + dinfo->di_bstart;
        for (bindex = dinfo->di_bstart; bindex <= bend; bindex++, p++) {
-               h_d = p->hd_dentry;
-               if (!h_d)
+               if (!p->hd_dentry)
                        continue;
 
-               h_dp = dget_parent(h_d);
-               if (h_dp == au_h_dptr(parent, bindex)) {
-                       dput(h_dp);
+               new_bindex = au_br_index(sb, p->hd_id);
+               if (new_bindex == bindex)
                        continue;
-               }
 
-               new_bindex = au_find_dbindex(parent, h_dp);
-               dput(h_dp);
                if (dinfo->di_bwh == bindex)
                        bwh = new_bindex;
                if (dinfo->di_bdiropq == bindex)
@@ -484,7 +484,6 @@ static void au_do_refresh_hdentry(struct au_hdentry *p, struct au_dinfo *dinfo,
                }
        }
 
-       sb = parent->d_sb;
        dinfo->di_bwh = -1;
        if (bwh >= 0 && bwh <= au_sbend(sb) && au_sbr_whable(sb, bwh))
                dinfo->di_bwh = bwh;
@@ -495,6 +494,9 @@ static void au_do_refresh_hdentry(struct au_hdentry *p, struct au_dinfo *dinfo,
            && au_sbr_whable(sb, bdiropq))
                dinfo->di_bdiropq = bdiropq;
 
+       err = -EIO;
+       dinfo->di_bstart = -1;
+       dinfo->di_bend = -1;
        bend = au_dbend(parent);
        p = dinfo->di_hdentry;
        for (bindex = 0; bindex <= bend; bindex++, p++)
@@ -503,61 +505,318 @@ static void au_do_refresh_hdentry(struct au_hdentry *p, struct au_dinfo *dinfo,
                        break;
                }
 
-       p = dinfo->di_hdentry + bend;
-       for (bindex = bend; bindex >= 0; bindex--, p--)
-               if (p->hd_dentry) {
-                       dinfo->di_bend = bindex;
-                       break;
+       if (dinfo->di_bstart >= 0) {
+               p = dinfo->di_hdentry + bend;
+               for (bindex = bend; bindex >= 0; bindex--, p--)
+                       if (p->hd_dentry) {
+                               dinfo->di_bend = bindex;
+                               err = 0;
+                               break;
+                       }
+       }
+
+       return err;
+}
+
+static void au_do_hide(struct dentry *dentry)
+{
+       struct inode *inode;
+
+       inode = dentry->d_inode;
+       if (inode) {
+               if (!S_ISDIR(inode->i_mode)) {
+                       if (inode->i_nlink && !d_unhashed(dentry))
+                               drop_nlink(inode);
+               } else {
+                       clear_nlink(inode);
+                       /* stop next lookup */
+                       inode->i_flags |= S_DEAD;
                }
+               smp_mb(); /* necessary? */
+       }
+       d_drop(dentry);
+}
+
+static int au_hide_children(struct dentry *parent)
+{
+       int err, i, j, ndentry;
+       struct au_dcsub_pages dpages;
+       struct au_dpage *dpage;
+       struct dentry *dentry;
+
+       err = au_dpages_init(&dpages, GFP_NOFS);
+       if (unlikely(err))
+               goto out;
+       err = au_dcsub_pages(&dpages, parent, NULL, NULL);
+       if (unlikely(err))
+               goto out_dpages;
+
+       /* in reverse order */
+       for (i = dpages.ndpage - 1; i >= 0; i--) {
+               dpage = dpages.dpages + i;
+               ndentry = dpage->ndentry;
+               for (j = ndentry - 1; j >= 0; j--) {
+                       dentry = dpage->dentries[j];
+                       if (dentry != parent)
+                               au_do_hide(dentry);
+               }
+       }
+
+out_dpages:
+       au_dpages_free(&dpages);
+out:
+       return err;
+}
+
+static void au_hide(struct dentry *dentry)
+{
+       int err;
+       struct inode *inode;
+
+       AuDbgDentry(dentry);
+       inode = dentry->d_inode;
+       if (inode && S_ISDIR(inode->i_mode)) {
+               /* shrink_dcache_parent(dentry); */
+               err = au_hide_children(dentry);
+               if (unlikely(err))
+                       AuIOErr("%.*s, failed hiding children, ignored %d\n",
+                               AuDLNPair(dentry), err);
+       }
+       au_do_hide(dentry);
 }
 
 /*
- * returns the number of found lower positive dentries,
- * otherwise an error.
+ * By adding a dirty branch, a cached dentry may be affected in various ways.
+ *
+ * a dirty branch is added
+ * - on the top of layers
+ * - in the middle of layers
+ * - to the bottom of layers
+ *
+ * on the added branch there exists
+ * - a whiteout
+ * - a diropq
+ * - a same named entry
+ *   + exist
+ *     * negative --> positive
+ *     * positive --> positive
+ *      - type is unchanged
+ *      - type is changed
+ *   + doesn't exist
+ *     * negative --> negative
+ *     * positive --> negative (rejected by au_br_del() for non-dir case)
+ * - none
  */
-int au_refresh_hdentry(struct dentry *dentry, mode_t type)
+static int au_refresh_by_dinfo(struct dentry *dentry, struct au_dinfo *dinfo,
+                              struct au_dinfo *tmp)
 {
-       int npositive, err;
+       int err;
+       aufs_bindex_t bindex, bend;
+       struct {
+               struct dentry *dentry;
+               struct inode *inode;
+               mode_t mode;
+       } orig_h, tmp_h;
+       struct au_hdentry *hd;
+       struct inode *inode, *h_inode;
+       struct dentry *h_dentry;
+
+       err = 0;
+       AuDebugOn(dinfo->di_bstart < 0);
+       orig_h.dentry = dinfo->di_hdentry[dinfo->di_bstart].hd_dentry;
+       orig_h.inode = orig_h.dentry->d_inode;
+       orig_h.mode = 0;
+       if (orig_h.inode)
+               orig_h.mode = orig_h.inode->i_mode & S_IFMT;
+       memset(&tmp_h, 0, sizeof(tmp_h));
+       if (tmp->di_bstart >= 0) {
+               tmp_h.dentry = tmp->di_hdentry[tmp->di_bstart].hd_dentry;
+               tmp_h.inode = tmp_h.dentry->d_inode;
+               if (tmp_h.inode)
+                       tmp_h.mode = tmp_h.inode->i_mode & S_IFMT;
+       }
+
+       inode = dentry->d_inode;
+       if (!orig_h.inode) {
+               AuDbg("nagative originally\n");
+               if (inode) {
+                       au_hide(dentry);
+                       goto out;
+               }
+               AuDebugOn(inode);
+               AuDebugOn(dinfo->di_bstart != dinfo->di_bend);
+               AuDebugOn(dinfo->di_bdiropq != -1);
+
+               if (!tmp_h.inode) {
+                       AuDbg("negative --> negative\n");
+                       /* should have only one negative lower */
+                       if (tmp->di_bstart >= 0
+                           && tmp->di_bstart < dinfo->di_bstart) {
+                               AuDebugOn(tmp->di_bstart != tmp->di_bend);
+                               AuDebugOn(dinfo->di_bstart != dinfo->di_bend);
+                               au_set_h_dptr(dentry, dinfo->di_bstart, NULL);
+                               au_di_cp(dinfo, tmp);
+                               hd = tmp->di_hdentry + tmp->di_bstart;
+                               au_set_h_dptr(dentry, tmp->di_bstart,
+                                             dget(hd->hd_dentry));
+                       }
+                       au_dbg_verify_dinode(dentry);
+               } else {
+                       AuDbg("negative --> positive\n");
+                       /*
+                        * similar to the behaviour of creating with bypassing
+                        * aufs.
+                        * unhash it in order to force an error in the
+                        * succeeding create operation.
+                        * we should not set S_DEAD here.
+                        */
+                       d_drop(dentry);
+                       /* au_di_swap(tmp, dinfo); */
+                       au_dbg_verify_dinode(dentry);
+               }
+       } else {
+               AuDbg("positive originally\n");
+               /* inode may be NULL */
+               AuDebugOn(inode && (inode->i_mode & S_IFMT) != orig_h.mode);
+               if (!tmp_h.inode) {
+                       AuDbg("positive --> negative\n");
+                       /* or bypassing aufs */
+                       au_hide(dentry);
+                       if (tmp->di_bwh >= 0 && tmp->di_bwh <= dinfo->di_bstart)
+                               dinfo->di_bwh = tmp->di_bwh;
+                       if (inode)
+                               err = au_refresh_hinode_self(inode);
+                       au_dbg_verify_dinode(dentry);
+               } else if (orig_h.mode == tmp_h.mode) {
+                       AuDbg("positive --> positive, same type\n");
+                       if (!S_ISDIR(orig_h.mode)
+                           && dinfo->di_bstart > tmp->di_bstart) {
+                               /*
+                                * similar to the behaviour of removing and
+                                * creating.
+                                */
+                               au_hide(dentry);
+                               if (inode)
+                                       err = au_refresh_hinode_self(inode);
+                               au_dbg_verify_dinode(dentry);
+                       } else {
+                               /* fill empty slots */
+                               if (dinfo->di_bstart > tmp->di_bstart)
+                                       dinfo->di_bstart = tmp->di_bstart;
+                               if (dinfo->di_bend < tmp->di_bend)
+                                       dinfo->di_bend = tmp->di_bend;
+                               dinfo->di_bwh = tmp->di_bwh;
+                               dinfo->di_bdiropq = tmp->di_bdiropq;
+                               hd = tmp->di_hdentry;
+                               bend = dinfo->di_bend;
+                               for (bindex = tmp->di_bstart; bindex <= bend;
+                                    bindex++) {
+                                       if (au_h_dptr(dentry, bindex))
+                                               continue;
+                                       h_dentry = hd[bindex].hd_dentry;
+                                       if (!h_dentry)
+                                               continue;
+                                       h_inode = h_dentry->d_inode;
+                                       AuDebugOn(!h_inode);
+                                       AuDebugOn(orig_h.mode
+                                                 != (h_inode->i_mode
+                                                     & S_IFMT));
+                                       au_set_h_dptr(dentry, bindex,
+                                                     dget(h_dentry));
+                               }
+                               err = au_refresh_hinode(inode, dentry);
+                               au_dbg_verify_dinode(dentry);
+                       }
+               } else {
+                       AuDbg("positive --> positive, different type\n");
+                       /* similar to the behaviour of removing and creating */
+                       au_hide(dentry);
+                       if (inode)
+                               err = au_refresh_hinode_self(inode);
+                       au_dbg_verify_dinode(dentry);
+               }
+       }
+
+out:
+       return err;
+}
+
+int au_refresh_dentry(struct dentry *dentry, struct dentry *parent)
+{
+       int err, ebrange;
        unsigned int sigen;
-       aufs_bindex_t bstart;
-       struct au_dinfo *dinfo;
+       struct au_dinfo *dinfo, *tmp;
        struct super_block *sb;
-       struct dentry *parent;
+       struct inode *inode;
 
        DiMustWriteLock(dentry);
+       AuDebugOn(IS_ROOT(dentry));
+       AuDebugOn(!parent->d_inode);
 
        sb = dentry->d_sb;
-       AuDebugOn(IS_ROOT(dentry));
+       inode = dentry->d_inode;
        sigen = au_sigen(sb);
-       parent = dget_parent(dentry);
-       AuDebugOn(au_digen(parent) != sigen
-                 || au_iigen(parent->d_inode) != sigen);
+       err = au_digen_test(parent, sigen);
+       if (unlikely(err))
+               goto out;
 
        dinfo = au_di(dentry);
        err = au_di_realloc(dinfo, au_sbend(sb) + 1);
-       npositive = err;
        if (unlikely(err))
                goto out;
-       au_do_refresh_hdentry(dinfo->di_hdentry + dinfo->di_bstart, dinfo,
-                             parent);
+       ebrange = au_dbrange_test(dentry);
+       if (!ebrange)
+               ebrange = au_do_refresh_hdentry(dentry, parent);
 
-       npositive = 0;
-       bstart = au_dbstart(parent);
-       if (type != S_IFDIR && dinfo->di_bstart == bstart)
-               goto out_dgen; /* success */
+       if (d_unhashed(dentry) || ebrange) {
+               AuDebugOn(au_dbstart(dentry) < 0 && au_dbend(dentry) >= 0);
+               if (inode)
+                       err = au_refresh_hinode_self(inode);
+               au_dbg_verify_dinode(dentry);
+               if (!err)
+                       goto out_dgen; /* success */
+               goto out;
+       }
 
-       npositive = au_lkup_dentry(dentry, bstart, type, /*nd*/NULL);
-       if (npositive < 0)
+       /* temporary dinfo */
+       AuDbgDentry(dentry);
+       err = -ENOMEM;
+       tmp = au_di_alloc(sb, AuLsc_DI_TMP);
+       if (unlikely(!tmp))
+               goto out;
+       au_di_swap(tmp, dinfo);
+       /* returns the number of positive dentries */
+       /*
+        * if current working dir is removed, it returns an error.
+        * but the dentry is legal.
+        */
+       err = au_lkup_dentry(dentry, /*bstart*/0, /*type*/0, /*nd*/NULL);
+       AuDbgDentry(dentry);
+       au_di_swap(tmp, dinfo);
+       if (err == -ENOENT)
+               err = 0;
+       if (err >= 0) {
+               /* compare/refresh by dinfo */
+               AuDbgDentry(dentry);
+               err = au_refresh_by_dinfo(dentry, dinfo, tmp);
+               au_dbg_verify_dinode(dentry);
+               AuTraceErr(err);
+       }
+       au_rw_write_unlock(&tmp->di_rwsem);
+       au_di_free(tmp);
+       if (unlikely(err))
                goto out;
-       if (dinfo->di_bwh >= 0 && dinfo->di_bwh <= dinfo->di_bstart)
-               d_drop(dentry);
 
 out_dgen:
        au_update_digen(dentry);
 out:
-       dput(parent);
-       AuTraceErr(npositive);
-       return npositive;
+       if (unlikely(err && !(dentry->d_flags & DCACHE_NFSFS_RENAMED))) {
+               AuIOErr("failed refreshing %.*s, %d\n",
+                       AuDLNPair(dentry), err);
+               AuDbgDentry(dentry);
+       }
+       AuTraceErr(err);
+       return err;
 }
 
 static noinline_for_stack
@@ -706,29 +965,23 @@ static int h_d_revalidate(struct dentry *dentry, struct inode *inode,
        return err;
 }
 
+/* todo: consolidate with do_refresh() and au_reval_for_attr() */
 static int simple_reval_dpath(struct dentry *dentry, unsigned int sigen)
 {
        int err;
        struct dentry *parent;
-       struct inode *inode;
 
-       inode = dentry->d_inode;
-       if (au_digen(dentry) == sigen && au_iigen(inode) == sigen)
+       if (!au_digen_test(dentry, sigen))
                return 0;
 
        parent = dget_parent(dentry);
        di_read_lock_parent(parent, AuLock_IR);
-       AuDebugOn(au_digen(parent) != sigen
-                 || au_iigen(parent->d_inode) != sigen);
+       AuDebugOn(au_digen_test(parent, sigen));
        au_dbg_verify_gen(parent, sigen);
-
-       /* returns a number of positive dentries */
-       err = au_refresh_hdentry(dentry, inode->i_mode & S_IFMT);
-       if (err >= 0)
-               err = au_refresh_hinode(inode, dentry);
-
+       err = au_refresh_dentry(dentry, parent);
        di_read_unlock(parent, AuLock_IR);
        dput(parent);
+       AuTraceErr(err);
        return err;
 }
 
@@ -738,36 +991,35 @@ int au_reval_dpath(struct dentry *dentry, unsigned int sigen)
        struct dentry *d, *parent;
        struct inode *inode;
 
-       if (!au_ftest_si(au_sbi(dentry->d_sb), FAILED_REFRESH_DIRS))
+       if (!au_ftest_si(au_sbi(dentry->d_sb), FAILED_REFRESH_DIR))
                return simple_reval_dpath(dentry, sigen);
 
        /* slow loop, keep it simple and stupid */
        /* cf: au_cpup_dirs() */
        err = 0;
        parent = NULL;
-       while (au_digen(dentry) != sigen
-              || au_iigen(dentry->d_inode) != sigen) {
+       while (au_digen_test(dentry, sigen)) {
                d = dentry;
                while (1) {
                        dput(parent);
                        parent = dget_parent(d);
-                       if (au_digen(parent) == sigen
-                           && au_iigen(parent->d_inode) == sigen)
+                       if (!au_digen_test(parent, sigen))
                                break;
                        d = parent;
                }
 
                inode = d->d_inode;
                if (d != dentry)
-                       di_write_lock_child(d);
+                       di_write_lock_child2(d);
 
                /* someone might update our dentry while we were sleeping */
-               if (au_digen(d) != sigen || au_iigen(d->d_inode) != sigen) {
+               if (au_digen_test(d, sigen)) {
+                       /*
+                        * todo: consolidate with simple_reval_dpath(),
+                        * do_refresh() and au_reval_for_attr().
+                        */
                        di_read_lock_parent(parent, AuLock_IR);
-                       /* returns a number of positive dentries */
-                       err = au_refresh_hdentry(d, inode->i_mode & S_IFMT);
-                       if (err >= 0)
-                               err = au_refresh_hinode(inode, d);
+                       err = au_refresh_dentry(d, parent);
                        di_read_unlock(parent, AuLock_IR);
                }
 
@@ -792,6 +1044,10 @@ static int aufs_d_revalidate(struct dentry *dentry, struct nameidata *nd)
        struct super_block *sb;
        struct inode *inode;
 
+       valid = 0;
+       if (unlikely(!au_di(dentry)))
+               goto out;
+
        valid = 1;
        sb = dentry->d_sb;
        inode = dentry->d_inode;
@@ -803,38 +1059,48 @@ static int aufs_d_revalidate(struct dentry *dentry, struct nameidata *nd)
        err = aufs_read_lock(dentry, AuLock_FLUSH | AuLock_DW | AuLock_NOPLM);
        if (unlikely(err)) {
                valid = err;
+               AuTraceErr(err);
                goto out;
        }
-       sigen = au_sigen(sb);
-       if (au_digen(dentry) != sigen) {
-               AuDebugOn(IS_ROOT(dentry));
-               if (inode)
-                       err = au_reval_dpath(dentry, sigen);
-               if (unlikely(err))
-                       goto out_dgrade;
+       if (unlikely(au_dbrange_test(dentry))) {
+               err = -EINVAL;
+               AuTraceErr(err);
+               goto out_dgrade;
        }
-       if (inode && au_iigen(inode) != sigen) {
+
+       sigen = au_sigen(sb);
+       if (au_digen_test(dentry, sigen)) {
                AuDebugOn(IS_ROOT(dentry));
-               err = au_refresh_hinode(inode, dentry);
-               if (unlikely(err))
+               err = au_reval_dpath(dentry, sigen);
+               if (unlikely(err)) {
+                       AuTraceErr(err);
                        goto out_dgrade;
+               }
        }
        di_downgrade_lock(dentry, AuLock_IR);
 
        err = -EINVAL;
+       if (inode && (IS_DEADDIR(inode) || !inode->i_nlink))
+               goto out_inval;
+
        do_udba = !au_opt_test(au_mntflags(sb), UDBA_NONE);
        if (do_udba && inode) {
                aufs_bindex_t bstart = au_ibstart(inode);
+               struct inode *h_inode;
 
-               if (bstart >= 0
-                   && au_test_higen(inode, au_h_iptr(inode, bstart)))
-                       goto out_inval;
+               if (bstart >= 0) {
+                       h_inode = au_h_iptr(inode, bstart);
+                       if (h_inode && au_test_higen(inode, h_inode))
+                               goto out_inval;
+               }
        }
 
        err = h_d_revalidate(dentry, inode, nd, do_udba);
-       if (unlikely(!err && do_udba && au_dbstart(dentry) < 0))
-               /* both of real entry and whiteout found */
+       if (unlikely(!err && do_udba && au_dbstart(dentry) < 0)) {
                err = -EIO;
+               AuDbg("both of real entry and whiteout found, %.*s, err %d\n",
+                     AuDLNPair(dentry), err);
+       }
        goto out_inval;
 
 out_dgrade:
@@ -844,14 +1110,16 @@ out_inval:
        AuTraceErr(err);
        valid = !err;
 out:
-       if (!valid)
+       if (!valid) {
                AuDbg("%.*s invalid, %d\n", AuDLNPair(dentry), valid);
+               d_drop(dentry);
+       }
        return valid;
 }
 
 static void aufs_d_release(struct dentry *dentry)
 {
-       if (dentry->d_fsdata) {
+       if (au_di(dentry)) {
                au_di_fin(dentry);
                au_hn_di_reinit(dentry);
        }