gfs2: Implement fallocate(FALLOC_FL_PUNCH_HOLE)
authorAndreas Gruenbacher <agruenba@redhat.com>
Thu, 14 Dec 2017 16:11:03 +0000 (17:11 +0100)
committerAndreas Gruenbacher <agruenba@redhat.com>
Thu, 18 Jan 2018 20:15:58 +0000 (21:15 +0100)
Implement the top-level bits of punching a hole into a file.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
Signed-off-by: Bob Peterson <rpeterso@redhat.com>
fs/gfs2/bmap.c
fs/gfs2/bmap.h
fs/gfs2/file.c

index f6dbd2f400cc0f9c4ee28e8f46947090e17059d2..2ec11981e6945e5f4a8d2f0b2c5689e8bdc9aedf 100644 (file)
@@ -1351,7 +1351,7 @@ static int punch_hole(struct gfs2_inode *ip, u64 offset, u64 length)
        u64 lblock = (offset + (1 << bsize_shift) - 1) >> bsize_shift;
        __u16 start_list[GFS2_MAX_META_HEIGHT];
        __u16 __end_list[GFS2_MAX_META_HEIGHT], *end_list = NULL;
-       unsigned int start_aligned, end_aligned;
+       unsigned int start_aligned, uninitialized_var(end_aligned);
        unsigned int strip_h = ip->i_height - 1;
        u32 btotal = 0;
        int ret, state;
@@ -1956,3 +1956,123 @@ int gfs2_write_alloc_required(struct gfs2_inode *ip, u64 offset,
        return 0;
 }
 
+static int stuffed_zero_range(struct inode *inode, loff_t offset, loff_t length)
+{
+       struct gfs2_inode *ip = GFS2_I(inode);
+       struct buffer_head *dibh;
+       int error;
+
+       if (offset >= inode->i_size)
+               return 0;
+       if (offset + length > inode->i_size)
+               length = inode->i_size - offset;
+
+       error = gfs2_meta_inode_buffer(ip, &dibh);
+       if (error)
+               return error;
+       gfs2_trans_add_meta(ip->i_gl, dibh);
+       memset(dibh->b_data + sizeof(struct gfs2_dinode) + offset, 0,
+              length);
+       brelse(dibh);
+       return 0;
+}
+
+static int gfs2_journaled_truncate_range(struct inode *inode, loff_t offset,
+                                        loff_t length)
+{
+       struct gfs2_sbd *sdp = GFS2_SB(inode);
+       loff_t max_chunk = GFS2_JTRUNC_REVOKES * sdp->sd_vfs->s_blocksize;
+       int error;
+
+       while (length) {
+               struct gfs2_trans *tr;
+               loff_t chunk;
+               unsigned int offs;
+
+               chunk = length;
+               if (chunk > max_chunk)
+                       chunk = max_chunk;
+
+               offs = offset & ~PAGE_MASK;
+               if (offs && chunk > PAGE_SIZE)
+                       chunk = offs + ((chunk - offs) & PAGE_MASK);
+
+               truncate_pagecache_range(inode, offset, chunk);
+               offset += chunk;
+               length -= chunk;
+
+               tr = current->journal_info;
+               if (!test_bit(TR_TOUCHED, &tr->tr_flags))
+                       continue;
+
+               gfs2_trans_end(sdp);
+               error = gfs2_trans_begin(sdp, RES_DINODE, GFS2_JTRUNC_REVOKES);
+               if (error)
+                       return error;
+       }
+       return 0;
+}
+
+int __gfs2_punch_hole(struct file *file, loff_t offset, loff_t length)
+{
+       struct inode *inode = file_inode(file);
+       struct gfs2_inode *ip = GFS2_I(inode);
+       struct gfs2_sbd *sdp = GFS2_SB(inode);
+       int error;
+
+       if (gfs2_is_jdata(ip))
+               error = gfs2_trans_begin(sdp, RES_DINODE + 2 * RES_JDATA,
+                                        GFS2_JTRUNC_REVOKES);
+       else
+               error = gfs2_trans_begin(sdp, RES_DINODE, 0);
+       if (error)
+               return error;
+
+       if (gfs2_is_stuffed(ip)) {
+               error = stuffed_zero_range(inode, offset, length);
+               if (error)
+                       goto out;
+       } else {
+               unsigned int start_off, end_off, blocksize;
+
+               blocksize = i_blocksize(inode);
+               start_off = offset & (blocksize - 1);
+               end_off = (offset + length) & (blocksize - 1);
+               if (start_off) {
+                       unsigned int len = length;
+                       if (length > blocksize - start_off)
+                               len = blocksize - start_off;
+                       error = gfs2_block_zero_range(inode, offset, len);
+                       if (error)
+                               goto out;
+                       if (start_off + length < blocksize)
+                               end_off = 0;
+               }
+               if (end_off) {
+                       error = gfs2_block_zero_range(inode,
+                               offset + length - end_off, end_off);
+                       if (error)
+                               goto out;
+               }
+       }
+
+       if (gfs2_is_jdata(ip)) {
+               BUG_ON(!current->journal_info);
+               gfs2_journaled_truncate_range(inode, offset, length);
+       } else
+               truncate_pagecache_range(inode, offset, offset + length - 1);
+
+       file_update_time(file);
+       mark_inode_dirty(inode);
+
+       if (current->journal_info)
+               gfs2_trans_end(sdp);
+
+       if (!gfs2_is_stuffed(ip))
+               error = punch_hole(ip, offset, length);
+
+out:
+       if (current->journal_info)
+               gfs2_trans_end(sdp);
+       return error;
+}
index 443cc182cf18ca552d7ae314a023e3369eef70f4..c3402fe0065366ba59a4331e73644cbb706b0a72 100644 (file)
@@ -61,5 +61,6 @@ extern int gfs2_write_alloc_required(struct gfs2_inode *ip, u64 offset,
                                     unsigned int len);
 extern int gfs2_map_journal_extents(struct gfs2_sbd *sdp, struct gfs2_jdesc *jd);
 extern void gfs2_free_journal_extents(struct gfs2_jdesc *jd);
+extern int __gfs2_punch_hole(struct file *file, loff_t offset, loff_t length);
 
 #endif /* __BMAP_DOT_H__ */
index 58705ef8643a21993eb674519e66ba208a11c476..bd60dc682676d2914c3b1edcefe7edf2c6e4540d 100644 (file)
@@ -924,7 +924,7 @@ static long gfs2_fallocate(struct file *file, int mode, loff_t offset, loff_t le
        struct gfs2_holder gh;
        int ret;
 
-       if (mode & ~FALLOC_FL_KEEP_SIZE)
+       if (mode & ~(FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE))
                return -EOPNOTSUPP;
        /* fallocate is needed by gfs2_grow to reserve space in the rindex */
        if (gfs2_is_jdata(ip) && inode != sdp->sd_rindex)
@@ -948,13 +948,18 @@ static long gfs2_fallocate(struct file *file, int mode, loff_t offset, loff_t le
        if (ret)
                goto out_unlock;
 
-       ret = gfs2_rsqa_alloc(ip);
-       if (ret)
-               goto out_putw;
+       if (mode & FALLOC_FL_PUNCH_HOLE) {
+               ret = __gfs2_punch_hole(file, offset, len);
+       } else {
+               ret = gfs2_rsqa_alloc(ip);
+               if (ret)
+                       goto out_putw;
 
-       ret = __gfs2_fallocate(file, mode, offset, len);
-       if (ret)
-               gfs2_rs_deltree(&ip->i_res);
+               ret = __gfs2_fallocate(file, mode, offset, len);
+
+               if (ret)
+                       gfs2_rs_deltree(&ip->i_res);
+       }
 
 out_putw:
        put_write_access(inode);