/** * 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 #endif #ifdef HAVE_INTTYPES_H #include #endif #ifdef HAVE_STRING_H #include #endif #ifdef HAVE_ERRNO_H #include #endif #ifdef HAVE_FCNTL_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_STDLIB_H #include #endif #ifdef HAVE_LIMITS_H #include #endif #include #ifdef HAVE_SYS_TYPES_H #include #endif #ifdef MAJOR_IN_MKDEV #include #endif #ifdef MAJOR_IN_SYSMACROS #include #endif #ifdef HAVE_SYS_STAT_H #include #endif #ifdef HAVE_LINUX_FS_H #include #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); }