1 /** 2 * ioctl.c - Processing of ioctls 3 * 4 * This module is part of ntfs-3g library 5 * 6 * Copyright (c) 2014-2015 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[80], path2[80]; 145 int ret; 146 147 /* Stat the backing device. Caller has ensured it is a block device. */ 148 if (stat(vol->dev->d_name, &statbuf) == -1) { 149 ntfs_log_debug("fstrim_limits: could not stat %s\n", 150 vol->dev->d_name); 151 return -errno; 152 } 153 154 /* For whole devices, 155 * /sys/dev/block/MAJOR:MINOR/discard_alignment 156 * /sys/dev/block/MAJOR:MINOR/queue/discard_granularity 157 * /sys/dev/block/MAJOR:MINOR/queue/discard_max_bytes 158 * will exist. 159 * For partitions, we also need to check the parent device: 160 * /sys/dev/block/MAJOR:MINOR/../queue/discard_granularity 161 * /sys/dev/block/MAJOR:MINOR/../queue/discard_max_bytes 162 */ 163 snprintf(path1, sizeof path1, "/sys/dev/block/%d:%d", 164 major(statbuf.st_rdev), minor(statbuf.st_rdev)); 165 166 snprintf(path2, sizeof path2, "%s/discard_alignment", path1); 167 ret = read_u64(path2, discard_alignment); 168 if (ret) { 169 if (ret != -ENOENT) 170 return ret; 171 else 172 /* We would expect this file to exist on all 173 * modern kernels. But for the sake of very 174 * old kernels: 175 */ 176 goto not_found; 177 } 178 179 snprintf(path2, sizeof path2, "%s/queue/discard_granularity", path1); 180 ret = read_u64(path2, discard_granularity); 181 if (ret) { 182 if (ret != -ENOENT) 183 return ret; 184 else { 185 snprintf(path2, sizeof path2, 186 "%s/../queue/discard_granularity", path1); 187 ret = read_u64(path2, discard_granularity); 188 if (ret) { 189 if (ret != -ENOENT) 190 return ret; 191 else 192 goto not_found; 193 } 194 } 195 } 196 197 snprintf(path2, sizeof path2, "%s/queue/discard_max_bytes", path1); 198 ret = read_u64(path2, discard_max_bytes); 199 if (ret) { 200 if (ret != -ENOENT) 201 return ret; 202 else { 203 snprintf(path2, sizeof path2, 204 "%s/../queue/discard_max_bytes", path1); 205 ret = read_u64(path2, discard_max_bytes); 206 if (ret) { 207 if (ret != -ENOENT) 208 return ret; 209 else 210 goto not_found; 211 } 212 } 213 } 214 215 return 0; 216 217 not_found: 218 /* If we reach here then we didn't find the device. This is 219 * not an error, but set discard_max_bytes = 0 to indicate 220 * that discard is not available. 221 */ 222 *discard_alignment = 0; 223 *discard_granularity = 0; 224 *discard_max_bytes = 0; 225 return 0; 226 } 227 228 #define FSTRIM_BUFSIZ 4096 229 230 /* Trim the filesystem. 231 * 232 * Free blocks between 'start' and 'start+len-1' (both byte offsets) 233 * are found and TRIM requests are sent to the block device. 'minlen' 234 * is the minimum continguous free range to discard. 235 */ 236 static int fstrim(ntfs_volume *vol, void *data, u64 *trimmed) 237 { 238 struct fstrim_range *range = data; 239 u64 start = range->start; 240 u64 len = range->len; 241 u64 minlen = range->minlen; 242 u64 discard_alignment, discard_granularity, discard_max_bytes; 243 u8 *buf = NULL; 244 LCN start_buf; 245 int ret; 246 247 ntfs_log_debug("fstrim: start=%llu len=%llu minlen=%llu\n", 248 (unsigned long long) start, 249 (unsigned long long) len, 250 (unsigned long long) minlen); 251 252 *trimmed = 0; 253 254 /* Fail if user tries to use the fstrim -o/-l/-m options. 255 * XXX We could fix these limitations in future. 256 */ 257 if (start != 0 || len != (uint64_t)-1) { 258 ntfs_log_debug("fstrim: setting start or length is not supported\n"); 259 return -EINVAL; 260 } 261 if (minlen > vol->cluster_size) { 262 ntfs_log_debug("fstrim: minlen > cluster size is not supported\n"); 263 return -EINVAL; 264 } 265 266 /* Only block devices are supported. It would be possible to 267 * support backing files (ie. without using loop) but the 268 * ioctls used to punch holes in files are completely 269 * different. 270 */ 271 if (!NDevBlock(vol->dev)) { 272 ntfs_log_debug("fstrim: not supported for non-block-device\n"); 273 return -EOPNOTSUPP; 274 } 275 276 ret = fstrim_limits(vol, &discard_alignment, 277 &discard_granularity, &discard_max_bytes); 278 if (ret) 279 return ret; 280 if (discard_alignment != 0) { 281 ntfs_log_debug("fstrim: backing device is not aligned for discards\n"); 282 return -EOPNOTSUPP; 283 } 284 if (discard_granularity > vol->cluster_size) { 285 ntfs_log_debug("fstrim: discard granularity of backing device is larger than cluster size\n"); 286 return -EOPNOTSUPP; 287 } 288 if (discard_max_bytes == 0) { 289 ntfs_log_debug("fstrim: backing device does not support discard (discard_max_bytes == 0)\n"); 290 return -EOPNOTSUPP; 291 } 292 293 /* Sync the device before doing anything. */ 294 ret = ntfs_device_sync(vol->dev); 295 if (ret) 296 return ret; 297 298 /* Read through the bitmap. */ 299 buf = ntfs_malloc(FSTRIM_BUFSIZ); 300 if (buf == NULL) 301 return -errno; 302 for (start_buf = 0; start_buf < vol->nr_clusters; 303 start_buf += FSTRIM_BUFSIZ * 8) { 304 s64 count; 305 s64 br; 306 LCN end_buf, start_lcn; 307 308 /* start_buf is LCN of first cluster in the current buffer. 309 * end_buf is LCN of last cluster + 1 in the current buffer. 310 */ 311 end_buf = start_buf + FSTRIM_BUFSIZ*8; 312 if (end_buf > vol->nr_clusters) 313 end_buf = vol->nr_clusters; 314 count = (end_buf - start_buf) / 8; 315 316 br = ntfs_attr_pread(vol->lcnbmp_na, start_buf/8, count, buf); 317 if (br != count) { 318 if (br >= 0) 319 ret = -EIO; 320 else 321 ret = -errno; 322 goto free_out; 323 } 324 325 /* Trim the clusters in large as possible blocks, but 326 * not larger than discard_max_bytes. 327 */ 328 for (start_lcn = start_buf; start_lcn < end_buf; ++start_lcn) { 329 if (!ntfs_bit_get(buf, start_lcn-start_buf)) { 330 LCN end_lcn; 331 332 /* Cluster 'start_lcn' is not in use, 333 * find end of this run. 334 */ 335 end_lcn = start_lcn+1; 336 while (end_lcn < end_buf && 337 (u64) (end_lcn-start_lcn) << vol->cluster_size_bits 338 < discard_max_bytes && 339 !ntfs_bit_get(buf, end_lcn-start_buf)) 340 end_lcn++; 341 342 ret = fstrim_clusters(vol, 343 start_lcn, end_lcn-start_lcn); 344 if (ret) 345 goto free_out; 346 347 *trimmed += (end_lcn - start_lcn) 348 << vol->cluster_size_bits; 349 start_lcn = end_lcn-1; 350 } 351 } 352 } 353 354 ret = 0; 355 free_out: 356 free(buf); 357 return ret; 358 } 359 360 #endif /* FITRIM && BLKDISCARD */ 361 362 int ntfs_ioctl(ntfs_inode *ni, int cmd, void *arg __attribute__((unused)), 363 unsigned int flags __attribute__((unused)), void *data) 364 { 365 int ret = 0; 366 367 switch (cmd) { 368 #if defined(FITRIM) && defined(BLKDISCARD) 369 case FITRIM: 370 if (!ni || !data) 371 ret = -EINVAL; 372 else { 373 u64 trimmed; 374 struct fstrim_range *range = (struct fstrim_range*)data; 375 376 ret = fstrim(ni->vol, data, &trimmed); 377 range->len = trimmed; 378 } 379 break; 380 #else 381 #warning Trimming not supported : FITRIM or BLKDISCARD not defined 382 #endif 383 default : 384 ret = -EINVAL; 385 break; 386 } 387 return (ret); 388 } 389