nilfs2: add support for FITRIM ioctl
v3 cover nilfs2: add support for FITRIM ioctl
Hi,
This patch adds support for the FITRIM ioctl, which allows user space tools like fstrim to issue TRIM/DISCARD requests to the underlying device. It takes a fstrim_range structure as a parameter and for every clean segment in the specified range the function blkdev_issue_discard is called. The range is truncated to file system block boundaries.
I tested it with a 32 bit and 64 bit kernel. On the 32 bit system CONFIG_LBDAF was disabled so that sector_t was 32 bit.
Best regards,
Andreas Rohner
---v2->v3 (based on review by Ryusuke Konishi)
* Fix integer overflow
* Round range to block boundary instead of sector boundary
* Move range check to nilfs_sufile_trim_fs()v1->v2 (based on review by Ryusuke Konishi)
* Remove upper limit of minlen
* Add check for minlen
* Round range to sector boundary instead of segment boundary
* Fix minor bug
* Use kmap_atomic instead of kmap
* Move input checks to ioctl.c
* Use nilfs_sufile_segment_usages_in_block()
--
Andreas Rohner (2):
nilfs2: add nilfs_sufile_trim_fs to trim clean segs
nilfs2: add FITRIM ioctl support for nilfs2
fs/nilfs2/ioctl.c | 46 +++++++++++++++++ fs/nilfs2/sufile.c | 147 +++++++++++++++++++++++++++++++++++++++++++++++++++++ fs/nilfs2/sufile.h | 1 + 3 files changed, 194 insertions(+)
Signature · 6 lines
1.9.0 -- To unsubscribe from this list: send the line "unsubscribe linux-nilfs" in the body of a message to [email protected] More majordomo info at http://vger.kernel.org/majordomo-info.html
PATCH v3 1/2 nilfs2: add nilfs_sufile_trim_fs to trim clean segs
In reply to a message not in the archiveThis patch adds the nilfs_sufile_trim_fs function, which takes a fstrim_range structure and calls blkdev_issue_discard for every clean segment in the specified range. The range is truncated to file system block boundaries.
--- fs/nilfs2/sufile.c | 147 +++++++++++++++++++++++++++++++++++++++++++++++++++++ fs/nilfs2/sufile.h | 1 + 2 files changed, 148 insertions(+)
fs/nilfs2/sufile.c +147 -0
@@ -870,6 +870,153 @@ ssize_t nilfs_sufile_get_suinfo(struct inode *sufile, __u64 segnum, void *buf, }/** +* nilfs_sufile_trim_fs() - trim ioctl handle function +* @sufile: inode of segment usage file +* @range: fstrim_range structure +* +* start: First Byte to trim +* len: number of Bytes to trim from start +* minlen: minimum extent length in Bytes +* +* Decription: nilfs_sufile_trim_fs goes through all segments containing bytes +* from start to start+len. start is rounded up to the next block boundary +* and start+len is rounded down. For each clean segment blkdev_issue_discard +* function is invoked. +* +* Return Value: On success, 0 is returned or negative error code, otherwise. +*/ +int nilfs_sufile_trim_fs (struct inode *sufile,struct fstrim_range *range) +{ +struct the_nilfs *nilfs = sufile->i_sb->s_fs_info; +struct buffer_head *su_bh; +struct nilfs_segment_usage *su; +void *kaddr; +size_t n, i, susz =NILFS_MDT (sufile)->mi_entry_size; +sector_t seg_start, seg_end, start_block, end_block; +sector_t start =0 , nblocks =0 ; +u64 segnum, segnum_end, minlen, len, max_blocks, ndiscarded =0 ; +int ret =0 ; +unsigned int sects_per_block; + + sects_per_block = (1 << nilfs->ns_blocksize_bits) / +bdev_logical_block_size (nilfs->ns_bdev); + len = range->len >> nilfs->ns_blocksize_bits; + minlen = range->minlen >> nilfs->ns_blocksize_bits; + max_blocks = ((u64 )nilfs->ns_nsegments * nilfs->ns_blocks_per_segment); + +if (!len || range->start >= max_blocks << nilfs->ns_blocksize_bits) +return -EINVAL ; + + start_block = (range->start + nilfs->ns_blocksize -1 ) >> + nilfs->ns_blocksize_bits; + +if (max_blocks - start_block < len) + end_block = max_blocks -1 ; +else + end_block = start_block + len -1 ; + + segnum =nilfs_get_segnum_of_block (nilfs, start_block); + segnum_end =nilfs_get_segnum_of_block (nilfs, end_block); + +down_read (&NILFS_MDT (sufile)->mi_sem); + +while (segnum <= segnum_end) { + n =nilfs_sufile_segment_usages_in_block (sufile, segnum, + segnum_end); + + ret =nilfs_sufile_get_segment_usage_block (sufile, segnum,0 , + &su_bh); +if (ret <0 ) { +if (ret != -ENOENT ) + goto out_sem; +/* hole */ + segnum += n; +continue ; + } + + kaddr =kmap_atomic (su_bh->b_page); + su =nilfs_sufile_block_get_segment_usage (sufile, segnum, + su_bh, kaddr); +for (i =0 ; i < n; ++i, ++segnum, su = (void *)su + susz) { +if (!nilfs_segment_usage_clean (su)) +continue ; + +nilfs_get_segment_range (nilfs, segnum, &seg_start, + &seg_end); + +if (!nblocks) { +/* start new extent */ + start = seg_start; + nblocks = seg_end - seg_start +1 ; +continue ; + } + +if (start + nblocks == seg_start) { +/* add to previous extent */ + nblocks += seg_end - seg_start +1 ; +continue ; + } + +/* discard previous extent */ +if (start < start_block) { + nblocks -= start_block - start; + start = start_block; + } + +if (nblocks >= minlen) { +kunmap_atomic (kaddr); + + ret =blkdev_issue_discard (nilfs->ns_bdev, + start * sects_per_block, + nblocks * sects_per_block, +GFP_NOFS ,0 ); +if (ret <0 ) { +put_bh (su_bh); + goto out_sem; + } + + ndiscarded += nblocks; + kaddr =kmap_atomic (su_bh->b_page); + su =nilfs_sufile_block_get_segment_usage ( + sufile, segnum, su_bh, kaddr); + } + +/* start new extent */ + start = seg_start; + nblocks = seg_end - seg_start +1 ; + } +kunmap_atomic (kaddr); +put_bh (su_bh); + } + + +if (nblocks) { +/* discard last extent */ +if (start < start_block) { + nblocks -= start_block - start; + start = start_block; + } +if (start + nblocks > end_block +1 ) + nblocks = end_block - start +1 ; + +if (nblocks >= minlen) { + ret =blkdev_issue_discard (nilfs->ns_bdev, + start * sects_per_block, + nblocks * sects_per_block, +GFP_NOFS ,0 ); +if (!ret) + ndiscarded += nblocks; + } + } + +out_sem: +up_read (&NILFS_MDT (sufile)->mi_sem); + + range->len = ndiscarded << nilfs->ns_blocksize_bits; +return ret; +} + +/** * nilfs_sufile_read - read or get sufile inode * @sb: super block instance * @susize: size of a segment usage entry
fs/nilfs2/sufile.h +1 -0
@@ -65,6 +65,7 @@ void nilfs_sufile_do_set_error(struct inode *, __u64, struct buffer_head *,int nilfs_sufile_resize (struct inode *sufile,__u64 newnsegs);int nilfs_sufile_read (struct super_block *sb,size_t susize,struct nilfs_inode *raw_inode,struct inode **inodep); +int nilfs_sufile_trim_fs (struct inode *sufile,struct fstrim_range *range);/** * nilfs_sufile_scrap - make a segment garbage
Signature · 6 lines
1.9.0 -- To unsubscribe from this list: send the line "unsubscribe linux-nilfs" in the body of a message to [email protected] More majordomo info at http://vger.kernel.org/majordomo-info.html
PATCH v3 2/2 nilfs2: add FITRIM ioctl support for nilfs2
In reply to a message not in the archiveThis patch adds support for the FITRIM ioctl, which enables user space tools to issue TRIM/DISCARD requests to the underlying device. Every clean segment within the specified range will be discarded.
--- fs/nilfs2/ioctl.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+)
fs/nilfs2/ioctl.c +46 -0
@@ -1072,6 +1072,49 @@ out: }/** +* nilfs_ioctl_trim_fs() - trim ioctl handle function +* @inode: inode object +* @argp: pointer on argument from userspace +* +* Decription: nilfs_ioctl_trim_fs is the FITRIM ioctl handle function. It +* checks the arguments from userspace and calls nilfs_sufile_trim_fs, which +* performs the actual trim operation. +* +* Return Value: On success, 0 is returned or negative error code, otherwise. +*/ +static int nilfs_ioctl_trim_fs (struct inode *inode,void __user *argp) +{ +struct the_nilfs *nilfs = inode->i_sb->s_fs_info; +struct request_queue *q =bdev_get_queue (nilfs->ns_bdev); +struct fstrim_range range; +int ret; + +if (!capable (CAP_SYS_ADMIN )) +return -EPERM ; + +if (!blk_queue_discard (q)) +return -EOPNOTSUPP ; + +if (copy_from_user (&range, argp,sizeof (range))) +return -EFAULT ; + + range.minlen = max_t(unsigned int, (unsigned int)range.minlen, + q->limits.discard_granularity); + +down_read (&nilfs->ns_segctor_sem); + ret =nilfs_sufile_trim_fs (nilfs->ns_sufile, &range); +up_read (&nilfs->ns_segctor_sem); + +if (ret <0 ) +return ret; + +if (copy_to_user (argp, &range,sizeof (range))) +return -EFAULT ; + +return 0 ; +} + +/** * nilfs_ioctl_set_alloc_range - limit range of segments to be allocated * @inode: inode object * @argp: pointer on argument from userspace
@@ -1205,6 +1248,8 @@ long nilfs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)return nilfs_ioctl_resize (inode, filp, argp);case NILFS_IOCTL_SET_ALLOC_RANGE :return nilfs_ioctl_set_alloc_range (inode, argp); +case FITRIM : +return nilfs_ioctl_trim_fs (inode, argp);default :return -ENOTTY ; }
@@ -1235,6 +1280,7 @@ long nilfs_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)case NILFS_IOCTL_SYNC :case NILFS_IOCTL_RESIZE :case NILFS_IOCTL_SET_ALLOC_RANGE : +case FITRIM :break ;default :return -ENOIOCTLCMD ;
Signature · 6 lines
1.9.0 -- To unsubscribe from this list: send the line "unsubscribe linux-nilfs" in the body of a message to [email protected] More majordomo info at http://vger.kernel.org/majordomo-info.html
Re: [PATCH v3 2/2] nilfs2: add FITRIM ioctl support for nilfs2
In reply to a message not in the archiveHi Andreas,
Quoted 35 lines · Andreas Rohner
This patch adds support for the FITRIM ioctl, which enables user space tools to issue TRIM/DISCARD requests to the underlying device. Every clean segment within the specified range will be discarded.
Signed-off-by Andreas Rohner <[email protected]>--- fs/nilfs2/ioctl.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+)fs/nilfs2/ioctl.c +18
-0@@ -1072,6 +1072,49 @@ out: }/** +* nilfs_ioctl_trim_fs() - trim ioctl handle function +* @inode: inode object +* @argp: pointer on argument from userspace +* +* Decription: nilfs_ioctl_trim_fs is the FITRIM ioctl handle function. It +* checks the arguments from userspace and calls nilfs_sufile_trim_fs, which +* performs the actual trim operation. +* +* Return Value: On success, 0 is returned or negative error code, otherwise. +*/ +static int nilfs_ioctl_trim_fs (struct inode *inode,void __user *argp) +{ +struct the_nilfs *nilfs = inode->i_sb->s_fs_info; +struct request_queue *q =bdev_get_queue (nilfs->ns_bdev); +struct fstrim_range range; +int ret; + +if (!capable (CAP_SYS_ADMIN ))
fs/nilfs2/ioctl.c out: · line 1102
Quoted 10 lines
fs/nilfs2/ioctl.c +10
-0+return -EPERM ; + +if (!blk_queue_discard (q)) +return -EOPNOTSUPP ; + +if (copy_from_user (&range, argp,sizeof (range))) +return -EFAULT ; + + range.minlen = max_t(unsigned int, (unsigned int)range.minlen, + q->limits.discard_granularity);
I just noticed that this can overflow since range.minlen has __u64 type. Should it be as follows ?
range.minlen = max_t(u64, range.minlen, q->limits.discard_granularity);
Regards,
Ryusuke Konishi
Quoted 42 lines
fs/nilfs2/ioctl.c +18
-0+ +down_read (&nilfs->ns_segctor_sem); + ret =nilfs_sufile_trim_fs (nilfs->ns_sufile, &range); +up_read (&nilfs->ns_segctor_sem); + +if (ret <0 ) +return ret; + +if (copy_to_user (argp, &range,sizeof (range))) +return -EFAULT ; + +return 0 ; +} + +/** * nilfs_ioctl_set_alloc_range - limit range of segments to be allocated * @inode: inode object * @argp: pointer on argument from userspace @@ -1205,6 +1248,8 @@ long nilfs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)return nilfs_ioctl_resize (inode, filp, argp);case NILFS_IOCTL_SET_ALLOC_RANGE :return nilfs_ioctl_set_alloc_range (inode, argp); +case FITRIM : +return nilfs_ioctl_trim_fs (inode, argp);default :return -ENOTTY ; }@@ -1235,6 +1280,7 @@ long nilfs_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)case NILFS_IOCTL_SYNC :case NILFS_IOCTL_RESIZE :case NILFS_IOCTL_SET_ALLOC_RANGE : +case FITRIM :break ;default :return -ENOIOCTLCMD ;Signature · 6 lines
1.9.0 -- To unsubscribe from this list: send the line "unsubscribe linux-nilfs" in the body of a message to [email protected] More majordomo info at http://vger.kernel.org/majordomo-info.html
--
To unsubscribe from this list: send the line "unsubscribe linux-nilfs" in the body of a message to [email protected] More majordomo info at http://vger.kernel.org/majordomo-info.html
Re: [PATCH v3 1/2] nilfs2: add nilfs_sufile_trim_fs to trim clean segs
In reply to a message not in the archiveQuoted 54 lines · Andreas Rohner
This patch adds the
nilfs_sufile_trim_fsfunction, which takes afstrim_rangestructure and callsblkdev_issue_discardfor every clean segment in the specified range. The range is truncated to file system block boundaries.Signed-off-by Andreas Rohner <[email protected]>--- fs/nilfs2/sufile.c | 147 +++++++++++++++++++++++++++++++++++++++++++++++++++++ fs/nilfs2/sufile.h | 1 + 2 files changed, 148 insertions(+)fs/nilfs2/sufile.c +35
-0@@ -870,6 +870,153 @@ ssize_t nilfs_sufile_get_suinfo(struct inode *sufile, __u64 segnum, void *buf, }/** +* nilfs_sufile_trim_fs() - trim ioctl handle function +* @sufile: inode of segment usage file +* @range: fstrim_range structure +* +* start: First Byte to trim +* len: number of Bytes to trim from start +* minlen: minimum extent length in Bytes +* +* Decription: nilfs_sufile_trim_fs goes through all segments containing bytes +* from start to start+len. start is rounded up to the next block boundary +* and start+len is rounded down. For each clean segment blkdev_issue_discard +* function is invoked. +* +* Return Value: On success, 0 is returned or negative error code, otherwise. +*/ +int nilfs_sufile_trim_fs (struct inode *sufile,struct fstrim_range *range) +{ +struct the_nilfs *nilfs = sufile->i_sb->s_fs_info; +struct buffer_head *su_bh; +struct nilfs_segment_usage *su; +void *kaddr; +size_t n, i, susz =NILFS_MDT (sufile)->mi_entry_size; +sector_t seg_start, seg_end, start_block, end_block; +sector_t start =0 , nblocks =0 ; +u64 segnum, segnum_end, minlen, len, max_blocks, ndiscarded =0 ; +int ret =0 ; +unsigned int sects_per_block; + + sects_per_block = (1 << nilfs->ns_blocksize_bits) / +bdev_logical_block_size (nilfs->ns_bdev); + len = range->len >> nilfs->ns_blocksize_bits; + minlen = range->minlen >> nilfs->ns_blocksize_bits; + max_blocks = ((u64 )nilfs->ns_nsegments * nilfs->ns_blocks_per_segment); + +if (!len || range->start >= max_blocks << nilfs->ns_blocksize_bits)
fs/nilfs2/sufile.c ssize_t nilfs_sufile_get_suinfo(struct inode *sufile, __u64 segnum, void *buf, · line 917
Quoted 10 lines
fs/nilfs2/sufile.c +9
-0+return -EINVAL ; + + start_block = (range->start + nilfs->ns_blocksize -1 ) >> + nilfs->ns_blocksize_bits; + +if (max_blocks - start_block < len) + end_block = max_blocks -1 ; +else + end_block = start_block + len -1 ;
This patch looks good to me. When you resubmit this series, please add a comment to clarify why we do not simplify this as follows:
end_block = min_t(sector_t, max_blocks, start_block + len) - 1;
For example,
/* * range->len can be very large (actually, it is set to * ULLONG_MAX by default) - truncate upper end of the range * carefully so as not to overflow. */ if (max_blocks - start_block < len) end_block = max_blocks - 1; else end_block = start_block + len - 1;
Regards,
Ryusuke Konishi
Quoted 125 lines
fs/nilfs2/sufile.c +103
-0+ + segnum =nilfs_get_segnum_of_block (nilfs, start_block); + segnum_end =nilfs_get_segnum_of_block (nilfs, end_block); + +down_read (&NILFS_MDT (sufile)->mi_sem); + +while (segnum <= segnum_end) { + n =nilfs_sufile_segment_usages_in_block (sufile, segnum, + segnum_end); + + ret =nilfs_sufile_get_segment_usage_block (sufile, segnum,0 , + &su_bh); +if (ret <0 ) { +if (ret != -ENOENT ) + goto out_sem; +/* hole */ + segnum += n; +continue ; + } + + kaddr =kmap_atomic (su_bh->b_page); + su =nilfs_sufile_block_get_segment_usage (sufile, segnum, + su_bh, kaddr); +for (i =0 ; i < n; ++i, ++segnum, su = (void *)su + susz) { +if (!nilfs_segment_usage_clean (su)) +continue ; + +nilfs_get_segment_range (nilfs, segnum, &seg_start, + &seg_end); + +if (!nblocks) { +/* start new extent */ + start = seg_start; + nblocks = seg_end - seg_start +1 ; +continue ; + } + +if (start + nblocks == seg_start) { +/* add to previous extent */ + nblocks += seg_end - seg_start +1 ; +continue ; + } + +/* discard previous extent */ +if (start < start_block) { + nblocks -= start_block - start; + start = start_block; + } + +if (nblocks >= minlen) { +kunmap_atomic (kaddr); + + ret =blkdev_issue_discard (nilfs->ns_bdev, + start * sects_per_block, + nblocks * sects_per_block, +GFP_NOFS ,0 ); +if (ret <0 ) { +put_bh (su_bh); + goto out_sem; + } + + ndiscarded += nblocks; + kaddr =kmap_atomic (su_bh->b_page); + su =nilfs_sufile_block_get_segment_usage ( + sufile, segnum, su_bh, kaddr); + } + +/* start new extent */ + start = seg_start; + nblocks = seg_end - seg_start +1 ; + } +kunmap_atomic (kaddr); +put_bh (su_bh); + } + + +if (nblocks) { +/* discard last extent */ +if (start < start_block) { + nblocks -= start_block - start; + start = start_block; + } +if (start + nblocks > end_block +1 ) + nblocks = end_block - start +1 ; + +if (nblocks >= minlen) { + ret =blkdev_issue_discard (nilfs->ns_bdev, + start * sects_per_block, + nblocks * sects_per_block, +GFP_NOFS ,0 ); +if (!ret) + ndiscarded += nblocks; + } + } + +out_sem: +up_read (&NILFS_MDT (sufile)->mi_sem); + + range->len = ndiscarded << nilfs->ns_blocksize_bits; +return ret; +} + +/** * nilfs_sufile_read - read or get sufile inode * @sb: super block instance * @susize: size of a segment usage entry
fs/nilfs2/sufile.h +1
-0@@ -65,6 +65,7 @@ void nilfs_sufile_do_set_error(struct inode *, __u64, struct buffer_head *,int nilfs_sufile_resize (struct inode *sufile,__u64 newnsegs);int nilfs_sufile_read (struct super_block *sb,size_t susize,struct nilfs_inode *raw_inode,struct inode **inodep); +int nilfs_sufile_trim_fs (struct inode *sufile,struct fstrim_range *range);/** * nilfs_sufile_scrap - make a segment garbage Signature · 6 lines
1.9.0 -- To unsubscribe from this list: send the line "unsubscribe linux-nilfs" in the body of a message to [email protected] More majordomo info at http://vger.kernel.org/majordomo-info.html
--
To unsubscribe from this list: send the line "unsubscribe linux-nilfs" in the body of a message to [email protected] More majordomo info at http://vger.kernel.org/majordomo-info.html