UBIFS: pre-allocate bulk-read buffer
[pandora-kernel.git] / fs / ubifs / gc.c
index 13f1019..0bef650 100644 (file)
@@ -95,6 +95,48 @@ static int switch_gc_head(struct ubifs_info *c)
        return err;
 }
 
+/**
+ * joinup - bring data nodes for an inode together.
+ * @c: UBIFS file-system description object
+ * @sleb: describes scanned LEB
+ * @inum: inode number
+ * @blk: block number
+ * @data: list to which to add data nodes
+ *
+ * This function looks at the first few nodes in the scanned LEB @sleb and adds
+ * them to @data if they are data nodes from @inum and have a larger block
+ * number than @blk. This function returns %0 on success and a negative error
+ * code on failure.
+ */
+static int joinup(struct ubifs_info *c, struct ubifs_scan_leb *sleb, ino_t inum,
+                 unsigned int blk, struct list_head *data)
+{
+       int err, cnt = 6, lnum = sleb->lnum, offs;
+       struct ubifs_scan_node *snod, *tmp;
+       union ubifs_key *key;
+
+       list_for_each_entry_safe(snod, tmp, &sleb->nodes, list) {
+               key = &snod->key;
+               if (key_inum(c, key) == inum &&
+                   key_type(c, key) == UBIFS_DATA_KEY &&
+                   key_block(c, key) > blk) {
+                       offs = snod->offs;
+                       err = ubifs_tnc_has_node(c, key, 0, lnum, offs, 0);
+                       if (err < 0)
+                               return err;
+                       list_del(&snod->list);
+                       if (err) {
+                               list_add_tail(&snod->list, data);
+                               blk = key_block(c, key);
+                       } else
+                               kfree(snod);
+                       cnt = 6;
+               } else if (--cnt == 0)
+                       break;
+       }
+       return 0;
+}
+
 /**
  * move_nodes - move nodes.
  * @c: UBIFS file-system description object
@@ -116,16 +158,21 @@ static int switch_gc_head(struct ubifs_info *c)
 static int move_nodes(struct ubifs_info *c, struct ubifs_scan_leb *sleb)
 {
        struct ubifs_scan_node *snod, *tmp;
-       struct list_head large, medium, small;
+       struct list_head data, large, medium, small;
        struct ubifs_wbuf *wbuf = &c->jheads[GCHD].wbuf;
        int avail, err, min = INT_MAX;
+       unsigned int blk = 0;
+       ino_t inum = 0;
 
+       INIT_LIST_HEAD(&data);
        INIT_LIST_HEAD(&large);
        INIT_LIST_HEAD(&medium);
        INIT_LIST_HEAD(&small);
 
-       list_for_each_entry_safe(snod, tmp, &sleb->nodes, list) {
-               struct list_head *lst;
+       while (!list_empty(&sleb->nodes)) {
+               struct list_head *lst = sleb->nodes.next;
+
+               snod = list_entry(lst, struct ubifs_scan_node, list);
 
                ubifs_assert(snod->type != UBIFS_IDX_NODE);
                ubifs_assert(snod->type != UBIFS_REF_NODE);
@@ -136,7 +183,6 @@ static int move_nodes(struct ubifs_info *c, struct ubifs_scan_leb *sleb)
                if (err < 0)
                        goto out;
 
-               lst = &snod->list;
                list_del(lst);
                if (!err) {
                        /* The node is obsolete, remove it from the list */
@@ -145,15 +191,30 @@ static int move_nodes(struct ubifs_info *c, struct ubifs_scan_leb *sleb)
                }
 
                /*
-                * Sort the list of nodes so that large nodes go first, and
-                * small nodes go last.
+                * Sort the list of nodes so that data nodes go first, large
+                * nodes go second, and small nodes go last.
                 */
-               if (snod->len > MEDIUM_NODE_WM)
-                       list_add(lst, &large);
+               if (key_type(c, &snod->key) == UBIFS_DATA_KEY) {
+                       if (inum != key_inum(c, &snod->key)) {
+                               if (inum) {
+                                       /*
+                                        * Try to move data nodes from the same
+                                        * inode together.
+                                        */
+                                       err = joinup(c, sleb, inum, blk, &data);
+                                       if (err)
+                                               goto out;
+                               }
+                               inum = key_inum(c, &snod->key);
+                               blk = key_block(c, &snod->key);
+                       }
+                       list_add_tail(lst, &data);
+               } else if (snod->len > MEDIUM_NODE_WM)
+                       list_add_tail(lst, &large);
                else if (snod->len > SMALL_NODE_WM)
-                       list_add(lst, &medium);
+                       list_add_tail(lst, &medium);
                else
-                       list_add(lst, &small);
+                       list_add_tail(lst, &small);
 
                /* And find the smallest node */
                if (snod->len < min)
@@ -164,6 +225,7 @@ static int move_nodes(struct ubifs_info *c, struct ubifs_scan_leb *sleb)
         * Join the tree lists so that we'd have one roughly sorted list
         * ('large' will be the head of the joined list).
         */
+       list_splice(&data, &large);
        list_splice(&medium, large.prev);
        list_splice(&small, large.prev);
 
@@ -334,15 +396,15 @@ int ubifs_garbage_collect_leb(struct ubifs_info *c, struct ubifs_lprops *lp)
 
                err = move_nodes(c, sleb);
                if (err)
-                       goto out;
+                       goto out_inc_seq;
 
                err = gc_sync_wbufs(c);
                if (err)
-                       goto out;
+                       goto out_inc_seq;
 
                err = ubifs_change_one_lp(c, lnum, c->leb_size, 0, 0, 0, 0);
                if (err)
-                       goto out;
+                       goto out_inc_seq;
 
                /* Allow for races with TNC */
                c->gced_lnum = lnum;
@@ -369,6 +431,14 @@ int ubifs_garbage_collect_leb(struct ubifs_info *c, struct ubifs_lprops *lp)
 out:
        ubifs_scan_destroy(sleb);
        return err;
+
+out_inc_seq:
+       /* We may have moved at least some nodes so allow for races with TNC */
+       c->gced_lnum = lnum;
+       smp_wmb();
+       c->gc_seq += 1;
+       smp_wmb();
+       goto out;
 }
 
 /**
@@ -645,7 +715,7 @@ int ubifs_gc_start_commit(struct ubifs_info *c)
         */
        while (1) {
                lp = ubifs_fast_find_freeable(c);
-               if (unlikely(IS_ERR(lp))) {
+               if (IS_ERR(lp)) {
                        err = PTR_ERR(lp);
                        goto out;
                }
@@ -657,7 +727,7 @@ int ubifs_gc_start_commit(struct ubifs_info *c)
                if (err)
                        goto out;
                lp = ubifs_change_lp(c, lp, c->leb_size, 0, lp->flags, 0);
-               if (unlikely(IS_ERR(lp))) {
+               if (IS_ERR(lp)) {
                        err = PTR_ERR(lp);
                        goto out;
                }
@@ -672,7 +742,7 @@ int ubifs_gc_start_commit(struct ubifs_info *c)
        /* Record index freeable LEBs for unmapping after commit */
        while (1) {
                lp = ubifs_fast_find_frdi_idx(c);
-               if (unlikely(IS_ERR(lp))) {
+               if (IS_ERR(lp)) {
                        err = PTR_ERR(lp);
                        goto out;
                }
@@ -688,7 +758,7 @@ int ubifs_gc_start_commit(struct ubifs_info *c)
                /* Don't release the LEB until after the next commit */
                flags = (lp->flags | LPROPS_TAKEN) ^ LPROPS_INDEX;
                lp = ubifs_change_lp(c, lp, c->leb_size, 0, flags, 1);
-               if (unlikely(IS_ERR(lp))) {
+               if (IS_ERR(lp)) {
                        err = PTR_ERR(lp);
                        kfree(idx_gc);
                        goto out;