nilfs2: implement resize ioctl
[cascardo/linux.git] / fs / nilfs2 / sufile.c
index 22f2e6e..37b9631 100644 (file)
@@ -721,6 +721,73 @@ out:
        return ret;
 }
 
+/**
+ * nilfs_sufile_resize - resize segment array
+ * @sufile: inode of segment usage file
+ * @newnsegs: new number of segments
+ *
+ * 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.
+ *
+ * %-ENOSPC - Enough free space is not left for shrinking
+ *
+ * %-EBUSY - Dirty or active segments exist in the region to be truncated
+ */
+int nilfs_sufile_resize(struct inode *sufile, __u64 newnsegs)
+{
+       struct the_nilfs *nilfs = sufile->i_sb->s_fs_info;
+       struct buffer_head *header_bh;
+       struct nilfs_sufile_header *header;
+       struct nilfs_sufile_info *sui = NILFS_SUI(sufile);
+       void *kaddr;
+       unsigned long nsegs, nrsvsegs;
+       int ret = 0;
+
+       down_write(&NILFS_MDT(sufile)->mi_sem);
+
+       nsegs = nilfs_sufile_get_nsegments(sufile);
+       if (nsegs == newnsegs)
+               goto out;
+
+       ret = -ENOSPC;
+       nrsvsegs = nilfs_nrsvsegs(nilfs, newnsegs);
+       if (newnsegs < nsegs && nsegs - newnsegs + nrsvsegs > sui->ncleansegs)
+               goto out;
+
+       ret = nilfs_sufile_get_header_block(sufile, &header_bh);
+       if (ret < 0)
+               goto out;
+
+       if (newnsegs > nsegs) {
+               sui->ncleansegs += newnsegs - nsegs;
+       } else /* newnsegs < nsegs */ {
+               ret = nilfs_sufile_truncate_range(sufile, newnsegs, nsegs - 1);
+               if (ret < 0)
+                       goto out_header;
+
+               sui->ncleansegs -= nsegs - newnsegs;
+       }
+
+       kaddr = kmap_atomic(header_bh->b_page, KM_USER0);
+       header = kaddr + bh_offset(header_bh);
+       header->sh_ncleansegs = cpu_to_le64(sui->ncleansegs);
+       kunmap_atomic(kaddr, KM_USER0);
+
+       nilfs_mdt_mark_buffer_dirty(header_bh);
+       nilfs_mdt_mark_dirty(sufile);
+       nilfs_set_nsegments(nilfs, newnsegs);
+
+out_header:
+       brelse(header_bh);
+out:
+       up_write(&NILFS_MDT(sufile)->mi_sem);
+       return ret;
+}
+
 /**
  * nilfs_sufile_get_suinfo -
  * @sufile: inode of segment usage file