Implemented fstrim(8)

fstrim(8) discards unused blocks on a mounted filesystem. It is useful for
solid-state drives (SSDs) and thinly-provisioned storage.
Only trimming the full device (with no option) is supported.

Contributed by Richard W.M. Jones
edge.strict_endians
Jean-Pierre André 2014-07-31 14:03:11 +02:00
parent ae9aeebbbf
commit f4e3f126df
7 changed files with 491 additions and 16 deletions

View File

@ -471,7 +471,8 @@ AC_CHECK_HEADERS([ctype.h fcntl.h libgen.h libintl.h limits.h locale.h \
regex.h endian.h byteswap.h sys/byteorder.h sys/disk.h sys/endian.h \
sys/param.h sys/ioctl.h sys/mkdev.h sys/mount.h sys/stat.h sys/types.h \
sys/vfs.h sys/statvfs.h sys/sysmacros.h linux/major.h linux/fd.h \
linux/hdreg.h machine/endian.h windows.h syslog.h pwd.h malloc.h])
linux/fs.h inttypes.h linux/hdreg.h \
machine/endian.h windows.h syslog.h pwd.h malloc.h])
# Checks for typedefs, structures, and compiler characteristics.
AC_HEADER_STDBOOL

View File

@ -0,0 +1,30 @@
/*
*
* Copyright (c) 2014 Jean-Pierre Andre
*
*/
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program (in the main directory of the NTFS-3G
* distribution in the file COPYING); if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef IOCTL_H
#define IOCTL_H
int ntfs_ioctl(ntfs_inode *ni, int cmd, void *arg,
unsigned int flags, void *data);
#endif /* IOCTL_H */

View File

@ -36,9 +36,7 @@
#ifdef HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
#ifdef HAVE_SYS_MOUNT_H
#include <sys/mount.h>
#endif
/* Do not #include <sys/mount.h> here : conflicts with <linux/fs.h> */
#ifdef HAVE_MNTENT_H
#include <mntent.h>
#endif

View File

@ -31,6 +31,7 @@ libntfs_3g_la_SOURCES = \
efs.c \
index.c \
inode.c \
ioctl.c \
lcnalloc.c \
logfile.c \
logging.c \

382
libntfs-3g/ioctl.c 100644
View File

@ -0,0 +1,382 @@
/**
* ioctl.c - Processing of ioctls
*
* This module is part of ntfs-3g library
*
* Copyright (c) 2014 Jean-Pierre Andre
* Copyright (c) 2014 Red Hat, Inc.
*
* This program/include file is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as published
* by the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program/include file is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program (in the main directory of the NTFS-3G
* distribution in the file COPYING); if not, write to the Free Software
* Foundation,Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "config.h"
#ifdef HAVE_STDIO_H
#include <stdio.h>
#endif
#ifdef HAVE_INTTYPES_H
#include <inttypes.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_LIMITS_H
#include <limits.h>
#endif
#include <syslog.h>
#ifdef HAVE_SETXATTR
#include <sys/xattr.h>
#endif
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#ifdef HAVE_LINUX_FS_H
#include <linux/fs.h>
#endif
#include <dirent.h>
#include "compat.h"
#include "debug.h"
#include "bitmap.h"
#include "attrib.h"
#include "inode.h"
#include "layout.h"
#include "volume.h"
#include "index.h"
#include "logging.h"
#include "ntfstime.h"
#include "unistr.h"
#include "dir.h"
#include "security.h"
#include "ioctl.h"
#include "misc.h"
#if defined(FITRIM) && defined(BLKDISCARD)
/* Issue a TRIM request to the underlying device for the given clusters. */
static int fstrim_clusters(ntfs_volume *vol, LCN lcn, s64 length)
{
struct ntfs_device *dev = vol->dev;
uint64_t range[2];
ntfs_log_debug("fstrim_clusters: %lld length %lld\n",
(long long) lcn, (long long) length);
range[0] = lcn << vol->cluster_size_bits;
range[1] = length << vol->cluster_size_bits;
if (dev->d_ops->ioctl(dev, BLKDISCARD, range) == -1) {
ntfs_log_debug("fstrim_one_cluster: ioctl failed: %m\n");
return -errno;
}
return 0;
}
static int read_line(const char *path, char *line, size_t max_bytes)
{
FILE *fp;
fp = fopen(path, "r");
if (fp == NULL)
return -errno;
if (fgets(line, max_bytes, fp) == NULL) {
int ret = -EIO; /* fgets doesn't set errno */
fclose(fp);
return ret;
}
fclose (fp);
return 0;
}
static int read_u64(const char *path, u64 *n)
{
char line[64];
int ret;
ret = read_line(path, line, sizeof line);
if (ret)
return ret;
if (sscanf(line, "%" SCNu64, n) != 1)
return -EINVAL;
return 0;
}
/* Find discard limits for current backing device.
* XXX Kernel makes this a pain in the neck.
*/
static int fstrim_limits(ntfs_volume *vol, u64 *discard_granularity,
u64 *discard_max_bytes)
{
struct stat statbuf;
DIR *dir;
struct dirent *d;
char path[80];
char line[64];
char dev[64];
int ret;
/* Stat the backing device. Caller has ensured it is a block device. */
if (stat(vol->dev->d_name, &statbuf) == -1) {
ntfs_log_debug("fstrim_limits: could not stat %s\n",
vol->dev->d_name);
return -errno;
}
/* Now look for a /sys/block/<dev>/dev file which contains
* "major:minor\n".
*/
snprintf(dev, sizeof dev, "%d:%d\n",
major(statbuf.st_rdev), minor(statbuf.st_rdev));
dir = opendir("/sys/block");
if (dir == NULL) {
ntfs_log_debug("fstrim_limits: could not open /sys/block\n");
return -errno;
}
for (;;) {
errno = 0;
d = readdir(dir);
if (!d) break;
snprintf(path, sizeof path, "/sys/block/%s/dev", d->d_name);
ret = read_line(path, line, sizeof line);
if (ret)
continue;
if (strcmp(line, dev) == 0)
goto found;
}
/* Check readdir didn't fail. */
if (errno != 0) {
ret = -errno;
ntfs_log_debug("fstrim_limits: readdir failed\n");
goto out;
}
/* If we reach here then we didn't find the device. This is
* not an error, but set discard_max_bytes = 0 to indicate
* that discard is not available.
*/
*discard_granularity = 0;
*discard_max_bytes = 0;
ntfs_log_debug("fstrim_limits: /sys/block entry corresponding to device %s not found\n",
vol->dev->d_name);
ret = 0;
goto out;
found:
/* Found the device at /sys/block/ + d->d_name */
snprintf (path, sizeof path,
"/sys/block/%s/queue/discard_granularity",
d->d_name);
ret = read_u64(path, discard_granularity);
if (ret) {
ntfs_log_debug("fstrim_limits: could not read %s\n", path);
goto out;
}
snprintf (path, sizeof path,
"/sys/block/%s/queue/discard_max_bytes",
d->d_name);
ret = read_u64(path, discard_max_bytes);
if (ret) {
ntfs_log_debug("fstrim_limits: could not read %s\n", path);
goto out;
}
ntfs_log_debug("fstrim_limits: device %s discard granularity = %llu max_bytes = %llu\n",
d->d_name,
(unsigned long long) *discard_granularity,
(unsigned long long) *discard_max_bytes);
ret = 0;
out:
if (closedir (dir) == -1) {
ret = -errno;
ntfs_log_debug("fstrim_limits: closedir failed\n");
return ret;
}
return ret;
}
#define FSTRIM_BUFSIZ 4096
/* Trim the filesystem.
*
* Free blocks between 'start' and 'start+len-1' (both byte offsets)
* are found and TRIM requests are sent to the block device. 'minlen'
* is the minimum continguous free range to discard.
*/
static int fstrim(ntfs_volume *vol, void *data)
{
struct fstrim_range *range = data;
u64 start = range->start;
u64 len = range->len;
u64 minlen = range->minlen;
u64 discard_granularity, discard_max_bytes;
u8 *buf = NULL;
LCN start_buf;
int ret;
ntfs_log_debug("fstrim: start=%llu len=%llu minlen=%llu\n",
(unsigned long long) start,
(unsigned long long) len,
(unsigned long long) minlen);
/* Fail if user tries to use the fstrim -o/-l/-m options.
* XXX We could fix these limitations in future.
*/
if (start != 0 || len != (uint64_t)-1) {
ntfs_log_debug("fstrim: setting start or length is not supported\n");
return -EINVAL;
}
if (minlen > vol->cluster_size) {
ntfs_log_debug("fstrim: minlen > cluster size is not supported\n");
return -EINVAL;
}
/* Only block devices are supported. It would be possible to
* support backing files (ie. without using loop) but the
* ioctls used to punch holes in files are completely
* different.
*/
if (!NDevBlock(vol->dev)) {
ntfs_log_debug("fstrim: not supported for non-block-device\n");
return -EOPNOTSUPP;
}
ret = fstrim_limits(vol, &discard_granularity, &discard_max_bytes);
if (ret)
return ret;
if (discard_granularity > vol->cluster_size) {
ntfs_log_debug("fstrim: discard granularity of backing device is larger than cluster size\n");
return -EOPNOTSUPP;
}
if (discard_max_bytes == 0) {
ntfs_log_debug("fstrim: backing device does not support discard (discard_max_bytes == 0)\n");
return -EOPNOTSUPP;
}
/* Sync the device before doing anything. */
ret = ntfs_device_sync(vol->dev);
if (ret)
return ret;
/* Read through the bitmap. */
buf = ntfs_malloc(FSTRIM_BUFSIZ);
if (buf == NULL)
return -errno;
for (start_buf = 0; start_buf < vol->nr_clusters;
start_buf += FSTRIM_BUFSIZ * 8) {
s64 count;
s64 br;
LCN end_buf, start_lcn;
/* start_buf is LCN of first cluster in the current buffer.
* end_buf is LCN of last cluster + 1 in the current buffer.
*/
end_buf = start_buf + FSTRIM_BUFSIZ*8;
if (end_buf > vol->nr_clusters)
end_buf = vol->nr_clusters;
count = (end_buf - start_buf) / 8;
br = ntfs_attr_pread(vol->lcnbmp_na, start_buf/8, count, buf);
if (br != count) {
if (br >= 0)
ret = -EIO;
else
ret = -errno;
goto free_out;
}
/* Trim the clusters in large as possible blocks, but
* not larger than discard_max_bytes.
*/
for (start_lcn = start_buf; start_lcn < end_buf; ++start_lcn) {
if (!ntfs_bit_get(buf, start_lcn-start_buf)) {
LCN end_lcn;
/* Cluster 'start_lcn' is not in use,
* find end of this run.
*/
end_lcn = start_lcn+1;
while (end_lcn < end_buf &&
(u64) (end_lcn-start_lcn) << vol->cluster_size_bits
< discard_max_bytes &&
!ntfs_bit_get(buf, end_lcn-start_buf))
end_lcn++;
ret = fstrim_clusters(vol,
start_lcn, end_lcn-start_lcn);
if (ret)
goto free_out;
start_lcn = end_lcn-1;
}
}
}
ret = 0;
free_out:
free(buf);
return ret;
}
#endif /* FITRIM && BLKDISCARD */
int ntfs_ioctl(ntfs_inode *ni, int cmd, void *arg __attribute__((unused)),
unsigned int flags __attribute__((unused)), void *data)
{
int ret = 0;
switch (cmd) {
#if defined(FITRIM) && defined(BLKDISCARD)
case FITRIM:
if (!ni || !data)
ret = -EINVAL;
else
ret = fstrim(ni->vol, data);
break;
#else
#warning FITRIM or BLKDISCARD not defined
#endif
default :
ret = -EINVAL;
break;
}
return (ret);
}

View File

@ -83,7 +83,12 @@
#include <sys/param.h>
#endif /* defined(__APPLE__) || defined(__DARWIN__), ... */
#ifdef HAVE_LINUX_FS_H
#include <linux/fs.h>
#endif
#include "compat.h"
#include "bitmap.h"
#include "attrib.h"
#include "inode.h"
#include "volume.h"
@ -99,6 +104,7 @@
#include "logging.h"
#include "xattrs.h"
#include "misc.h"
#include "ioctl.h"
#include "ntfs-3g_common.h"
@ -566,8 +572,6 @@ int ntfs_macfuse_setchgtime(const char *path, const struct timespec *tv)
}
#endif /* defined(__APPLE__) || defined(__DARWIN__) */
#if defined(FUSE_CAP_DONT_MASK) || defined(FUSE_CAP_BIG_WRITES) \
|| (defined(__APPLE__) || defined(__DARWIN__))
static void ntfs_init(void *userdata __attribute__((unused)),
struct fuse_conn_info *conn)
{
@ -584,8 +588,8 @@ static void ntfs_init(void *userdata __attribute__((unused)),
>= SAFE_CAPACITY_FOR_BIG_WRITES))
conn->want |= FUSE_CAP_BIG_WRITES;
#endif
conn->want |= FUSE_CAP_IOCTL_DIR;
}
#endif /* defined(FUSE_CAP_DONT_MASK) || (defined(__APPLE__) || defined(__DARWIN__)) */
static int ntfs_fuse_getstat(struct SECURITY_CONTEXT *scx,
ntfs_inode *ni, struct stat *stbuf)
@ -2600,6 +2604,48 @@ static void ntfs_fuse_fsync(fuse_req_t req,
fuse_reply_err(req, 0);
}
static void ntfs_fuse_ioctl(fuse_req_t req __attribute__((unused)),
fuse_ino_t ino __attribute__((unused)),
int cmd, void *arg,
struct fuse_file_info *fi __attribute__((unused)),
unsigned flags, const void *data,
size_t in_bufsz, size_t out_bufsz)
{
ntfs_inode *ni;
char *buf = (char*)NULL;
int bufsz;
int ret = 0;
if (flags & FUSE_IOCTL_COMPAT) {
ret = -ENOSYS;
} else {
ni = ntfs_inode_open(ctx->vol, INODE(ino));
if (!ni) {
ret = -errno;
goto fail;
}
bufsz = (in_bufsz > out_bufsz ? in_bufsz : out_bufsz);
if (bufsz) {
buf = ntfs_malloc(bufsz);
if (!buf) {
ret = ENOMEM;
goto fail;
}
memcpy(buf, data, in_bufsz);
}
ret = ntfs_ioctl(ni, cmd, arg, flags, buf);
if (ntfs_inode_close (ni))
set_fuse_error(&ret);
}
if (ret)
fail :
fuse_reply_err(req, -ret);
else
fuse_reply_ioctl(req, 0, buf, out_bufsz);
if (buf)
free(buf);
}
static void ntfs_fuse_bmap(fuse_req_t req, fuse_ino_t ino, size_t blocksize,
uint64_t vidx)
{
@ -3551,6 +3597,7 @@ static struct fuse_lowlevel_ops ntfs_3g_ops = {
.fsyncdir = ntfs_fuse_fsync,
.bmap = ntfs_fuse_bmap,
.destroy = ntfs_fuse_destroy2,
.ioctl = ntfs_fuse_ioctl,
#if !KERNELPERMS | (POSIXACLS & !KERNELACLS)
.access = ntfs_fuse_access,
#endif
@ -3567,10 +3614,7 @@ static struct fuse_lowlevel_ops ntfs_3g_ops = {
.setbkuptime = ntfs_macfuse_setbkuptime,
.setchgtime = ntfs_macfuse_setchgtime,
#endif /* defined(__APPLE__) || defined(__DARWIN__) */
#if defined(FUSE_CAP_DONT_MASK) || defined(FUSE_CAP_BIG_WRITES) \
|| (defined(__APPLE__) || defined(__DARWIN__))
.init = ntfs_init
#endif
};
static int ntfs_fuse_init(void)

View File

@ -98,6 +98,7 @@
#include "logging.h"
#include "xattrs.h"
#include "misc.h"
#include "ioctl.h"
#include "ntfs-3g_common.h"
@ -641,8 +642,6 @@ int ntfs_macfuse_setchgtime(const char *path, const struct timespec *tv)
}
#endif /* defined(__APPLE__) || defined(__DARWIN__) */
#if defined(FUSE_CAP_DONT_MASK) || defined(FUSE_CAP_BIG_WRITES) \
|| (defined(__APPLE__) || defined(__DARWIN__))
static void *ntfs_init(struct fuse_conn_info *conn)
{
#if defined(__APPLE__) || defined(__DARWIN__)
@ -658,9 +657,9 @@ static void *ntfs_init(struct fuse_conn_info *conn)
>= SAFE_CAPACITY_FOR_BIG_WRITES))
conn->want |= FUSE_CAP_BIG_WRITES;
#endif
conn->want |= FUSE_CAP_IOCTL_DIR;
return NULL;
}
#endif /* defined(FUSE_CAP_DONT_MASK) || (defined(__APPLE__) || defined(__DARWIN__)) */
static int ntfs_fuse_getattr(const char *org_path, struct stat *stbuf)
{
@ -2445,6 +2444,28 @@ static int ntfs_fuse_fsync(const char *path __attribute__((unused)),
return (ret);
}
static int ntfs_fuse_ioctl(const char *path,
int cmd, void *arg,
struct fuse_file_info *fi __attribute__((unused)),
unsigned int flags, void *data)
{
ntfs_inode *ni;
int ret;
if (flags & FUSE_IOCTL_COMPAT)
return -ENOSYS;
ni = ntfs_pathname_to_inode(ctx->vol, NULL, path);
if (!ni)
return -errno;
ret = ntfs_ioctl(ni, cmd, arg, flags, data);
if (ntfs_inode_close (ni))
set_fuse_error(&ret);
return ret;
}
static int ntfs_fuse_bmap(const char *path, size_t blocksize, uint64_t *idx)
{
ntfs_inode *ni;
@ -3396,6 +3417,7 @@ static struct fuse_operations ntfs_3g_ops = {
.fsyncdir = ntfs_fuse_fsync,
.bmap = ntfs_fuse_bmap,
.destroy = ntfs_fuse_destroy2,
.ioctl = ntfs_fuse_ioctl,
#if !KERNELPERMS | (POSIXACLS & !KERNELACLS)
.access = ntfs_fuse_access,
.opendir = ntfs_fuse_opendir,
@ -3413,10 +3435,7 @@ static struct fuse_operations ntfs_3g_ops = {
.setbkuptime = ntfs_macfuse_setbkuptime,
.setchgtime = ntfs_macfuse_setchgtime,
#endif /* defined(__APPLE__) || defined(__DARWIN__) */
#if defined(FUSE_CAP_DONT_MASK) || defined(FUSE_CAP_BIG_WRITES) \
|| (defined(__APPLE__) || defined(__DARWIN__))
.init = ntfs_init
#endif
};
static int ntfs_fuse_init(void)