You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

420 lines
10 KiB

/**
* ioctl.c - Processing of ioctls
*
* This module is part of ntfs-3g library
*
* Copyright (c) 2014-2019 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_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef MAJOR_IN_MKDEV
#include <sys/mkdev.h>
#endif
#ifdef MAJOR_IN_SYSMACROS
#include <sys/sysmacros.h>
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#ifdef HAVE_LINUX_FS_H
#include <linux/fs.h>
#endif
#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.
*/
static int fstrim_limits(ntfs_volume *vol,
u64 *discard_alignment,
u64 *discard_granularity,
u64 *discard_max_bytes)
{
struct stat statbuf;
char path1[40]; /* holds "/sys/dev/block/%d:%d" */
char path2[40 + sizeof(path1)]; /* less than 40 bytes more than path1 */
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;
}
/* For whole devices,
* /sys/dev/block/MAJOR:MINOR/discard_alignment
* /sys/dev/block/MAJOR:MINOR/queue/discard_granularity
* /sys/dev/block/MAJOR:MINOR/queue/discard_max_bytes
* will exist.
* For partitions, we also need to check the parent device:
* /sys/dev/block/MAJOR:MINOR/../queue/discard_granularity
* /sys/dev/block/MAJOR:MINOR/../queue/discard_max_bytes
*/
snprintf(path1, sizeof path1, "/sys/dev/block/%d:%d",
major(statbuf.st_rdev), minor(statbuf.st_rdev));
snprintf(path2, sizeof path2, "%s/discard_alignment", path1);
ret = read_u64(path2, discard_alignment);
if (ret) {
if (ret != -ENOENT)
return ret;
else
/* We would expect this file to exist on all
* modern kernels. But for the sake of very
* old kernels:
*/
goto not_found;
}
snprintf(path2, sizeof path2, "%s/queue/discard_granularity", path1);
ret = read_u64(path2, discard_granularity);
if (ret) {
if (ret != -ENOENT)
return ret;
else {
snprintf(path2, sizeof path2,
"%s/../queue/discard_granularity", path1);
ret = read_u64(path2, discard_granularity);
if (ret) {
if (ret != -ENOENT)
return ret;
else
goto not_found;
}
}
}
snprintf(path2, sizeof path2, "%s/queue/discard_max_bytes", path1);
ret = read_u64(path2, discard_max_bytes);
if (ret) {
if (ret != -ENOENT)
return ret;
else {
snprintf(path2, sizeof path2,
"%s/../queue/discard_max_bytes", path1);
ret = read_u64(path2, discard_max_bytes);
if (ret) {
if (ret != -ENOENT)
return ret;
else
goto not_found;
}
}
}
return 0;
not_found:
/* 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_alignment = 0;
*discard_granularity = 0;
*discard_max_bytes = 0;
return 0;
}
static inline LCN align_up(ntfs_volume *vol, LCN lcn, u64 granularity)
{
u64 aligned;
aligned = (lcn << vol->cluster_size_bits) + granularity - 1;
aligned -= aligned % granularity;
return (aligned >> vol->cluster_size_bits);
}
static inline u64 align_down(ntfs_volume *vol, u64 count, u64 granularity)
{
u64 aligned;
aligned = count << vol->cluster_size_bits;
aligned -= aligned % granularity;
return (aligned >> vol->cluster_size_bits);
}
#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, u64 *trimmed)
{
struct fstrim_range *range = data;
u64 start = range->start;
u64 len = range->len;
u64 minlen = range->minlen;
u64 discard_alignment, 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);
*trimmed = 0;
/* 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_error("fstrim: setting start or length is not supported\n");
return -EINVAL;
}
if (minlen > vol->cluster_size) {
ntfs_log_error("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_error("fstrim: not supported for non-block-device\n");
return -EOPNOTSUPP;
}
ret = fstrim_limits(vol, &discard_alignment,
&discard_granularity, &discard_max_bytes);
if (ret)
return ret;
if (discard_alignment != 0) {
ntfs_log_error("fstrim: backing device is not aligned for discards\n");
return -EOPNOTSUPP;
}
if (discard_max_bytes == 0) {
ntfs_log_error("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, and compatible
* with the supported trim granularity.
*/
for (start_lcn = start_buf; start_lcn < end_buf; ++start_lcn) {
if (!ntfs_bit_get(buf, start_lcn-start_buf)) {
LCN end_lcn;
LCN aligned_lcn;
u64 aligned_count;
/* 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++;
aligned_lcn = align_up(vol, start_lcn,
discard_granularity);
if (aligned_lcn >= end_lcn)
aligned_count = 0;
else {
aligned_count =
align_down(vol,
end_lcn - aligned_lcn,
discard_granularity);
}
if (aligned_count) {
ret = fstrim_clusters(vol,
aligned_lcn, aligned_count);
if (ret)
goto free_out;
*trimmed += aligned_count
<< vol->cluster_size_bits;
}
start_lcn = end_lcn-1;
}
}
}
ret = 0;
free_out:
free(buf);
return ret;
}
#endif /* FITRIM && BLKDISCARD */
int ntfs_ioctl(ntfs_inode *ni, unsigned long 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 {
u64 trimmed;
struct fstrim_range *range = (struct fstrim_range*)data;
ret = fstrim(ni->vol, data, &trimmed);
range->len = trimmed;
}
break;
#else
#warning Trimming not supported : FITRIM or BLKDISCARD not defined
#endif
default :
ret = -EINVAL;
break;
}
return (ret);
}