nilfs2: stop zero-fill of btree path just before free it
[safe/jmp/linux-2.6] / fs / nilfs2 / sufile.c
index 8b2f93c..37994d4 100644 (file)
@@ -18,6 +18,7 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  *
  * Written by Koji Sato <koji@osrg.net>.
+ * Rivised by Ryusuke Konishi <ryusuke@osrg.net>.
  */
 
 #include <linux/kernel.h>
@@ -93,6 +94,148 @@ nilfs_sufile_get_segment_usage_block(struct inode *sufile, __u64 segnum,
                                   create, NULL, bhp);
 }
 
+static void nilfs_sufile_mod_counter(struct buffer_head *header_bh,
+                                    u64 ncleanadd, u64 ndirtyadd)
+{
+       struct nilfs_sufile_header *header;
+       void *kaddr;
+
+       kaddr = kmap_atomic(header_bh->b_page, KM_USER0);
+       header = kaddr + bh_offset(header_bh);
+       le64_add_cpu(&header->sh_ncleansegs, ncleanadd);
+       le64_add_cpu(&header->sh_ndirtysegs, ndirtyadd);
+       kunmap_atomic(kaddr, KM_USER0);
+
+       nilfs_mdt_mark_buffer_dirty(header_bh);
+}
+
+/**
+ * nilfs_sufile_updatev - modify multiple segment usages at a time
+ * @sufile: inode of segment usage file
+ * @segnumv: array of segment numbers
+ * @nsegs: size of @segnumv array
+ * @create: creation flag
+ * @ndone: place to store number of modified segments on @segnumv
+ * @dofunc: primitive operation for the update
+ *
+ * Description: nilfs_sufile_updatev() repeatedly calls @dofunc
+ * against the given array of segments.  The @dofunc is called with
+ * buffers of a header block and the sufile block in which the target
+ * segment usage entry is contained.  If @ndone is given, the number
+ * of successfully modified segments from the head is stored in the
+ * place @ndone points to.
+ *
+ * Return Value: On success, zero is returned.  On error, one of the
+ * following negative error codes is returned.
+ *
+ * %-EIO - I/O error.
+ *
+ * %-ENOMEM - Insufficient amount of memory available.
+ *
+ * %-ENOENT - Given segment usage is in hole block (may be returned if
+ *            @create is zero)
+ *
+ * %-EINVAL - Invalid segment usage number
+ */
+int nilfs_sufile_updatev(struct inode *sufile, __u64 *segnumv, size_t nsegs,
+                        int create, size_t *ndone,
+                        void (*dofunc)(struct inode *, __u64,
+                                       struct buffer_head *,
+                                       struct buffer_head *))
+{
+       struct buffer_head *header_bh, *bh;
+       unsigned long blkoff, prev_blkoff;
+       __u64 *seg;
+       size_t nerr = 0, n = 0;
+       int ret = 0;
+
+       if (unlikely(nsegs == 0))
+               goto out;
+
+       down_write(&NILFS_MDT(sufile)->mi_sem);
+       for (seg = segnumv; seg < segnumv + nsegs; seg++) {
+               if (unlikely(*seg >= nilfs_sufile_get_nsegments(sufile))) {
+                       printk(KERN_WARNING
+                              "%s: invalid segment number: %llu\n", __func__,
+                              (unsigned long long)*seg);
+                       nerr++;
+               }
+       }
+       if (nerr > 0) {
+               ret = -EINVAL;
+               goto out_sem;
+       }
+
+       ret = nilfs_sufile_get_header_block(sufile, &header_bh);
+       if (ret < 0)
+               goto out_sem;
+
+       seg = segnumv;
+       blkoff = nilfs_sufile_get_blkoff(sufile, *seg);
+       ret = nilfs_mdt_get_block(sufile, blkoff, create, NULL, &bh);
+       if (ret < 0)
+               goto out_header;
+
+       for (;;) {
+               dofunc(sufile, *seg, header_bh, bh);
+
+               if (++seg >= segnumv + nsegs)
+                       break;
+               prev_blkoff = blkoff;
+               blkoff = nilfs_sufile_get_blkoff(sufile, *seg);
+               if (blkoff == prev_blkoff)
+                       continue;
+
+               /* get different block */
+               brelse(bh);
+               ret = nilfs_mdt_get_block(sufile, blkoff, create, NULL, &bh);
+               if (unlikely(ret < 0))
+                       goto out_header;
+       }
+       brelse(bh);
+
+ out_header:
+       n = seg - segnumv;
+       brelse(header_bh);
+ out_sem:
+       up_write(&NILFS_MDT(sufile)->mi_sem);
+ out:
+       if (ndone)
+               *ndone = n;
+       return ret;
+}
+
+int nilfs_sufile_update(struct inode *sufile, __u64 segnum, int create,
+                       void (*dofunc)(struct inode *, __u64,
+                                      struct buffer_head *,
+                                      struct buffer_head *))
+{
+       struct buffer_head *header_bh, *bh;
+       int ret;
+
+       if (unlikely(segnum >= nilfs_sufile_get_nsegments(sufile))) {
+               printk(KERN_WARNING "%s: invalid segment number: %llu\n",
+                      __func__, (unsigned long long)segnum);
+               return -EINVAL;
+       }
+       down_write(&NILFS_MDT(sufile)->mi_sem);
+
+       ret = nilfs_sufile_get_header_block(sufile, &header_bh);
+       if (ret < 0)
+               goto out_sem;
+
+       ret = nilfs_sufile_get_segment_usage_block(sufile, segnum, create, &bh);
+       if (!ret) {
+               dofunc(sufile, segnum, header_bh, bh);
+               brelse(bh);
+       }
+       brelse(header_bh);
+
+ out_sem:
+       up_write(&NILFS_MDT(sufile)->mi_sem);
+       return ret;
+}
+
 /**
  * nilfs_sufile_alloc - allocate a segment
  * @sufile: inode of segment usage file
@@ -113,7 +256,6 @@ nilfs_sufile_get_segment_usage_block(struct inode *sufile, __u64 segnum,
 int nilfs_sufile_alloc(struct inode *sufile, __u64 *segnump)
 {
        struct buffer_head *header_bh, *su_bh;
-       struct the_nilfs *nilfs;
        struct nilfs_sufile_header *header;
        struct nilfs_segment_usage *su;
        size_t susz = NILFS_MDT(sufile)->mi_entry_size;
@@ -124,8 +266,6 @@ int nilfs_sufile_alloc(struct inode *sufile, __u64 *segnump)
 
        down_write(&NILFS_MDT(sufile)->mi_sem);
 
-       nilfs = NILFS_MDT(sufile)->mi_nilfs;
-
        ret = nilfs_sufile_get_header_block(sufile, &header_bh);
        if (ret < 0)
                goto out_sem;
@@ -192,165 +332,84 @@ int nilfs_sufile_alloc(struct inode *sufile, __u64 *segnump)
        return ret;
 }
 
-/**
- * nilfs_sufile_cancel_free -
- * @sufile: inode of segment usage file
- * @segnum: segment number
- *
- * Description:
- *
- * Return Value: On success, 0 is returned. On error, one of the following
- * negative error codes is returned.
- *
- * %-EIO - I/O error.
- *
- * %-ENOMEM - Insufficient amount of memory available.
- */
-int nilfs_sufile_cancel_free(struct inode *sufile, __u64 segnum)
+void nilfs_sufile_do_cancel_free(struct inode *sufile, __u64 segnum,
+                                struct buffer_head *header_bh,
+                                struct buffer_head *su_bh)
 {
-       struct buffer_head *header_bh, *su_bh;
-       struct the_nilfs *nilfs;
-       struct nilfs_sufile_header *header;
        struct nilfs_segment_usage *su;
        void *kaddr;
-       int ret;
-
-       down_write(&NILFS_MDT(sufile)->mi_sem);
-
-       nilfs = NILFS_MDT(sufile)->mi_nilfs;
-
-       ret = nilfs_sufile_get_header_block(sufile, &header_bh);
-       if (ret < 0)
-               goto out_sem;
-
-       ret = nilfs_sufile_get_segment_usage_block(sufile, segnum, 0, &su_bh);
-       if (ret < 0)
-               goto out_header;
 
        kaddr = kmap_atomic(su_bh->b_page, KM_USER0);
-       su = nilfs_sufile_block_get_segment_usage(
-               sufile, segnum, su_bh, kaddr);
+       su = nilfs_sufile_block_get_segment_usage(sufile, segnum, su_bh, kaddr);
        if (unlikely(!nilfs_segment_usage_clean(su))) {
                printk(KERN_WARNING "%s: segment %llu must be clean\n",
                       __func__, (unsigned long long)segnum);
                kunmap_atomic(kaddr, KM_USER0);
-               goto out_su_bh;
+               return;
        }
        nilfs_segment_usage_set_dirty(su);
        kunmap_atomic(kaddr, KM_USER0);
 
-       kaddr = kmap_atomic(header_bh->b_page, KM_USER0);
-       header = nilfs_sufile_block_get_header(sufile, header_bh, kaddr);
-       le64_add_cpu(&header->sh_ncleansegs, -1);
-       le64_add_cpu(&header->sh_ndirtysegs, 1);
-       kunmap_atomic(kaddr, KM_USER0);
-
-       nilfs_mdt_mark_buffer_dirty(header_bh);
+       nilfs_sufile_mod_counter(header_bh, -1, 1);
        nilfs_mdt_mark_buffer_dirty(su_bh);
        nilfs_mdt_mark_dirty(sufile);
-
- out_su_bh:
-       brelse(su_bh);
- out_header:
-       brelse(header_bh);
- out_sem:
-       up_write(&NILFS_MDT(sufile)->mi_sem);
-       return ret;
 }
 
-/**
- * nilfs_sufile_freev - free segments
- * @sufile: inode of segment usage file
- * @segnum: array of segment numbers
- * @nsegs: number of segments
- *
- * Description: nilfs_sufile_freev() frees segments specified by @segnum and
- * @nsegs, which must have been returned by a previous call to
- * nilfs_sufile_alloc().
- *
- * Return Value: On success, 0 is returned. On error, one of the following
- * negative error codes is returned.
- *
- * %-EIO - I/O error.
- *
- * %-ENOMEM - Insufficient amount of memory available.
- */
-#define NILFS_SUFILE_FREEV_PREALLOC    16
-int nilfs_sufile_freev(struct inode *sufile, __u64 *segnum, size_t nsegs)
+void nilfs_sufile_do_scrap(struct inode *sufile, __u64 segnum,
+                          struct buffer_head *header_bh,
+                          struct buffer_head *su_bh)
 {
-       struct buffer_head *header_bh, **su_bh,
-               *su_bh_prealloc[NILFS_SUFILE_FREEV_PREALLOC];
-       struct the_nilfs *nilfs;
-       struct nilfs_sufile_header *header;
        struct nilfs_segment_usage *su;
        void *kaddr;
-       int ret, i;
-
-       down_write(&NILFS_MDT(sufile)->mi_sem);
-
-       nilfs = NILFS_MDT(sufile)->mi_nilfs;
+       int clean, dirty;
 
-       /* prepare resources */
-       if (nsegs <= NILFS_SUFILE_FREEV_PREALLOC)
-               su_bh = su_bh_prealloc;
-       else {
-               su_bh = kmalloc(sizeof(*su_bh) * nsegs, GFP_NOFS);
-               if (su_bh == NULL) {
-                       ret = -ENOMEM;
-                       goto out_sem;
-               }
-       }
-
-       ret = nilfs_sufile_get_header_block(sufile, &header_bh);
-       if (ret < 0)
-               goto out_su_bh;
-       for (i = 0; i < nsegs; i++) {
-               ret = nilfs_sufile_get_segment_usage_block(sufile, segnum[i],
-                                                          0, &su_bh[i]);
-               if (ret < 0)
-                       goto out_bh;
-       }
-
-       /* free segments */
-       for (i = 0; i < nsegs; i++) {
-               kaddr = kmap_atomic(su_bh[i]->b_page, KM_USER0);
-               su = nilfs_sufile_block_get_segment_usage(
-                       sufile, segnum[i], su_bh[i], kaddr);
-               WARN_ON(nilfs_segment_usage_error(su));
-               nilfs_segment_usage_set_clean(su);
+       kaddr = kmap_atomic(su_bh->b_page, KM_USER0);
+       su = nilfs_sufile_block_get_segment_usage(sufile, segnum, su_bh, kaddr);
+       if (su->su_flags == cpu_to_le32(1UL << NILFS_SEGMENT_USAGE_DIRTY) &&
+           su->su_nblocks == cpu_to_le32(0)) {
                kunmap_atomic(kaddr, KM_USER0);
-               nilfs_mdt_mark_buffer_dirty(su_bh[i]);
+               return;
        }
-       kaddr = kmap_atomic(header_bh->b_page, KM_USER0);
-       header = nilfs_sufile_block_get_header(sufile, header_bh, kaddr);
-       le64_add_cpu(&header->sh_ncleansegs, nsegs);
-       le64_add_cpu(&header->sh_ndirtysegs, -(u64)nsegs);
+       clean = nilfs_segment_usage_clean(su);
+       dirty = nilfs_segment_usage_dirty(su);
+
+       /* make the segment garbage */
+       su->su_lastmod = cpu_to_le64(0);
+       su->su_nblocks = cpu_to_le32(0);
+       su->su_flags = cpu_to_le32(1UL << NILFS_SEGMENT_USAGE_DIRTY);
        kunmap_atomic(kaddr, KM_USER0);
-       nilfs_mdt_mark_buffer_dirty(header_bh);
+
+       nilfs_sufile_mod_counter(header_bh, clean ? (u64)-1 : 0, dirty ? 0 : 1);
+       nilfs_mdt_mark_buffer_dirty(su_bh);
        nilfs_mdt_mark_dirty(sufile);
+}
 
- out_bh:
-       for (i--; i >= 0; i--)
-               brelse(su_bh[i]);
-       brelse(header_bh);
+void nilfs_sufile_do_free(struct inode *sufile, __u64 segnum,
+                         struct buffer_head *header_bh,
+                         struct buffer_head *su_bh)
+{
+       struct nilfs_segment_usage *su;
+       void *kaddr;
+       int sudirty;
 
- out_su_bh:
-       if (su_bh != su_bh_prealloc)
-               kfree(su_bh);
+       kaddr = kmap_atomic(su_bh->b_page, KM_USER0);
+       su = nilfs_sufile_block_get_segment_usage(sufile, segnum, su_bh, kaddr);
+       if (nilfs_segment_usage_clean(su)) {
+               printk(KERN_WARNING "%s: segment %llu is already clean\n",
+                      __func__, (unsigned long long)segnum);
+               kunmap_atomic(kaddr, KM_USER0);
+               return;
+       }
+       WARN_ON(nilfs_segment_usage_error(su));
+       WARN_ON(!nilfs_segment_usage_dirty(su));
 
- out_sem:
-       up_write(&NILFS_MDT(sufile)->mi_sem);
-       return ret;
-}
+       sudirty = nilfs_segment_usage_dirty(su);
+       nilfs_segment_usage_set_clean(su);
+       kunmap_atomic(kaddr, KM_USER0);
+       nilfs_mdt_mark_buffer_dirty(su_bh);
 
-/**
- * nilfs_sufile_free -
- * @sufile:
- * @segnum:
- */
-int nilfs_sufile_free(struct inode *sufile, __u64 segnum)
-{
-       return nilfs_sufile_freev(sufile, &segnum, 1);
+       nilfs_sufile_mod_counter(header_bh, 1, sudirty ? (u64)-1 : 0);
+       nilfs_mdt_mark_dirty(sufile);
 }
 
 /**
@@ -500,82 +559,36 @@ int nilfs_sufile_get_ncleansegs(struct inode *sufile, unsigned long *nsegsp)
        return ret;
 }
 
-/**
- * nilfs_sufile_set_error - mark a segment as erroneous
- * @sufile: inode of segment usage file
- * @segnum: segment number
- *
- * Description: nilfs_sufile_set_error() marks the segment specified by
- * @segnum as erroneous. The error segment will never be used again.
- *
- * Return Value: On success, 0 is returned. On error, one of the following
- * negative error codes is returned.
- *
- * %-EIO - I/O error.
- *
- * %-ENOMEM - Insufficient amount of memory available.
- *
- * %-EINVAL - Invalid segment usage number.
- */
-int nilfs_sufile_set_error(struct inode *sufile, __u64 segnum)
+void nilfs_sufile_do_set_error(struct inode *sufile, __u64 segnum,
+                              struct buffer_head *header_bh,
+                              struct buffer_head *su_bh)
 {
-       struct buffer_head *header_bh, *su_bh;
        struct nilfs_segment_usage *su;
-       struct nilfs_sufile_header *header;
        void *kaddr;
-       int suclean, ret;
-
-       if (unlikely(segnum >= nilfs_sufile_get_nsegments(sufile))) {
-               printk(KERN_WARNING "%s: invalid segment number: %llu\n",
-                      __func__, (unsigned long long)segnum);
-               return -EINVAL;
-       }
-       down_write(&NILFS_MDT(sufile)->mi_sem);
-
-       ret = nilfs_sufile_get_header_block(sufile, &header_bh);
-       if (ret < 0)
-               goto out_sem;
-       ret = nilfs_sufile_get_segment_usage_block(sufile, segnum, 0, &su_bh);
-       if (ret < 0)
-               goto out_header;
+       int suclean;
 
        kaddr = kmap_atomic(su_bh->b_page, KM_USER0);
        su = nilfs_sufile_block_get_segment_usage(sufile, segnum, su_bh, kaddr);
        if (nilfs_segment_usage_error(su)) {
                kunmap_atomic(kaddr, KM_USER0);
-               brelse(su_bh);
-               goto out_header;
+               return;
        }
        suclean = nilfs_segment_usage_clean(su);
-
        nilfs_segment_usage_set_error(su);
        kunmap_atomic(kaddr, KM_USER0);
 
-       if (suclean) {
-               kaddr = kmap_atomic(header_bh->b_page, KM_USER0);
-               header = nilfs_sufile_block_get_header(sufile, header_bh,
-                                                      kaddr);
-               le64_add_cpu(&header->sh_ncleansegs, -1);
-               kunmap_atomic(kaddr, KM_USER0);
-               nilfs_mdt_mark_buffer_dirty(header_bh);
-       }
+       if (suclean)
+               nilfs_sufile_mod_counter(header_bh, -1, 0);
        nilfs_mdt_mark_buffer_dirty(su_bh);
        nilfs_mdt_mark_dirty(sufile);
-       brelse(su_bh);
-
- out_header:
-       brelse(header_bh);
-
- out_sem:
-       up_write(&NILFS_MDT(sufile)->mi_sem);
-       return ret;
 }
 
 /**
  * nilfs_sufile_get_suinfo -
  * @sufile: inode of segment usage file
  * @segnum: segment number to start looking
- * @si: array of suinfo
+ * @buf: array of suinfo
+ * @sisz: byte size of suinfo
  * @nsi: size of suinfo array
  *
  * Description:
@@ -587,11 +600,12 @@ int nilfs_sufile_set_error(struct inode *sufile, __u64 segnum)
  *
  * %-ENOMEM - Insufficient amount of memory available.
  */
-ssize_t nilfs_sufile_get_suinfo(struct inode *sufile, __u64 segnum,
-                               struct nilfs_suinfo *si, size_t nsi)
+ssize_t nilfs_sufile_get_suinfo(struct inode *sufile, __u64 segnum, void *buf,
+                               unsigned sisz, size_t nsi)
 {
        struct buffer_head *su_bh;
        struct nilfs_segment_usage *su;
+       struct nilfs_suinfo *si = buf;
        size_t susz = NILFS_MDT(sufile)->mi_entry_size;
        struct the_nilfs *nilfs = NILFS_MDT(sufile)->mi_nilfs;
        void *kaddr;
@@ -616,20 +630,22 @@ ssize_t nilfs_sufile_get_suinfo(struct inode *sufile, __u64 segnum,
                        if (ret != -ENOENT)
                                goto out;
                        /* hole */
-                       memset(&si[i], 0, sizeof(struct nilfs_suinfo) * n);
+                       memset(si, 0, sisz * n);
+                       si = (void *)si + sisz * n;
                        continue;
                }
 
                kaddr = kmap_atomic(su_bh->b_page, KM_USER0);
                su = nilfs_sufile_block_get_segment_usage(
                        sufile, segnum, su_bh, kaddr);
-               for (j = 0; j < n; j++, su = (void *)su + susz) {
-                       si[i + j].sui_lastmod = le64_to_cpu(su->su_lastmod);
-                       si[i + j].sui_nblocks = le32_to_cpu(su->su_nblocks);
-                       si[i + j].sui_flags = le32_to_cpu(su->su_flags) &
+               for (j = 0; j < n;
+                    j++, su = (void *)su + susz, si = (void *)si + sisz) {
+                       si->sui_lastmod = le64_to_cpu(su->su_lastmod);
+                       si->sui_nblocks = le32_to_cpu(su->su_nblocks);
+                       si->sui_flags = le32_to_cpu(su->su_flags) &
                                ~(1UL << NILFS_SEGMENT_USAGE_ACTIVE);
                        if (nilfs_segment_is_active(nilfs, segnum + j))
-                               si[i + j].sui_flags |=
+                               si->sui_flags |=
                                        (1UL << NILFS_SEGMENT_USAGE_ACTIVE);
                }
                kunmap_atomic(kaddr, KM_USER0);