1 /** 2 * ioctl.c - Processing of ioctls 3 * 4 * This module is part of ntfs-3g library 5 * 6 * Copyright (c) 2014-2019 Jean-Pierre Andre 7 * Copyright (c) 2014 Red Hat, Inc. 8 * 9 * This program/include file is free software; you can redistribute it and/or 10 * modify it under the terms of the GNU General Public License as published 11 * by the Free Software Foundation; either version 2 of the License, or 12 * (at your option) any later version. 13 * 14 * This program/include file is distributed in the hope that it will be 15 * useful, but WITHOUT ANY WARRANTY; without even the implied warranty 16 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 * GNU General Public License for more details. 18 * 19 * You should have received a copy of the GNU General Public License 20 * along with this program (in the main directory of the NTFS-3G 21 * distribution in the file COPYING); if not, write to the Free Software 22 * Foundation,Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 23 */ 24 25 #include "config.h" 26 27 #ifdef HAVE_STDIO_H 28 #include <stdio.h> 29 #endif 30 #ifdef HAVE_INTTYPES_H 31 #include <inttypes.h> 32 #endif 33 #ifdef HAVE_STRING_H 34 #include <string.h> 35 #endif 36 #ifdef HAVE_ERRNO_H 37 #include <errno.h> 38 #endif 39 #ifdef HAVE_FCNTL_H 40 #include <fcntl.h> 41 #endif 42 #ifdef HAVE_UNISTD_H 43 #include <unistd.h> 44 #endif 45 #ifdef HAVE_STDLIB_H 46 #include <stdlib.h> 47 #endif 48 #ifdef HAVE_LIMITS_H 49 #include <limits.h> 50 #endif 51 #include <syslog.h> 52 #ifdef HAVE_SYS_TYPES_H 53 #include <sys/types.h> 54 #endif 55 #ifdef MAJOR_IN_MKDEV 56 #include <sys/mkdev.h> 57 #endif 58 #ifdef MAJOR_IN_SYSMACROS 59 #include <sys/sysmacros.h> 60 #endif 61 62 #ifdef HAVE_SYS_STAT_H 63 #include <sys/stat.h> 64 #endif 65 66 #ifdef HAVE_LINUX_FS_H 67 #include <linux/fs.h> 68 #endif 69 70 #include "compat.h" 71 #include "debug.h" 72 #include "bitmap.h" 73 #include "attrib.h" 74 #include "inode.h" 75 #include "layout.h" 76 #include "volume.h" 77 #include "index.h" 78 #include "logging.h" 79 #include "ntfstime.h" 80 #include "unistr.h" 81 #include "dir.h" 82 #include "security.h" 83 #include "ioctl.h" 84 #include "misc.h" 85 86 #if defined(FITRIM) && defined(BLKDISCARD) 87 88 /* Issue a TRIM request to the underlying device for the given clusters. */ 89 static int fstrim_clusters(ntfs_volume *vol, LCN lcn, s64 length) 90 { 91 struct ntfs_device *dev = vol->dev; 92 uint64_t range[2]; 93 94 ntfs_log_debug("fstrim_clusters: %lld length %lld\n", 95 (long long) lcn, (long long) length); 96 97 range[0] = lcn << vol->cluster_size_bits; 98 range[1] = length << vol->cluster_size_bits; 99 100 if (dev->d_ops->ioctl(dev, BLKDISCARD, range) == -1) { 101 ntfs_log_debug("fstrim_one_cluster: ioctl failed: %m\n"); 102 return -errno; 103 } 104 return 0; 105 } 106 107 static int read_line(const char *path, char *line, size_t max_bytes) 108 { 109 FILE *fp; 110 111 fp = fopen(path, "r"); 112 if (fp == NULL) 113 return -errno; 114 if (fgets(line, max_bytes, fp) == NULL) { 115 int ret = -EIO; /* fgets doesn't set errno */ 116 fclose(fp); 117 return ret; 118 } 119 fclose (fp); 120 return 0; 121 } 122 123 static int read_u64(const char *path, u64 *n) 124 { 125 char line[64]; 126 int ret; 127 128 ret = read_line(path, line, sizeof line); 129 if (ret) 130 return ret; 131 if (sscanf(line, "%" SCNu64, n) != 1) 132 return -EINVAL; 133 return 0; 134 } 135 136 /* Find discard limits for current backing device. 137 */ 138 static int fstrim_limits(ntfs_volume *vol, 139 u64 *discard_alignment, 140 u64 *discard_granularity, 141 u64 *discard_max_bytes) 142 { 143 struct stat statbuf; 144 char path1[40]; /* holds "/sys/dev/block/%d:%d" */ 145 char path2[40 + sizeof(path1)]; /* less than 40 bytes more than path1 */ 146 int ret; 147 148 /* Stat the backing device. Caller has ensured it is a block device. */ 149 if (stat(vol->dev->d_name, &statbuf) == -1) { 150 ntfs_log_debug("fstrim_limits: could not stat %s\n", 151 vol->dev->d_name); 152 return -errno; 153 } 154 155 /* For whole devices, 156 * /sys/dev/block/MAJOR:MINOR/discard_alignment 157 * /sys/dev/block/MAJOR:MINOR/queue/discard_granularity 158 * /sys/dev/block/MAJOR:MINOR/queue/discard_max_bytes 159 * will exist. 160 * For partitions, we also need to check the parent device: 161 * /sys/dev/block/MAJOR:MINOR/../queue/discard_granularity 162 * /sys/dev/block/MAJOR:MINOR/../queue/discard_max_bytes 163 */ 164 snprintf(path1, sizeof path1, "/sys/dev/block/%d:%d", 165 major(statbuf.st_rdev), minor(statbuf.st_rdev)); 166 167 snprintf(path2, sizeof path2, "%s/discard_alignment", path1); 168 ret = read_u64(path2, discard_alignment); 169 if (ret) { 170 if (ret != -ENOENT) 171 return ret; 172 else 173 /* We would expect this file to exist on all 174 * modern kernels. But for the sake of very 175 * old kernels: 176 */ 177 goto not_found; 178 } 179 180 snprintf(path2, sizeof path2, "%s/queue/discard_granularity", path1); 181 ret = read_u64(path2, discard_granularity); 182 if (ret) { 183 if (ret != -ENOENT) 184 return ret; 185 else { 186 snprintf(path2, sizeof path2, 187 "%s/../queue/discard_granularity", path1); 188 ret = read_u64(path2, discard_granularity); 189 if (ret) { 190 if (ret != -ENOENT) 191 return ret; 192 else 193 goto not_found; 194 } 195 } 196 } 197 198 snprintf(path2, sizeof path2, "%s/queue/discard_max_bytes", path1); 199 ret = read_u64(path2, discard_max_bytes); 200 if (ret) { 201 if (ret != -ENOENT) 202 return ret; 203 else { 204 snprintf(path2, sizeof path2, 205 "%s/../queue/discard_max_bytes", path1); 206 ret = read_u64(path2, discard_max_bytes); 207 if (ret) { 208 if (ret != -ENOENT) 209 return ret; 210 else 211 goto not_found; 212 } 213 } 214 } 215 216 return 0; 217 218 not_found: 219 /* If we reach here then we didn't find the device. This is 220 * not an error, but set discard_max_bytes = 0 to indicate 221 * that discard is not available. 222 */ 223 *discard_alignment = 0; 224 *discard_granularity = 0; 225 *discard_max_bytes = 0; 226 return 0; 227 } 228 229 static inline LCN align_up(ntfs_volume *vol, LCN lcn, u64 granularity) 230 { 231 u64 aligned; 232 233 aligned = (lcn << vol->cluster_size_bits) + granularity - 1; 234 aligned -= aligned % granularity; 235 return (aligned >> vol->cluster_size_bits); 236 } 237 238 static inline u64 align_down(ntfs_volume *vol, u64 count, u64 granularity) 239 { 240 u64 aligned; 241 242 aligned = count << vol->cluster_size_bits; 243 aligned -= aligned % granularity; 244 return (aligned >> vol->cluster_size_bits); 245 } 246 247 #define FSTRIM_BUFSIZ 4096 248 249 /* Trim the filesystem. 250 * 251 * Free blocks between 'start' and 'start+len-1' (both byte offsets) 252 * are found and TRIM requests are sent to the block device. 'minlen' 253 * is the minimum continguous free range to discard. 254 */ 255 static int fstrim(ntfs_volume *vol, void *data, u64 *trimmed) 256 { 257 struct fstrim_range *range = data; 258 u64 start = range->start; 259 u64 len = range->len; 260 u64 minlen = range->minlen; 261 u64 discard_alignment, discard_granularity, discard_max_bytes; 262 u8 *buf = NULL; 263 LCN start_buf; 264 int ret; 265 266 ntfs_log_debug("fstrim: start=%llu len=%llu minlen=%llu\n", 267 (unsigned long long) start, 268 (unsigned long long) len, 269 (unsigned long long) minlen); 270 271 *trimmed = 0; 272 273 /* Fail if user tries to use the fstrim -o/-l/-m options. 274 * XXX We could fix these limitations in future. 275 */ 276 if (start != 0 || len != (uint64_t)-1) { 277 ntfs_log_error("fstrim: setting start or length is not supported\n"); 278 return -EINVAL; 279 } 280 if (minlen > vol->cluster_size) { 281 ntfs_log_error("fstrim: minlen > cluster size is not supported\n"); 282 return -EINVAL; 283 } 284 285 /* Only block devices are supported. It would be possible to 286 * support backing files (ie. without using loop) but the 287 * ioctls used to punch holes in files are completely 288 * different. 289 */ 290 if (!NDevBlock(vol->dev)) { 291 ntfs_log_error("fstrim: not supported for non-block-device\n"); 292 return -EOPNOTSUPP; 293 } 294 295 ret = fstrim_limits(vol, &discard_alignment, 296 &discard_granularity, &discard_max_bytes); 297 if (ret) 298 return ret; 299 if (discard_alignment != 0) { 300 ntfs_log_error("fstrim: backing device is not aligned for discards\n"); 301 return -EOPNOTSUPP; 302 } 303 304 if (discard_max_bytes == 0) { 305 ntfs_log_error("fstrim: backing device does not support discard (discard_max_bytes == 0)\n"); 306 return -EOPNOTSUPP; 307 } 308 309 /* Sync the device before doing anything. */ 310 ret = ntfs_device_sync(vol->dev); 311 if (ret) 312 return ret; 313 314 /* Read through the bitmap. */ 315 buf = ntfs_malloc(FSTRIM_BUFSIZ); 316 if (buf == NULL) 317 return -errno; 318 for (start_buf = 0; start_buf < vol->nr_clusters; 319 start_buf += FSTRIM_BUFSIZ * 8) { 320 s64 count; 321 s64 br; 322 LCN end_buf, start_lcn; 323 324 /* start_buf is LCN of first cluster in the current buffer. 325 * end_buf is LCN of last cluster + 1 in the current buffer. 326 */ 327 end_buf = start_buf + FSTRIM_BUFSIZ*8; 328 if (end_buf > vol->nr_clusters) 329 end_buf = vol->nr_clusters; 330 count = (end_buf - start_buf) / 8; 331 332 br = ntfs_attr_pread(vol->lcnbmp_na, start_buf/8, count, buf); 333 if (br != count) { 334 if (br >= 0) 335 ret = -EIO; 336 else 337 ret = -errno; 338 goto free_out; 339 } 340 341 /* Trim the clusters in large as possible blocks, but 342 * not larger than discard_max_bytes, and compatible 343 * with the supported trim granularity. 344 */ 345 for (start_lcn = start_buf; start_lcn < end_buf; ++start_lcn) { 346 if (!ntfs_bit_get(buf, start_lcn-start_buf)) { 347 LCN end_lcn; 348 LCN aligned_lcn; 349 u64 aligned_count; 350 351 /* Cluster 'start_lcn' is not in use, 352 * find end of this run. 353 */ 354 end_lcn = start_lcn+1; 355 while (end_lcn < end_buf && 356 (u64) (end_lcn-start_lcn) << vol->cluster_size_bits 357 < discard_max_bytes && 358 !ntfs_bit_get(buf, end_lcn-start_buf)) 359 end_lcn++; 360 aligned_lcn = align_up(vol, start_lcn, 361 discard_granularity); 362 if (aligned_lcn >= end_lcn) 363 aligned_count = 0; 364 else { 365 aligned_count = 366 align_down(vol, 367 end_lcn - aligned_lcn, 368 discard_granularity); 369 } 370 if (aligned_count) { 371 ret = fstrim_clusters(vol, 372 aligned_lcn, aligned_count); 373 if (ret) 374 goto free_out; 375 376 *trimmed += aligned_count 377 << vol->cluster_size_bits; 378 } 379 start_lcn = end_lcn-1; 380 } 381 } 382 } 383 384 ret = 0; 385 free_out: 386 free(buf); 387 return ret; 388 } 389 390 #endif /* FITRIM && BLKDISCARD */ 391 392 int ntfs_ioctl(ntfs_inode *ni, unsigned long cmd, 393 void *arg __attribute__((unused)), 394 unsigned int flags __attribute__((unused)), void *data) 395 { 396 int ret = 0; 397 398 switch (cmd) { 399 #if defined(FITRIM) && defined(BLKDISCARD) 400 case FITRIM: 401 if (!ni || !data) 402 ret = -EINVAL; 403 else { 404 u64 trimmed; 405 struct fstrim_range *range = (struct fstrim_range*)data; 406 407 ret = fstrim(ni->vol, data, &trimmed); 408 range->len = trimmed; 409 } 410 break; 411 #else 412 #warning Trimming not supported : FITRIM or BLKDISCARD not defined 413 #endif 414 default : 415 ret = -EINVAL; 416 break; 417 } 418 return (ret); 419 } 420