Commit f663b5b3 authored by Wentao Wang's avatar Wentao Wang Committed by Linus Torvalds
Browse files

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: default avatarWentao Wang <witallwang@gmail.com>
Signed-off-by: default avatarOGAWA Hirofumi <hirofumi@mail.parknet.co.jp>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent a13f085d
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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,
+102 −0
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@
 */

#include <linux/blkdev.h>
#include <linux/sched/signal.h>
#include "fat.h"

struct fatent_operations {
@@ -690,3 +691,104 @@ out:
	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;
}
+33 −0
Original line number Diff line number Diff line
@@ -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 */
	}