fat: add FITRIM ioctl for FAT file system

Add FITRIM ioctl for FAT file system

[witallwang@gmail.com: use u64s]
  Link: http://lkml.kernel.org/r/87h8l37hub.fsf@mail.parknet.co.jp
[hirofumi@mail.parknet.co.jp: bug fixes, coding style fixes, add signal check]
Link: http://lkml.kernel.org/r/87fu10anhj.fsf@mail.parknet.co.jp
Signed-off-by: Wentao Wang <witallwang@gmail.com>
Signed-off-by: OGAWA Hirofumi <hirofumi@mail.parknet.co.jp>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
Wentao Wang 2018-08-21 21:59:41 -07:00 committed by Linus Torvalds
parent a13f085d11
commit f663b5b38f
3 changed files with 136 additions and 0 deletions

View file

@ -357,6 +357,7 @@ extern int fat_alloc_clusters(struct inode *inode, int *cluster,
int nr_cluster);
extern int fat_free_clusters(struct inode *inode, int cluster);
extern int fat_count_free_clusters(struct super_block *sb);
extern int fat_trim_fs(struct inode *inode, struct fstrim_range *range);
/* fat/file.c */
extern long fat_generic_ioctl(struct file *filp, unsigned int cmd,

View file

@ -4,6 +4,7 @@
*/
#include <linux/blkdev.h>
#include <linux/sched/signal.h>
#include "fat.h"
struct fatent_operations {
@ -690,3 +691,104 @@ int fat_count_free_clusters(struct super_block *sb)
unlock_fat(sbi);
return err;
}
static int fat_trim_clusters(struct super_block *sb, u32 clus, u32 nr_clus)
{
struct msdos_sb_info *sbi = MSDOS_SB(sb);
return sb_issue_discard(sb, fat_clus_to_blknr(sbi, clus),
nr_clus * sbi->sec_per_clus, GFP_NOFS, 0);
}
int fat_trim_fs(struct inode *inode, struct fstrim_range *range)
{
struct super_block *sb = inode->i_sb;
struct msdos_sb_info *sbi = MSDOS_SB(sb);
const struct fatent_operations *ops = sbi->fatent_ops;
struct fat_entry fatent;
u64 ent_start, ent_end, minlen, trimmed = 0;
u32 free = 0;
unsigned long reada_blocks, reada_mask, cur_block = 0;
int err = 0;
/*
* FAT data is organized as clusters, trim at the granulary of cluster.
*
* fstrim_range is in byte, convert vaules to cluster index.
* Treat sectors before data region as all used, not to trim them.
*/
ent_start = max_t(u64, range->start>>sbi->cluster_bits, FAT_START_ENT);
ent_end = ent_start + (range->len >> sbi->cluster_bits) - 1;
minlen = range->minlen >> sbi->cluster_bits;
if (ent_start >= sbi->max_cluster || range->len < sbi->cluster_size)
return -EINVAL;
if (ent_end >= sbi->max_cluster)
ent_end = sbi->max_cluster - 1;
reada_blocks = FAT_READA_SIZE >> sb->s_blocksize_bits;
reada_mask = reada_blocks - 1;
fatent_init(&fatent);
lock_fat(sbi);
fatent_set_entry(&fatent, ent_start);
while (fatent.entry <= ent_end) {
/* readahead of fat blocks */
if ((cur_block & reada_mask) == 0) {
unsigned long rest = sbi->fat_length - cur_block;
fat_ent_reada(sb, &fatent, min(reada_blocks, rest));
}
cur_block++;
err = fat_ent_read_block(sb, &fatent);
if (err)
goto error;
do {
if (ops->ent_get(&fatent) == FAT_ENT_FREE) {
free++;
} else if (free) {
if (free >= minlen) {
u32 clus = fatent.entry - free;
err = fat_trim_clusters(sb, clus, free);
if (err && err != -EOPNOTSUPP)
goto error;
if (!err)
trimmed += free;
err = 0;
}
free = 0;
}
} while (fat_ent_next(sbi, &fatent) && fatent.entry <= ent_end);
if (fatal_signal_pending(current)) {
err = -ERESTARTSYS;
goto error;
}
if (need_resched()) {
fatent_brelse(&fatent);
unlock_fat(sbi);
cond_resched();
lock_fat(sbi);
}
}
/* handle scenario when tail entries are all free */
if (free && free >= minlen) {
u32 clus = fatent.entry - free;
err = fat_trim_clusters(sb, clus, free);
if (err && err != -EOPNOTSUPP)
goto error;
if (!err)
trimmed += free;
err = 0;
}
error:
fatent_brelse(&fatent);
unlock_fat(sbi);
range->len = trimmed << sbi->cluster_bits;
return err;
}

View file

@ -121,6 +121,37 @@ static int fat_ioctl_get_volume_id(struct inode *inode, u32 __user *user_attr)
return put_user(sbi->vol_id, user_attr);
}
static int fat_ioctl_fitrim(struct inode *inode, unsigned long arg)
{
struct super_block *sb = inode->i_sb;
struct fstrim_range __user *user_range;
struct fstrim_range range;
struct request_queue *q = bdev_get_queue(sb->s_bdev);
int err;
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
if (!blk_queue_discard(q))
return -EOPNOTSUPP;
user_range = (struct fstrim_range __user *)arg;
if (copy_from_user(&range, user_range, sizeof(range)))
return -EFAULT;
range.minlen = max_t(unsigned int, range.minlen,
q->limits.discard_granularity);
err = fat_trim_fs(inode, &range);
if (err < 0)
return err;
if (copy_to_user(user_range, &range, sizeof(range)))
return -EFAULT;
return 0;
}
long fat_generic_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct inode *inode = file_inode(filp);
@ -133,6 +164,8 @@ long fat_generic_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
return fat_ioctl_set_attributes(filp, user_attr);
case FAT_IOCTL_GET_VOLUME_ID:
return fat_ioctl_get_volume_id(inode, user_attr);
case FITRIM:
return fat_ioctl_fitrim(inode, arg);
default:
return -ENOTTY; /* Inappropriate ioctl for device */
}