From 20cdc1931ee8e03ce3a26061ff14a05a3f8cbe78 Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Fri, 22 Sep 2017 07:39:54 -0500 Subject: gfs2: Clarify gfs2_block_map Add a comment about the logical block size for directories. Rename "bsize" in gfs2_block_map to "factor". Fix a typo in the description of metaptr1. Signed-off-by: Andreas Gruenbacher Signed-off-by: Bob Peterson --- fs/gfs2/bmap.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'fs') diff --git a/fs/gfs2/bmap.c b/fs/gfs2/bmap.c index 3dd0cceefa43..8830e2903a34 100644 --- a/fs/gfs2/bmap.c +++ b/fs/gfs2/bmap.c @@ -248,7 +248,7 @@ static inline unsigned int metapath_branch_start(const struct metapath *mp) } /** - * metaptr1 - Return the first possible metadata pointer in a metaath buffer + * metaptr1 - Return the first possible metadata pointer in a metapath buffer * @height: The metadata height (0 = dinode) * @mp: The metapath */ @@ -659,7 +659,7 @@ int gfs2_block_map(struct inode *inode, sector_t lblock, { struct gfs2_inode *ip = GFS2_I(inode); struct gfs2_sbd *sdp = GFS2_SB(inode); - unsigned int bsize = sdp->sd_sb.sb_bsize; + unsigned int factor = sdp->sd_sb.sb_bsize; const size_t maxlen = bh_map->b_size >> inode->i_blkbits; const u64 *arr = sdp->sd_heightsize; __be64 *ptr; @@ -679,8 +679,14 @@ int gfs2_block_map(struct inode *inode, sector_t lblock, clear_buffer_new(bh_map); clear_buffer_boundary(bh_map); trace_gfs2_bmap(ip, bh_map, lblock, create, 1); + + /* + * Directory data blocks have a struct gfs2_meta_header header, so the + * remaining size is smaller than the filesystem block size. Logical + * block numbers for directories are in units of this remaining size! + */ if (gfs2_is_dir(ip)) { - bsize = sdp->sd_jbsize; + factor = sdp->sd_jbsize; arr = sdp->sd_jheightsize; } @@ -689,7 +695,7 @@ int gfs2_block_map(struct inode *inode, sector_t lblock, goto out; height = ip->i_height; - size = (lblock + 1) * bsize; + size = (lblock + 1) * factor; while (size > arr[height]) height++; find_metapath(sdp, lblock, &mp, height); -- cgit From 9b7c2ddb453e0c8c7529b562e7581d99e970b8b4 Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Fri, 22 Sep 2017 08:29:19 -0500 Subject: gfs2: Update ctime in setflags ioctl The FS_IOC_SETFLAGS ioctl is supposed to update the inode ctime. Fixes xfstests generic/277. Signed-off-by: Andreas Gruenbacher Signed-off-by: Bob Peterson --- fs/gfs2/file.c | 1 + 1 file changed, 1 insertion(+) (limited to 'fs') diff --git a/fs/gfs2/file.c b/fs/gfs2/file.c index 33a0cb5701a3..c7a904a8fbb4 100644 --- a/fs/gfs2/file.c +++ b/fs/gfs2/file.c @@ -271,6 +271,7 @@ static int do_gfs2_set_flags(struct file *filp, u32 reqflags, u32 mask) error = gfs2_meta_inode_buffer(ip, &bh); if (error) goto out_trans_end; + inode->i_ctime = current_time(inode); gfs2_trans_add_meta(ip->i_gl, bh); ip->i_diskflags = new_flags; gfs2_dinode_out(ip, bh->b_data); -- cgit From 38eedf2841b03b779710e8ad0442810a4747a348 Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Fri, 22 Sep 2017 08:34:46 -0500 Subject: gfs2: Support negative atimes When inodes are read from disk, GFS2 will only update in-memory atimes older than the on-disk atimes; this prevents atimes from going backwards. The atimes of newly allocated inodes are initialized to 0. This means that when an atime is explicitly set to a negative value, this value will not persist. Fix by setting the atime of newly allocated inodes to the lowest possible value instead of 0. Fixes xfstest generic/258. Signed-off-by: Andreas Gruenbacher Signed-off-by: Bob Peterson --- fs/gfs2/inode.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'fs') diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c index 863749e29bf9..b288cf2f85e6 100644 --- a/fs/gfs2/inode.c +++ b/fs/gfs2/inode.c @@ -189,7 +189,8 @@ struct inode *gfs2_inode_lookup(struct super_block *sb, unsigned int type, gfs2_set_iop(inode); - inode->i_atime.tv_sec = 0; + /* Lowest possible timestamp; will be overwritten in gfs2_dinode_in. */ + inode->i_atime.tv_sec = 1LL << (8 * sizeof(inode->i_atime.tv_sec) - 1); inode->i_atime.tv_nsec = 0; unlock_new_inode(inode); -- cgit From c2c4be28c248232d5bdfa5911f3b721db771f4f0 Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Mon, 25 Sep 2017 08:37:15 -0500 Subject: gfs2: Always update inode ctime in set_acl Three-entry POSIX ACLs can be stored in the file mode permission bits, with no need to store them in extended attributes. When a process sets such a minimal ACL, the kernel updates the file mode like chmod does, and removes any existing extended attributes for that ACL. Make sure the ctime is always updated in that case. Fixes xfstest generic/307. Signed-off-by: Andreas Gruenbacher Signed-off-by: Bob Peterson --- fs/gfs2/acl.c | 1 + 1 file changed, 1 insertion(+) (limited to 'fs') diff --git a/fs/gfs2/acl.c b/fs/gfs2/acl.c index 9d5eecb123de..776717f1eeea 100644 --- a/fs/gfs2/acl.c +++ b/fs/gfs2/acl.c @@ -141,6 +141,7 @@ int gfs2_set_acl(struct inode *inode, struct posix_acl *acl, int type) ret = __gfs2_set_acl(inode, acl, type); if (!ret && mode != inode->i_mode) { + inode->i_ctime = current_time(inode); inode->i_mode = mode; mark_inode_dirty(inode); } -- cgit From 5f8bd4440d94729d1977fba6ca0b4875c2ee1515 Mon Sep 17 00:00:00 2001 From: Bob Peterson Date: Fri, 28 Oct 2016 14:29:29 -0500 Subject: GFS2: Make height info part of metapath This patch eliminates height parameters from function gfs2_bmap_alloc. Function find_metapath determines the metapath's "find height", also known as the desired height. Function lookup_metapath determines the metapath's "actual height", previously known as starting height or sheight. Function gfs2_bmap_alloc now gets both height values from the metapath. This simplification was done as a step toward switching the block_map functions to using iomap. The bh_map responsibilities are also removed from function gfs2_bmap_alloc for the same reason. Signed-off-by: Bob Peterson Signed-off-by: Andreas Gruenbacher --- fs/gfs2/bmap.c | 109 ++++++++++++++++++++++++++++++++------------------------- 1 file changed, 62 insertions(+), 47 deletions(-) (limited to 'fs') diff --git a/fs/gfs2/bmap.c b/fs/gfs2/bmap.c index 8830e2903a34..03badc8417d7 100644 --- a/fs/gfs2/bmap.c +++ b/fs/gfs2/bmap.c @@ -36,6 +36,8 @@ struct metapath { struct buffer_head *mp_bh[GFS2_MAX_META_HEIGHT]; __u16 mp_list[GFS2_MAX_META_HEIGHT]; + int mp_fheight; /* find_metapath height */ + int mp_aheight; /* actual height (lookup height) */ }; /** @@ -235,9 +237,9 @@ static void find_metapath(const struct gfs2_sbd *sdp, u64 block, { unsigned int i; + mp->mp_fheight = height; for (i = height; i--;) mp->mp_list[i] = do_div(block, sdp->sd_inptrs); - } static inline unsigned int metapath_branch_start(const struct metapath *mp) @@ -345,10 +347,13 @@ static int lookup_metapath(struct gfs2_inode *ip, struct metapath *mp) for (x = 0; x < end_of_metadata; x++) { ret = lookup_mp_height(ip, mp, x); if (ret) - return ret; + goto out; } - return ip->i_height; + ret = ip->i_height; +out: + mp->mp_aheight = ret; + return ret; } /** @@ -480,10 +485,11 @@ static inline unsigned int hptrs(struct gfs2_sbd *sdp, const unsigned int hgt) * @inode: The GFS2 inode * @lblock: The logical starting block of the extent * @bh_map: This is used to return the mapping details - * @mp: The metapath - * @sheight: The starting height (i.e. whats already mapped) - * @height: The height to build to + * @zero_new: True if newly allocated blocks should be zeroed + * @mp: The metapath, with proper height information calculated * @maxlen: The max number of data blocks to alloc + * @dblock: Pointer to return the resulting new block + * @dblks: Pointer to return the number of blocks allocated * * In this routine we may have to alloc: * i) Indirect blocks to grow the metadata tree height @@ -500,62 +506,63 @@ static inline unsigned int hptrs(struct gfs2_sbd *sdp, const unsigned int hgt) */ static int gfs2_bmap_alloc(struct inode *inode, const sector_t lblock, - struct buffer_head *bh_map, struct metapath *mp, - const unsigned int sheight, - const unsigned int height, - const size_t maxlen) + bool zero_new, struct metapath *mp, + const size_t maxlen, sector_t *dblock, + unsigned *dblks) { struct gfs2_inode *ip = GFS2_I(inode); struct gfs2_sbd *sdp = GFS2_SB(inode); struct super_block *sb = sdp->sd_vfs; struct buffer_head *dibh = mp->mp_bh[0]; - u64 bn, dblock = 0; + u64 bn; unsigned n, i, blks, alloced = 0, iblks = 0, branch_start = 0; - unsigned dblks = 0; unsigned ptrs_per_blk; - const unsigned end_of_metadata = height - 1; + const unsigned end_of_metadata = mp->mp_fheight - 1; int ret; int eob = 0; enum alloc_state state; __be64 *ptr; __be64 zero_bn = 0; - BUG_ON(sheight < 1); + BUG_ON(mp->mp_aheight < 1); BUG_ON(dibh == NULL); + *dblock = 0; + *dblks = 0; gfs2_trans_add_meta(ip->i_gl, dibh); - if (height == sheight) { + if (mp->mp_fheight == mp->mp_aheight) { struct buffer_head *bh; /* Bottom indirect block exists, find unalloced extent size */ ptr = metapointer(end_of_metadata, mp); bh = mp->mp_bh[end_of_metadata]; - dblks = gfs2_extent_length(bh->b_data, bh->b_size, ptr, maxlen, - &eob); - BUG_ON(dblks < 1); + *dblks = gfs2_extent_length(bh->b_data, bh->b_size, ptr, + maxlen, &eob); + BUG_ON(*dblks < 1); state = ALLOC_DATA; } else { /* Need to allocate indirect blocks */ - ptrs_per_blk = height > 1 ? sdp->sd_inptrs : sdp->sd_diptrs; - dblks = min(maxlen, (size_t)(ptrs_per_blk - - mp->mp_list[end_of_metadata])); - if (height == ip->i_height) { + ptrs_per_blk = mp->mp_fheight > 1 ? sdp->sd_inptrs : + sdp->sd_diptrs; + *dblks = min(maxlen, (size_t)(ptrs_per_blk - + mp->mp_list[end_of_metadata])); + if (mp->mp_fheight == ip->i_height) { /* Writing into existing tree, extend tree down */ - iblks = height - sheight; + iblks = mp->mp_fheight - mp->mp_aheight; state = ALLOC_GROW_DEPTH; } else { /* Building up tree height */ state = ALLOC_GROW_HEIGHT; - iblks = height - ip->i_height; + iblks = mp->mp_fheight - ip->i_height; branch_start = metapath_branch_start(mp); - iblks += (height - branch_start); + iblks += (mp->mp_fheight - branch_start); } } /* start of the second part of the function (state machine) */ - blks = dblks + iblks; - i = sheight; + blks = *dblks + iblks; + i = mp->mp_aheight; do { int error; n = blks - alloced; @@ -573,9 +580,10 @@ static int gfs2_bmap_alloc(struct inode *inode, const sector_t lblock, sizeof(struct gfs2_dinode)); zero_bn = *ptr; } - for (; i - 1 < height - ip->i_height && n > 0; i++, n--) + for (; i - 1 < mp->mp_fheight - ip->i_height && n > 0; + i++, n--) gfs2_indirect_init(mp, ip->i_gl, i, 0, bn++); - if (i - 1 == height - ip->i_height) { + if (i - 1 == mp->mp_fheight - ip->i_height) { i--; gfs2_buffer_copy_tail(mp->mp_bh[i], sizeof(struct gfs2_meta_header), @@ -587,7 +595,7 @@ static int gfs2_bmap_alloc(struct inode *inode, const sector_t lblock, sizeof(struct gfs2_meta_header)); *ptr = zero_bn; state = ALLOC_GROW_DEPTH; - for(i = branch_start; i < height; i++) { + for(i = branch_start; i < mp->mp_fheight; i++) { if (mp->mp_bh[i] == NULL) break; brelse(mp->mp_bh[i]); @@ -599,44 +607,40 @@ static int gfs2_bmap_alloc(struct inode *inode, const sector_t lblock, break; /* Branching from existing tree */ case ALLOC_GROW_DEPTH: - if (i > 1 && i < height) + if (i > 1 && i < mp->mp_fheight) gfs2_trans_add_meta(ip->i_gl, mp->mp_bh[i-1]); - for (; i < height && n > 0; i++, n--) + for (; i < mp->mp_fheight && n > 0; i++, n--) gfs2_indirect_init(mp, ip->i_gl, i, mp->mp_list[i-1], bn++); - if (i == height) + if (i == mp->mp_fheight) state = ALLOC_DATA; if (n == 0) break; /* Tree complete, adding data blocks */ case ALLOC_DATA: - BUG_ON(n > dblks); + BUG_ON(n > *dblks); BUG_ON(mp->mp_bh[end_of_metadata] == NULL); gfs2_trans_add_meta(ip->i_gl, mp->mp_bh[end_of_metadata]); - dblks = n; + *dblks = n; ptr = metapointer(end_of_metadata, mp); - dblock = bn; + *dblock = bn; while (n-- > 0) *ptr++ = cpu_to_be64(bn++); - if (buffer_zeronew(bh_map)) { - ret = sb_issue_zeroout(sb, dblock, dblks, + if (zero_new) { + ret = sb_issue_zeroout(sb, *dblock, *dblks, GFP_NOFS); if (ret) { fs_err(sdp, "Failed to zero data buffers\n"); - clear_buffer_zeronew(bh_map); } } break; } - } while ((state != ALLOC_DATA) || !dblock); + } while ((state != ALLOC_DATA) || !(*dblock)); - ip->i_height = height; + ip->i_height = mp->mp_fheight; gfs2_add_inode_blocks(&ip->i_inode, alloced); gfs2_dinode_out(ip, mp->mp_bh[0]->b_data); - map_bh(bh_map, inode->i_sb, dblock); - bh_map->b_size = dblks << inode->i_blkbits; - set_buffer_new(bh_map); return 0; } @@ -670,6 +674,9 @@ int gfs2_block_map(struct inode *inode, sector_t lblock, unsigned int len; struct buffer_head *bh; u8 height; + bool zero_new = false; + sector_t dblock = 0; + unsigned dblks; BUG_ON(maxlen == 0); @@ -699,13 +706,13 @@ int gfs2_block_map(struct inode *inode, sector_t lblock, while (size > arr[height]) height++; find_metapath(sdp, lblock, &mp, height); - ret = 1; + mp.mp_aheight = 1; if (height > ip->i_height || gfs2_is_stuffed(ip)) goto do_alloc; ret = lookup_metapath(ip, &mp); if (ret < 0) goto out; - if (ret != ip->i_height) + if (mp.mp_aheight != ip->i_height) goto do_alloc; ptr = metapointer(ip->i_height - 1, &mp); if (*ptr == 0) @@ -732,7 +739,15 @@ do_alloc: } /* At this point ret is the tree depth of already allocated blocks */ - ret = gfs2_bmap_alloc(inode, lblock, bh_map, &mp, ret, height, maxlen); + if (buffer_zeronew(bh_map)) + zero_new = true; + ret = gfs2_bmap_alloc(inode, lblock, zero_new, &mp, maxlen, &dblock, + &dblks); + if (ret == 0) { + map_bh(bh_map, inode->i_sb, dblock); + bh_map->b_size = dblks << inode->i_blkbits; + set_buffer_new(bh_map); + } goto out; } -- cgit From 3974320ca6aa68d479051f208d5c95afd1e47a4c Mon Sep 17 00:00:00 2001 From: Bob Peterson Date: Thu, 16 Feb 2017 10:27:16 -0500 Subject: GFS2: Implement iomap for block_map This patch implements iomap for block mapping, and switches the block_map function to use it under the covers. The additional IOMAP_F_BOUNDARY iomap flag indicates when iomap has reached a "metadata boundary" and fetching the next mapping is likely to incur an additional I/O. This flag is used for setting the bh buffer boundary flag. Signed-off-by: Bob Peterson Signed-off-by: Andreas Gruenbacher --- fs/gfs2/bmap.c | 273 ++++++++++++++++++++++++++++++++++++++------------- fs/gfs2/bmap.h | 4 + fs/gfs2/trace_gfs2.h | 65 ++++++++++++ 3 files changed, 274 insertions(+), 68 deletions(-) (limited to 'fs') diff --git a/fs/gfs2/bmap.c b/fs/gfs2/bmap.c index 03badc8417d7..d5f0d96169c5 100644 --- a/fs/gfs2/bmap.c +++ b/fs/gfs2/bmap.c @@ -13,6 +13,7 @@ #include #include #include +#include #include "gfs2.h" #include "incore.h" @@ -505,10 +506,8 @@ static inline unsigned int hptrs(struct gfs2_sbd *sdp, const unsigned int hgt) * Returns: errno on error */ -static int gfs2_bmap_alloc(struct inode *inode, const sector_t lblock, - bool zero_new, struct metapath *mp, - const size_t maxlen, sector_t *dblock, - unsigned *dblks) +static int gfs2_iomap_alloc(struct inode *inode, struct iomap *iomap, + unsigned flags, struct metapath *mp) { struct gfs2_inode *ip = GFS2_I(inode); struct gfs2_sbd *sdp = GFS2_SB(inode); @@ -516,36 +515,37 @@ static int gfs2_bmap_alloc(struct inode *inode, const sector_t lblock, struct buffer_head *dibh = mp->mp_bh[0]; u64 bn; unsigned n, i, blks, alloced = 0, iblks = 0, branch_start = 0; + unsigned dblks = 0; unsigned ptrs_per_blk; const unsigned end_of_metadata = mp->mp_fheight - 1; int ret; - int eob = 0; enum alloc_state state; __be64 *ptr; __be64 zero_bn = 0; + size_t maxlen = iomap->length >> inode->i_blkbits; BUG_ON(mp->mp_aheight < 1); BUG_ON(dibh == NULL); - *dblock = 0; - *dblks = 0; gfs2_trans_add_meta(ip->i_gl, dibh); if (mp->mp_fheight == mp->mp_aheight) { struct buffer_head *bh; + int eob; + /* Bottom indirect block exists, find unalloced extent size */ ptr = metapointer(end_of_metadata, mp); bh = mp->mp_bh[end_of_metadata]; - *dblks = gfs2_extent_length(bh->b_data, bh->b_size, ptr, - maxlen, &eob); - BUG_ON(*dblks < 1); + dblks = gfs2_extent_length(bh->b_data, bh->b_size, ptr, + maxlen, &eob); + BUG_ON(dblks < 1); state = ALLOC_DATA; } else { /* Need to allocate indirect blocks */ ptrs_per_blk = mp->mp_fheight > 1 ? sdp->sd_inptrs : sdp->sd_diptrs; - *dblks = min(maxlen, (size_t)(ptrs_per_blk - - mp->mp_list[end_of_metadata])); + dblks = min(maxlen, (size_t)(ptrs_per_blk - + mp->mp_list[end_of_metadata])); if (mp->mp_fheight == ip->i_height) { /* Writing into existing tree, extend tree down */ iblks = mp->mp_fheight - mp->mp_aheight; @@ -561,7 +561,7 @@ static int gfs2_bmap_alloc(struct inode *inode, const sector_t lblock, /* start of the second part of the function (state machine) */ - blks = *dblks + iblks; + blks = dblks + iblks; i = mp->mp_aheight; do { int error; @@ -618,26 +618,29 @@ static int gfs2_bmap_alloc(struct inode *inode, const sector_t lblock, break; /* Tree complete, adding data blocks */ case ALLOC_DATA: - BUG_ON(n > *dblks); + BUG_ON(n > dblks); BUG_ON(mp->mp_bh[end_of_metadata] == NULL); gfs2_trans_add_meta(ip->i_gl, mp->mp_bh[end_of_metadata]); - *dblks = n; + dblks = n; ptr = metapointer(end_of_metadata, mp); - *dblock = bn; + iomap->addr = bn << inode->i_blkbits; + iomap->flags |= IOMAP_F_NEW; while (n-- > 0) *ptr++ = cpu_to_be64(bn++); - if (zero_new) { - ret = sb_issue_zeroout(sb, *dblock, *dblks, - GFP_NOFS); + if (flags & IOMAP_ZERO) { + ret = sb_issue_zeroout(sb, iomap->addr >> inode->i_blkbits, + dblks, GFP_NOFS); if (ret) { fs_err(sdp, "Failed to zero data buffers\n"); + flags &= ~IOMAP_ZERO; } } break; } - } while ((state != ALLOC_DATA) || !(*dblock)); + } while (iomap->addr == IOMAP_NULL_ADDR); + iomap->length = (u64)dblks << inode->i_blkbits; ip->i_height = mp->mp_fheight; gfs2_add_inode_blocks(&ip->i_inode, alloced); gfs2_dinode_out(ip, mp->mp_bh[0]->b_data); @@ -645,47 +648,123 @@ static int gfs2_bmap_alloc(struct inode *inode, const sector_t lblock, } /** - * gfs2_block_map - Map a block from an inode to a disk block + * hole_size - figure out the size of a hole * @inode: The inode - * @lblock: The logical block number - * @bh_map: The bh to be mapped - * @create: True if its ok to alloc blocks to satify the request + * @lblock: The logical starting block number + * @mp: The metapath * - * Sets buffer_mapped() if successful, sets buffer_boundary() if a - * read of metadata will be required before the next block can be - * mapped. Sets buffer_new() if new blocks were allocated. + * Returns: The hole size in bytes * - * Returns: errno */ +static u64 hole_size(struct inode *inode, sector_t lblock, struct metapath *mp) +{ + struct gfs2_inode *ip = GFS2_I(inode); + struct gfs2_sbd *sdp = GFS2_SB(inode); + struct metapath mp_eof; + u64 factor = 1; + int hgt; + u64 holesz = 0; + const __be64 *first, *end, *ptr; + const struct buffer_head *bh; + u64 lblock_stop = (i_size_read(inode) - 1) >> inode->i_blkbits; + int zeroptrs; + bool done = false; + + /* Get another metapath, to the very last byte */ + find_metapath(sdp, lblock_stop, &mp_eof, ip->i_height); + for (hgt = ip->i_height - 1; hgt >= 0 && !done; hgt--) { + bh = mp->mp_bh[hgt]; + if (bh) { + zeroptrs = 0; + first = metapointer(hgt, mp); + end = (const __be64 *)(bh->b_data + bh->b_size); + + for (ptr = first; ptr < end; ptr++) { + if (*ptr) { + done = true; + break; + } else { + zeroptrs++; + } + } + } else { + zeroptrs = sdp->sd_inptrs; + } + if (factor * zeroptrs >= lblock_stop - lblock + 1) { + holesz = lblock_stop - lblock + 1; + break; + } + holesz += factor * zeroptrs; -int gfs2_block_map(struct inode *inode, sector_t lblock, - struct buffer_head *bh_map, int create) + factor *= sdp->sd_inptrs; + if (hgt && (mp->mp_list[hgt - 1] < mp_eof.mp_list[hgt - 1])) + (mp->mp_list[hgt - 1])++; + } + return holesz << inode->i_blkbits; +} + +static void gfs2_stuffed_iomap(struct inode *inode, struct iomap *iomap) +{ + struct gfs2_inode *ip = GFS2_I(inode); + + iomap->addr = (ip->i_no_addr << inode->i_blkbits) + + sizeof(struct gfs2_dinode); + iomap->offset = 0; + iomap->length = i_size_read(inode); + iomap->type = IOMAP_MAPPED; + iomap->flags = IOMAP_F_DATA_INLINE; +} + +/** + * gfs2_iomap_begin - Map blocks from an inode to disk blocks + * @inode: The inode + * @pos: Starting position in bytes + * @length: Length to map, in bytes + * @flags: iomap flags + * @iomap: The iomap structure + * + * Returns: errno + */ +int gfs2_iomap_begin(struct inode *inode, loff_t pos, loff_t length, + unsigned flags, struct iomap *iomap) { struct gfs2_inode *ip = GFS2_I(inode); struct gfs2_sbd *sdp = GFS2_SB(inode); + struct metapath mp = { .mp_aheight = 1, }; unsigned int factor = sdp->sd_sb.sb_bsize; - const size_t maxlen = bh_map->b_size >> inode->i_blkbits; const u64 *arr = sdp->sd_heightsize; __be64 *ptr; - u64 size; - struct metapath mp; + sector_t lblock; + sector_t lend; int ret; int eob; unsigned int len; struct buffer_head *bh; u8 height; - bool zero_new = false; - sector_t dblock = 0; - unsigned dblks; - BUG_ON(maxlen == 0); + trace_gfs2_iomap_start(ip, pos, length, flags); + if (!length) { + ret = -EINVAL; + goto out; + } - memset(&mp, 0, sizeof(mp)); - bmap_lock(ip, create); - clear_buffer_mapped(bh_map); - clear_buffer_new(bh_map); - clear_buffer_boundary(bh_map); - trace_gfs2_bmap(ip, bh_map, lblock, create, 1); + if ((flags & IOMAP_REPORT) && gfs2_is_stuffed(ip)) { + gfs2_stuffed_iomap(inode, iomap); + if (pos >= iomap->length) + return -ENOENT; + ret = 0; + goto out; + } + + lblock = pos >> inode->i_blkbits; + lend = (pos + length + sdp->sd_sb.sb_bsize - 1) >> inode->i_blkbits; + + iomap->offset = lblock << inode->i_blkbits; + iomap->addr = IOMAP_NULL_ADDR; + iomap->type = IOMAP_HOLE; + iomap->length = (u64)(lend - lblock) << inode->i_blkbits; + iomap->flags = IOMAP_F_MERGED; + bmap_lock(ip, 0); /* * Directory data blocks have a struct gfs2_meta_header header, so the @@ -699,56 +778,114 @@ int gfs2_block_map(struct inode *inode, sector_t lblock, ret = gfs2_meta_inode_buffer(ip, &mp.mp_bh[0]); if (ret) - goto out; + goto out_release; height = ip->i_height; - size = (lblock + 1) * factor; - while (size > arr[height]) + while ((lblock + 1) * factor > arr[height]) height++; find_metapath(sdp, lblock, &mp, height); - mp.mp_aheight = 1; if (height > ip->i_height || gfs2_is_stuffed(ip)) goto do_alloc; + ret = lookup_metapath(ip, &mp); if (ret < 0) - goto out; + goto out_release; + if (mp.mp_aheight != ip->i_height) goto do_alloc; + ptr = metapointer(ip->i_height - 1, &mp); if (*ptr == 0) goto do_alloc; - map_bh(bh_map, inode->i_sb, be64_to_cpu(*ptr)); + + iomap->type = IOMAP_MAPPED; + iomap->addr = be64_to_cpu(*ptr) << inode->i_blkbits; + bh = mp.mp_bh[ip->i_height - 1]; - len = gfs2_extent_length(bh->b_data, bh->b_size, ptr, maxlen, &eob); - bh_map->b_size = (len << inode->i_blkbits); + len = gfs2_extent_length(bh->b_data, bh->b_size, ptr, lend - lblock, &eob); if (eob) - set_buffer_boundary(bh_map); + iomap->flags |= IOMAP_F_BOUNDARY; + iomap->length = (u64)len << inode->i_blkbits; + ret = 0; -out: + +out_release: release_metapath(&mp); - trace_gfs2_bmap(ip, bh_map, lblock, create, ret); - bmap_unlock(ip, create); + bmap_unlock(ip, 0); +out: + trace_gfs2_iomap_end(ip, iomap, ret); return ret; do_alloc: - /* All allocations are done here, firstly check create flag */ - if (!create) { - BUG_ON(gfs2_is_stuffed(ip)); + if (!(flags & IOMAP_WRITE)) { + if (pos >= i_size_read(inode)) { + ret = -ENOENT; + goto out_release; + } ret = 0; - goto out; + iomap->length = hole_size(inode, lblock, &mp); + goto out_release; } - /* At this point ret is the tree depth of already allocated blocks */ + ret = gfs2_iomap_alloc(inode, iomap, flags, &mp); + goto out_release; +} + +/** + * gfs2_block_map - Map a block from an inode to a disk block + * @inode: The inode + * @lblock: The logical block number + * @bh_map: The bh to be mapped + * @create: True if its ok to alloc blocks to satify the request + * + * Sets buffer_mapped() if successful, sets buffer_boundary() if a + * read of metadata will be required before the next block can be + * mapped. Sets buffer_new() if new blocks were allocated. + * + * Returns: errno + */ + +int gfs2_block_map(struct inode *inode, sector_t lblock, + struct buffer_head *bh_map, int create) +{ + struct gfs2_inode *ip = GFS2_I(inode); + struct iomap iomap; + int ret, flags = 0; + + clear_buffer_mapped(bh_map); + clear_buffer_new(bh_map); + clear_buffer_boundary(bh_map); + trace_gfs2_bmap(ip, bh_map, lblock, create, 1); + + if (create) + flags |= IOMAP_WRITE; if (buffer_zeronew(bh_map)) - zero_new = true; - ret = gfs2_bmap_alloc(inode, lblock, zero_new, &mp, maxlen, &dblock, - &dblks); - if (ret == 0) { - map_bh(bh_map, inode->i_sb, dblock); - bh_map->b_size = dblks << inode->i_blkbits; - set_buffer_new(bh_map); + flags |= IOMAP_ZERO; + ret = gfs2_iomap_begin(inode, (loff_t)lblock << inode->i_blkbits, + bh_map->b_size, flags, &iomap); + if (ret) { + if (!create && ret == -ENOENT) { + /* Return unmapped buffer beyond the end of file. */ + ret = 0; + } + goto out; + } + + if (iomap.length > bh_map->b_size) { + iomap.length = bh_map->b_size; + iomap.flags &= ~IOMAP_F_BOUNDARY; } - goto out; + if (iomap.addr != IOMAP_NULL_ADDR) + map_bh(bh_map, inode->i_sb, iomap.addr >> inode->i_blkbits); + bh_map->b_size = iomap.length; + if (iomap.flags & IOMAP_F_BOUNDARY) + set_buffer_boundary(bh_map); + if (iomap.flags & IOMAP_F_NEW) + set_buffer_new(bh_map); + +out: + trace_gfs2_bmap(ip, bh_map, lblock, create, ret); + return ret; } /* diff --git a/fs/gfs2/bmap.h b/fs/gfs2/bmap.h index 81ded5e2aaa2..443cc182cf18 100644 --- a/fs/gfs2/bmap.h +++ b/fs/gfs2/bmap.h @@ -10,6 +10,8 @@ #ifndef __BMAP_DOT_H__ #define __BMAP_DOT_H__ +#include + #include "inode.h" struct inode; @@ -47,6 +49,8 @@ static inline void gfs2_write_calc_reserv(const struct gfs2_inode *ip, extern int gfs2_unstuff_dinode(struct gfs2_inode *ip, struct page *page); extern int gfs2_block_map(struct inode *inode, sector_t lblock, struct buffer_head *bh, int create); +extern int gfs2_iomap_begin(struct inode *inode, loff_t pos, loff_t length, + unsigned flags, struct iomap *iomap); extern int gfs2_extent_map(struct inode *inode, u64 lblock, int *new, u64 *dblock, unsigned *extlen); extern int gfs2_setattr_size(struct inode *inode, u64 size); diff --git a/fs/gfs2/trace_gfs2.h b/fs/gfs2/trace_gfs2.h index 49ac55da4e33..3c91ae3cf0b2 100644 --- a/fs/gfs2/trace_gfs2.h +++ b/fs/gfs2/trace_gfs2.h @@ -12,6 +12,7 @@ #include #include #include +#include #include "incore.h" #include "glock.h" #include "rgrp.h" @@ -469,6 +470,70 @@ TRACE_EVENT(gfs2_bmap, __entry->errno) ); +TRACE_EVENT(gfs2_iomap_start, + + TP_PROTO(const struct gfs2_inode *ip, loff_t pos, ssize_t length, + u16 flags), + + TP_ARGS(ip, pos, length, flags), + + TP_STRUCT__entry( + __field( dev_t, dev ) + __field( u64, inum ) + __field( loff_t, pos ) + __field( ssize_t, length ) + __field( u16, flags ) + ), + + TP_fast_assign( + __entry->dev = ip->i_gl->gl_name.ln_sbd->sd_vfs->s_dev; + __entry->inum = ip->i_no_addr; + __entry->pos = pos; + __entry->length = length; + __entry->flags = flags; + ), + + TP_printk("%u,%u bmap %llu iomap start %llu/%lu flags:%08x", + MAJOR(__entry->dev), MINOR(__entry->dev), + (unsigned long long)__entry->inum, + (unsigned long long)__entry->pos, + (unsigned long)__entry->length, (u16)__entry->flags) +); + +TRACE_EVENT(gfs2_iomap_end, + + TP_PROTO(const struct gfs2_inode *ip, struct iomap *iomap, int ret), + + TP_ARGS(ip, iomap, ret), + + TP_STRUCT__entry( + __field( dev_t, dev ) + __field( u64, inum ) + __field( loff_t, offset ) + __field( ssize_t, length ) + __field( u16, flags ) + __field( u16, type ) + __field( int, ret ) + ), + + TP_fast_assign( + __entry->dev = ip->i_gl->gl_name.ln_sbd->sd_vfs->s_dev; + __entry->inum = ip->i_no_addr; + __entry->offset = iomap->offset; + __entry->length = iomap->length; + __entry->flags = iomap->flags; + __entry->type = iomap->type; + __entry->ret = ret; + ), + + TP_printk("%u,%u bmap %llu iomap end %llu/%lu ty:%d flags:%08x rc:%d", + MAJOR(__entry->dev), MINOR(__entry->dev), + (unsigned long long)__entry->inum, + (unsigned long long)__entry->offset, + (unsigned long)__entry->length, (u16)__entry->type, + (u16)__entry->flags, __entry->ret) +); + /* Keep track of blocks as they are allocated/freed */ TRACE_EVENT(gfs2_block_alloc, -- cgit From aac1a55b450c623ec236c0635cdb68408f632e9c Mon Sep 17 00:00:00 2001 From: Bob Peterson Date: Thu, 16 Feb 2017 21:13:54 +0100 Subject: GFS2: Switch fiemap implementation to use iomap This patch switches GFS2's implementation of fiemap from the old block_map code to the new iomap interface. Signed-off-by: Bob Peterson Signed-off-by: Andreas Gruenbacher --- fs/gfs2/Kconfig | 1 + fs/gfs2/inode.c | 34 +++++++++------------------------- 2 files changed, 10 insertions(+), 25 deletions(-) (limited to 'fs') diff --git a/fs/gfs2/Kconfig b/fs/gfs2/Kconfig index 90c6a8faaecb..43c827a7cce5 100644 --- a/fs/gfs2/Kconfig +++ b/fs/gfs2/Kconfig @@ -4,6 +4,7 @@ config GFS2_FS select FS_POSIX_ACL select CRC32 select QUOTACTL + select FS_IOMAP help A cluster filesystem. diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c index b288cf2f85e6..321da48ca123 100644 --- a/fs/gfs2/inode.c +++ b/fs/gfs2/inode.c @@ -18,7 +18,7 @@ #include #include #include -#include +#include #include #include @@ -2003,6 +2003,10 @@ static int gfs2_getattr(const struct path *path, struct kstat *stat, return 0; } +const struct iomap_ops gfs2_iomap_ops = { + .iomap_begin = gfs2_iomap_begin, +}; + static int gfs2_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo, u64 start, u64 len) { @@ -2010,38 +2014,18 @@ static int gfs2_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo, struct gfs2_holder gh; int ret; - ret = fiemap_check_flags(fieinfo, FIEMAP_FLAG_SYNC); - if (ret) - return ret; - - inode_lock(inode); + inode_lock_shared(inode); ret = gfs2_glock_nq_init(ip->i_gl, LM_ST_SHARED, 0, &gh); if (ret) goto out; - if (gfs2_is_stuffed(ip)) { - u64 phys = ip->i_no_addr << inode->i_blkbits; - u64 size = i_size_read(inode); - u32 flags = FIEMAP_EXTENT_LAST|FIEMAP_EXTENT_NOT_ALIGNED| - FIEMAP_EXTENT_DATA_INLINE; - phys += sizeof(struct gfs2_dinode); - phys += start; - if (start + len > size) - len = size - start; - if (start < size) - ret = fiemap_fill_next_extent(fieinfo, start, phys, - len, flags); - if (ret == 1) - ret = 0; - } else { - ret = __generic_block_fiemap(inode, fieinfo, start, len, - gfs2_block_map); - } + ret = iomap_fiemap(inode, fieinfo, start, len, &gfs2_iomap_ops); gfs2_glock_dq_uninit(&gh); + out: - inode_unlock(inode); + inode_unlock_shared(inode); return ret; } -- cgit From 3a27411cb4bc3ce31db228e3569ad01b462a4310 Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Wed, 15 Mar 2017 19:12:59 +0100 Subject: gfs2: Implement SEEK_HOLE / SEEK_DATA via iomap So far, lseek on gfs2 did not report holes. Signed-off-by: Andreas Gruenbacher Signed-off-by: Bob Peterson --- fs/gfs2/file.c | 17 ++++++++++++++--- fs/gfs2/inode.c | 38 ++++++++++++++++++++++++++++++++++++++ fs/gfs2/inode.h | 2 ++ 3 files changed, 54 insertions(+), 3 deletions(-) (limited to 'fs') diff --git a/fs/gfs2/file.c b/fs/gfs2/file.c index c7a904a8fbb4..8fefb80fe830 100644 --- a/fs/gfs2/file.c +++ b/fs/gfs2/file.c @@ -60,9 +60,7 @@ static loff_t gfs2_llseek(struct file *file, loff_t offset, int whence) loff_t error; switch (whence) { - case SEEK_END: /* These reference inode->i_size */ - case SEEK_DATA: - case SEEK_HOLE: + case SEEK_END: error = gfs2_glock_nq_init(ip->i_gl, LM_ST_SHARED, LM_FLAG_ANY, &i_gh); if (!error) { @@ -70,8 +68,21 @@ static loff_t gfs2_llseek(struct file *file, loff_t offset, int whence) gfs2_glock_dq_uninit(&i_gh); } break; + + case SEEK_DATA: + error = gfs2_seek_data(file, offset); + break; + + case SEEK_HOLE: + error = gfs2_seek_hole(file, offset); + break; + case SEEK_CUR: case SEEK_SET: + /* + * These don't reference inode->i_size and don't depend on the + * block mapping, so we don't need the glock. + */ error = generic_file_llseek(file, offset, whence); break; default: diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c index 321da48ca123..4749a6b8e4dd 100644 --- a/fs/gfs2/inode.c +++ b/fs/gfs2/inode.c @@ -2029,6 +2029,44 @@ out: return ret; } +loff_t gfs2_seek_data(struct file *file, loff_t offset) +{ + struct inode *inode = file->f_mapping->host; + struct gfs2_inode *ip = GFS2_I(inode); + struct gfs2_holder gh; + loff_t ret; + + inode_lock_shared(inode); + ret = gfs2_glock_nq_init(ip->i_gl, LM_ST_SHARED, 0, &gh); + if (!ret) + ret = iomap_seek_data(inode, offset, &gfs2_iomap_ops); + gfs2_glock_dq_uninit(&gh); + inode_unlock_shared(inode); + + if (ret < 0) + return ret; + return vfs_setpos(file, ret, inode->i_sb->s_maxbytes); +} + +loff_t gfs2_seek_hole(struct file *file, loff_t offset) +{ + struct inode *inode = file->f_mapping->host; + struct gfs2_inode *ip = GFS2_I(inode); + struct gfs2_holder gh; + loff_t ret; + + inode_lock_shared(inode); + ret = gfs2_glock_nq_init(ip->i_gl, LM_ST_SHARED, 0, &gh); + if (!ret) + ret = iomap_seek_hole(inode, offset, &gfs2_iomap_ops); + gfs2_glock_dq_uninit(&gh); + inode_unlock_shared(inode); + + if (ret < 0) + return ret; + return vfs_setpos(file, ret, inode->i_sb->s_maxbytes); +} + const struct inode_operations gfs2_file_iops = { .permission = gfs2_permission, .setattr = gfs2_setattr, diff --git a/fs/gfs2/inode.h b/fs/gfs2/inode.h index aace8ce34a18..b5b6341a4f5c 100644 --- a/fs/gfs2/inode.h +++ b/fs/gfs2/inode.h @@ -109,6 +109,8 @@ extern int gfs2_setattr_simple(struct inode *inode, struct iattr *attr); extern struct inode *gfs2_lookup_simple(struct inode *dip, const char *name); extern void gfs2_dinode_out(const struct gfs2_inode *ip, void *buf); extern int gfs2_open_common(struct inode *inode, struct file *file); +extern loff_t gfs2_seek_data(struct file *file, loff_t offset); +extern loff_t gfs2_seek_hole(struct file *file, loff_t offset); extern const struct inode_operations gfs2_file_iops; extern const struct inode_operations gfs2_dir_iops; -- cgit From adbc3ddf28ad9c2742fb9fc82e2aacfd414a16c1 Mon Sep 17 00:00:00 2001 From: Bob Peterson Date: Wed, 11 Oct 2017 16:22:07 +0200 Subject: GFS2: flush the log and all pages for jdata as we do for WB_SYNC_ALL In function gfs2_write_inode, starting with patch a9185b41a4f84, we only flush the log and call filemap_fdatawait if we're passed in a wbc sync_mode of WB_SYNC_ALL. We also need to do these things if we're evicting a jdata inode, because we might have jdata pages still attached to bufdata descriptors that need to be revoked, but by the time it gets to evict() it's too late to start a new transaction. This patch changes it to treat jdata inodes as if WB_SYNC_ALL had been specified. Signed-off-by: Bob Peterson Signed-off-by: Andreas Gruenbacher Acked-by: Abhijith Das --- fs/gfs2/super.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'fs') diff --git a/fs/gfs2/super.c b/fs/gfs2/super.c index 8e54f2e3a304..9cb5c9a97d69 100644 --- a/fs/gfs2/super.c +++ b/fs/gfs2/super.c @@ -754,14 +754,15 @@ static int gfs2_write_inode(struct inode *inode, struct writeback_control *wbc) struct address_space *metamapping = gfs2_glock2aspace(ip->i_gl); struct backing_dev_info *bdi = inode_to_bdi(metamapping->host); int ret = 0; + bool flush_all = (wbc->sync_mode == WB_SYNC_ALL || gfs2_is_jdata(ip)); - if (wbc->sync_mode == WB_SYNC_ALL) + if (flush_all) gfs2_log_flush(GFS2_SB(inode), ip->i_gl, NORMAL_FLUSH); if (bdi->wb.dirty_exceeded) gfs2_ail1_flush(sdp, wbc); else filemap_fdatawrite(metamapping); - if (wbc->sync_mode == WB_SYNC_ALL) + if (flush_all) ret = filemap_fdatawait(metamapping); if (ret) mark_inode_dirty_sync(inode); -- cgit From cc555b09d8c3817aeebda43a14ab67049a5653f7 Mon Sep 17 00:00:00 2001 From: Bob Peterson Date: Wed, 20 Sep 2017 08:30:04 -0500 Subject: GFS2: Take inode off order_write list when setting jdata flag This patch fixes a deadlock caused when the jdata flag is set for inodes that are already on the ordered write list. Since it is on the ordered write list, log_flush calls gfs2_ordered_write which calls filemap_fdatawrite. But since the inode had the jdata flag set, that calls gfs2_jdata_writepages, which tries to start a new transaction. A new transaction cannot be started because it tries to acquire the log_flush rwsem which is already locked by the log flush operation. The bottom line is: We cannot switch an inode from ordered to jdata until we eliminate any ordered data pages (via log flush) or any log_flush operation afterward will create the circular dependency above. So we need to flush the log before setting the diskflags to switch the file mode, then we need to remove the inode from the ordered writes list. Before this patch, the log flush was done for jdata->ordered, but that's wrong. If we're going from jdata to ordered, we don't need to call gfs2_log_flush because the call to filemap_fdatawrite will do it for us: filemap_fdatawrite() -> __filemap_fdatawrite_range() __filemap_fdatawrite_range() -> do_writepages() do_writepages() -> gfs2_jdata_writepages() gfs2_jdata_writepages() -> gfs2_log_flush() This patch modifies function do_gfs2_set_flags so that if a file has its jdata flag set, and it's already on the ordered write list, the log will be flushed and it will be removed from the list before setting the flag. Signed-off-by: Bob Peterson Signed-off-by: Andreas Gruenbacher Acked-by: Abhijith Das --- fs/gfs2/file.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'fs') diff --git a/fs/gfs2/file.c b/fs/gfs2/file.c index 8fefb80fe830..c7aea96144b4 100644 --- a/fs/gfs2/file.c +++ b/fs/gfs2/file.c @@ -267,7 +267,7 @@ static int do_gfs2_set_flags(struct file *filp, u32 reqflags, u32 mask) goto out; } if ((flags ^ new_flags) & GFS2_DIF_JDATA) { - if (flags & GFS2_DIF_JDATA) + if (new_flags & GFS2_DIF_JDATA) gfs2_log_flush(sdp, ip->i_gl, NORMAL_FLUSH); error = filemap_fdatawrite(inode->i_mapping); if (error) @@ -275,6 +275,8 @@ static int do_gfs2_set_flags(struct file *filp, u32 reqflags, u32 mask) error = filemap_fdatawait(inode->i_mapping); if (error) goto out; + if (new_flags & GFS2_DIF_JDATA) + gfs2_ordered_del_inode(ip); } error = gfs2_trans_begin(sdp, RES_DINODE, 0); if (error) -- cgit From 6862c44ec5ad0261968c3bc47d23cffb088b6836 Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Wed, 4 Oct 2017 16:21:19 +0200 Subject: gfs2: Fix xattr fsync Make sure that changing xattrs marks the corresponding inode dirty so that a subsequent fsync will sync those changes to disk. We set I_DIRTY_SYNC as well as I_DIRTY_DATASYNC so that both fsync and fdatasync will sync xattr changes: xattrs can contain information critical to how the data can be accessed, so we don't want fdatasync to skip them. Fixes xfstest generic/066. Signed-off-by: Andreas Gruenbacher Reviewed-by: Andrew Price Signed-off-by: Bob Peterson --- fs/gfs2/xattr.c | 40 ++++++++-------------------------------- 1 file changed, 8 insertions(+), 32 deletions(-) (limited to 'fs') diff --git a/fs/gfs2/xattr.c b/fs/gfs2/xattr.c index ea09e41dbb49..3e96dce21c1c 100644 --- a/fs/gfs2/xattr.c +++ b/fs/gfs2/xattr.c @@ -231,7 +231,6 @@ static int ea_dealloc_unstuffed(struct gfs2_inode *ip, struct buffer_head *bh, struct gfs2_sbd *sdp = GFS2_SB(&ip->i_inode); struct gfs2_rgrpd *rgd; struct gfs2_holder rg_gh; - struct buffer_head *dibh; __be64 *dataptrs; u64 bn = 0; u64 bstart = 0; @@ -308,13 +307,8 @@ static int ea_dealloc_unstuffed(struct gfs2_inode *ip, struct buffer_head *bh, ea->ea_num_ptrs = 0; } - error = gfs2_meta_inode_buffer(ip, &dibh); - if (!error) { - ip->i_inode.i_ctime = current_time(&ip->i_inode); - gfs2_trans_add_meta(ip->i_gl, dibh); - gfs2_dinode_out(ip, dibh->b_data); - brelse(dibh); - } + ip->i_inode.i_ctime = current_time(&ip->i_inode); + __mark_inode_dirty(&ip->i_inode, I_DIRTY_SYNC | I_DIRTY_DATASYNC); gfs2_trans_end(sdp); @@ -749,7 +743,6 @@ static int ea_alloc_skeleton(struct gfs2_inode *ip, struct gfs2_ea_request *er, ea_skeleton_call_t skeleton_call, void *private) { struct gfs2_alloc_parms ap = { .target = blks }; - struct buffer_head *dibh; int error; error = gfs2_rindex_update(GFS2_SB(&ip->i_inode)); @@ -774,13 +767,8 @@ static int ea_alloc_skeleton(struct gfs2_inode *ip, struct gfs2_ea_request *er, if (error) goto out_end_trans; - error = gfs2_meta_inode_buffer(ip, &dibh); - if (!error) { - ip->i_inode.i_ctime = current_time(&ip->i_inode); - gfs2_trans_add_meta(ip->i_gl, dibh); - gfs2_dinode_out(ip, dibh->b_data); - brelse(dibh); - } + ip->i_inode.i_ctime = current_time(&ip->i_inode); + __mark_inode_dirty(&ip->i_inode, I_DIRTY_SYNC | I_DIRTY_DATASYNC); out_end_trans: gfs2_trans_end(GFS2_SB(&ip->i_inode)); @@ -891,7 +879,6 @@ static int ea_set_simple_noalloc(struct gfs2_inode *ip, struct buffer_head *bh, struct gfs2_ea_header *ea, struct ea_set *es) { struct gfs2_ea_request *er = es->es_er; - struct buffer_head *dibh; int error; error = gfs2_trans_begin(GFS2_SB(&ip->i_inode), RES_DINODE + 2 * RES_EATTR, 0); @@ -908,14 +895,9 @@ static int ea_set_simple_noalloc(struct gfs2_inode *ip, struct buffer_head *bh, if (es->es_el) ea_set_remove_stuffed(ip, es->es_el); - error = gfs2_meta_inode_buffer(ip, &dibh); - if (error) - goto out; ip->i_inode.i_ctime = current_time(&ip->i_inode); - gfs2_trans_add_meta(ip->i_gl, dibh); - gfs2_dinode_out(ip, dibh->b_data); - brelse(dibh); -out: + __mark_inode_dirty(&ip->i_inode, I_DIRTY_SYNC | I_DIRTY_DATASYNC); + gfs2_trans_end(GFS2_SB(&ip->i_inode)); return error; } @@ -1111,7 +1093,6 @@ static int ea_remove_stuffed(struct gfs2_inode *ip, struct gfs2_ea_location *el) { struct gfs2_ea_header *ea = el->el_ea; struct gfs2_ea_header *prev = el->el_prev; - struct buffer_head *dibh; int error; error = gfs2_trans_begin(GFS2_SB(&ip->i_inode), RES_DINODE + RES_EATTR, 0); @@ -1132,13 +1113,8 @@ static int ea_remove_stuffed(struct gfs2_inode *ip, struct gfs2_ea_location *el) ea->ea_type = GFS2_EATYPE_UNUSED; } - error = gfs2_meta_inode_buffer(ip, &dibh); - if (!error) { - ip->i_inode.i_ctime = current_time(&ip->i_inode); - gfs2_trans_add_meta(ip->i_gl, dibh); - gfs2_dinode_out(ip, dibh->b_data); - brelse(dibh); - } + ip->i_inode.i_ctime = current_time(&ip->i_inode); + __mark_inode_dirty(&ip->i_inode, I_DIRTY_SYNC | I_DIRTY_DATASYNC); gfs2_trans_end(GFS2_SB(&ip->i_inode)); -- cgit From 61d6899ad4268b6d95187053740fcb17a0d31632 Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Wed, 4 Oct 2017 16:39:18 +0200 Subject: gfs2: Fix a harmless typo Signed-off-by: Andreas Gruenbacher Signed-off-by: Bob Peterson --- fs/gfs2/trans.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'fs') diff --git a/fs/gfs2/trans.c b/fs/gfs2/trans.c index affef3c066e0..a85ca8b2c9ba 100644 --- a/fs/gfs2/trans.c +++ b/fs/gfs2/trans.c @@ -145,7 +145,7 @@ static struct gfs2_bufdata *gfs2_alloc_bufdata(struct gfs2_glock *gl, * * This is used in two distinct cases: * i) In ordered write mode - * We put the data buffer on a list so that we can ensure that its + * We put the data buffer on a list so that we can ensure that it's * synced to disk at the right time * ii) In journaled data mode * We need to journal the data block in the same way as metadata in -- cgit From b16f7e57b7811d5c60ef1858bd92339be28359bf Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Mon, 9 Oct 2017 16:15:30 +0200 Subject: gfs2: Fix and clean up {GET,SET}FLAGS ioctl Switch to a simple array for mapping between the FS_*_FL and GFS_DIF_* flags. Clarify how the mapping between FS_JOURNAL_DATA_FL and the filesystem flags works. The GFS2_DIF_SYSTEM flag cannot be set from user space, so remove it from GFS2_FLAGS_USER_SET. Fail with -EINVAL when trying to set flags that are not supported instead of silently ignoring those flags. Partially fixes xfstest generic/424. Signed-off-by: Andreas Gruenbacher Reviewed-by: Andrew Price Signed-off-by: Bob Peterson --- fs/gfs2/file.c | 102 ++++++++++++++++++++++++++------------------------------- 1 file changed, 47 insertions(+), 55 deletions(-) (limited to 'fs') diff --git a/fs/gfs2/file.c b/fs/gfs2/file.c index c7aea96144b4..58705ef8643a 100644 --- a/fs/gfs2/file.c +++ b/fs/gfs2/file.c @@ -119,45 +119,22 @@ static int gfs2_readdir(struct file *file, struct dir_context *ctx) } /** - * fsflags_cvt - * @table: A table of 32 u32 flags - * @val: a 32 bit value to convert + * fsflag_gfs2flag * - * This function can be used to convert between fsflags values and - * GFS2's own flags values. - * - * Returns: the converted flags + * The FS_JOURNAL_DATA_FL flag maps to GFS2_DIF_INHERIT_JDATA for directories, + * and to GFS2_DIF_JDATA for non-directories. */ -static u32 fsflags_cvt(const u32 *table, u32 val) -{ - u32 res = 0; - while(val) { - if (val & 1) - res |= *table; - table++; - val >>= 1; - } - return res; -} - -static const u32 fsflags_to_gfs2[32] = { - [3] = GFS2_DIF_SYNC, - [4] = GFS2_DIF_IMMUTABLE, - [5] = GFS2_DIF_APPENDONLY, - [7] = GFS2_DIF_NOATIME, - [12] = GFS2_DIF_EXHASH, - [14] = GFS2_DIF_INHERIT_JDATA, - [17] = GFS2_DIF_TOPDIR, -}; - -static const u32 gfs2_to_fsflags[32] = { - [gfs2fl_Sync] = FS_SYNC_FL, - [gfs2fl_Immutable] = FS_IMMUTABLE_FL, - [gfs2fl_AppendOnly] = FS_APPEND_FL, - [gfs2fl_NoAtime] = FS_NOATIME_FL, - [gfs2fl_ExHash] = FS_INDEX_FL, - [gfs2fl_TopLevel] = FS_TOPDIR_FL, - [gfs2fl_InheritJdata] = FS_JOURNAL_DATA_FL, +static struct { + u32 fsflag; + u32 gfsflag; +} fsflag_gfs2flag[] = { + {FS_SYNC_FL, GFS2_DIF_SYNC}, + {FS_IMMUTABLE_FL, GFS2_DIF_IMMUTABLE}, + {FS_APPEND_FL, GFS2_DIF_APPENDONLY}, + {FS_NOATIME_FL, GFS2_DIF_NOATIME}, + {FS_INDEX_FL, GFS2_DIF_EXHASH}, + {FS_TOPDIR_FL, GFS2_DIF_TOPDIR}, + {FS_JOURNAL_DATA_FL, GFS2_DIF_JDATA | GFS2_DIF_INHERIT_JDATA}, }; static int gfs2_get_flags(struct file *filp, u32 __user *ptr) @@ -165,17 +142,23 @@ static int gfs2_get_flags(struct file *filp, u32 __user *ptr) struct inode *inode = file_inode(filp); struct gfs2_inode *ip = GFS2_I(inode); struct gfs2_holder gh; - int error; - u32 fsflags; + int i, error; + u32 gfsflags, fsflags = 0; gfs2_holder_init(ip->i_gl, LM_ST_SHARED, 0, &gh); error = gfs2_glock_nq(&gh); if (error) goto out_uninit; - fsflags = fsflags_cvt(gfs2_to_fsflags, ip->i_diskflags); - if (!S_ISDIR(inode->i_mode) && ip->i_diskflags & GFS2_DIF_JDATA) - fsflags |= FS_JOURNAL_DATA_FL; + gfsflags = ip->i_diskflags; + if (S_ISDIR(inode->i_mode)) + gfsflags &= ~GFS2_DIF_JDATA; + else + gfsflags &= ~GFS2_DIF_INHERIT_JDATA; + for (i = 0; i < ARRAY_SIZE(fsflag_gfs2flag); i++) + if (gfsflags & fsflag_gfs2flag[i].gfsflag) + fsflags |= fsflag_gfs2flag[i].fsflag; + if (put_user(fsflags, ptr)) error = -EFAULT; @@ -210,7 +193,6 @@ void gfs2_set_inode_flags(struct inode *inode) GFS2_DIF_APPENDONLY| \ GFS2_DIF_NOATIME| \ GFS2_DIF_SYNC| \ - GFS2_DIF_SYSTEM| \ GFS2_DIF_TOPDIR| \ GFS2_DIF_INHERIT_JDATA) @@ -249,10 +231,6 @@ static int do_gfs2_set_flags(struct file *filp, u32 reqflags, u32 mask) if ((new_flags ^ flags) == 0) goto out; - error = -EINVAL; - if ((new_flags ^ flags) & ~GFS2_FLAGS_USER_SET) - goto out; - error = -EPERM; if (IS_IMMUTABLE(inode) && (new_flags & GFS2_DIF_IMMUTABLE)) goto out; @@ -303,19 +281,33 @@ out_drop_write: static int gfs2_set_flags(struct file *filp, u32 __user *ptr) { struct inode *inode = file_inode(filp); - u32 fsflags, gfsflags; + u32 fsflags, gfsflags = 0; + u32 mask; + int i; if (get_user(fsflags, ptr)) return -EFAULT; - gfsflags = fsflags_cvt(fsflags_to_gfs2, fsflags); - if (!S_ISDIR(inode->i_mode)) { - gfsflags &= ~GFS2_DIF_TOPDIR; - if (gfsflags & GFS2_DIF_INHERIT_JDATA) - gfsflags ^= (GFS2_DIF_JDATA | GFS2_DIF_INHERIT_JDATA); - return do_gfs2_set_flags(filp, gfsflags, ~GFS2_DIF_SYSTEM); + for (i = 0; i < ARRAY_SIZE(fsflag_gfs2flag); i++) { + if (fsflags & fsflag_gfs2flag[i].fsflag) { + fsflags &= ~fsflag_gfs2flag[i].fsflag; + gfsflags |= fsflag_gfs2flag[i].gfsflag; + } + } + if (fsflags || gfsflags & ~GFS2_FLAGS_USER_SET) + return -EINVAL; + + mask = GFS2_FLAGS_USER_SET; + if (S_ISDIR(inode->i_mode)) { + mask &= ~GFS2_DIF_JDATA; + } else { + /* The GFS2_DIF_TOPDIR flag is only valid for directories. */ + if (gfsflags & GFS2_DIF_TOPDIR) + return -EINVAL; + mask &= ~(GFS2_DIF_TOPDIR | GFS2_DIF_INHERIT_JDATA); } - return do_gfs2_set_flags(filp, gfsflags, ~(GFS2_DIF_SYSTEM | GFS2_DIF_JDATA)); + + return do_gfs2_set_flags(filp, gfsflags, mask); } static long gfs2_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) -- cgit From b2623c2fe6eb1f757eff5a8fb515fe584caac667 Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Mon, 9 Oct 2017 17:55:58 +0200 Subject: gfs2: Add support for statx inode flags Add support for the STATX_ATTR_ flags in statx. (Compression, encryption, and the nodump flag are not supported by gfs2.) Partially fixes xfstest generic/424. Signed-off-by: Andreas Gruenbacher Reviewed-by: Andrew Price Signed-off-by: Bob Peterson --- fs/gfs2/inode.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'fs') diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c index 4749a6b8e4dd..4e971b1c7f92 100644 --- a/fs/gfs2/inode.c +++ b/fs/gfs2/inode.c @@ -1987,6 +1987,7 @@ static int gfs2_getattr(const struct path *path, struct kstat *stat, struct inode *inode = d_inode(path->dentry); struct gfs2_inode *ip = GFS2_I(inode); struct gfs2_holder gh; + u32 gfsflags; int error; gfs2_holder_mark_uninitialized(&gh); @@ -1996,7 +1997,20 @@ static int gfs2_getattr(const struct path *path, struct kstat *stat, return error; } + gfsflags = ip->i_diskflags; + if (gfsflags & GFS2_DIF_APPENDONLY) + stat->attributes |= STATX_ATTR_APPEND; + if (gfsflags & GFS2_DIF_IMMUTABLE) + stat->attributes |= STATX_ATTR_IMMUTABLE; + + stat->attributes_mask |= (STATX_ATTR_APPEND | + STATX_ATTR_COMPRESSED | + STATX_ATTR_ENCRYPTED | + STATX_ATTR_IMMUTABLE | + STATX_ATTR_NODUMP); + generic_fillattr(inode, stat); + if (gfs2_holder_initialized(&gh)) gfs2_glock_dq_uninit(&gh); -- cgit From d0920a9cd7e735c429c510b523a100db82c937a1 Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Fri, 13 Oct 2017 00:39:38 +0200 Subject: gfs2: Allow gfs2_xattr_set to be called with the glock held On the following call path: gfs2_setattr -> setattr_prepare -> ... -> cap_inode_killpriv -> ... -> gfs2_xattr_set the glock is locked in gfs2_setattr, so check for recursive locking in gfs2_xattr_set as gfs2_xattr_get already does. While at it, get rid of need_unlock in gfs2_xattr_get. Fixes xfstest generic/093. Signed-off-by: Andreas Gruenbacher Acked-by: Abhijith Das Signed-off-by: Bob Peterson --- fs/gfs2/xattr.c | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) (limited to 'fs') diff --git a/fs/gfs2/xattr.c b/fs/gfs2/xattr.c index 3e96dce21c1c..05de20954659 100644 --- a/fs/gfs2/xattr.c +++ b/fs/gfs2/xattr.c @@ -610,7 +610,6 @@ static int gfs2_xattr_get(const struct xattr_handler *handler, { struct gfs2_inode *ip = GFS2_I(inode); struct gfs2_holder gh; - bool need_unlock = false; int ret; /* During lookup, SELinux calls this function with the glock locked. */ @@ -619,10 +618,11 @@ static int gfs2_xattr_get(const struct xattr_handler *handler, ret = gfs2_glock_nq_init(ip->i_gl, LM_ST_SHARED, LM_FLAG_ANY, &gh); if (ret) return ret; - need_unlock = true; + } else { + gfs2_holder_mark_uninitialized(&gh); } ret = __gfs2_xattr_get(inode, name, buffer, size, handler->flags); - if (need_unlock) + if (gfs2_holder_initialized(&gh)) gfs2_glock_dq_uninit(&gh); return ret; } @@ -1244,11 +1244,20 @@ static int gfs2_xattr_set(const struct xattr_handler *handler, if (ret) return ret; - ret = gfs2_glock_nq_init(ip->i_gl, LM_ST_EXCLUSIVE, 0, &gh); - if (ret) - return ret; + /* May be called from gfs_setattr with the glock locked. */ + + if (!gfs2_glock_is_locked_by_me(ip->i_gl)) { + ret = gfs2_glock_nq_init(ip->i_gl, LM_ST_EXCLUSIVE, 0, &gh); + if (ret) + return ret; + } else { + if (WARN_ON_ONCE(ip->i_gl->gl_state != LM_ST_EXCLUSIVE)) + return -EIO; + gfs2_holder_mark_uninitialized(&gh); + } ret = __gfs2_xattr_set(inode, name, value, size, flags, handler->flags); - gfs2_glock_dq_uninit(&gh); + if (gfs2_holder_initialized(&gh)) + gfs2_glock_dq_uninit(&gh); return ret; } -- cgit