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. */
fstrim_clusters(ntfs_volume * vol,LCN lcn,s64 length)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
read_line(const char * path,char * line,size_t max_bytes)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
read_u64(const char * path,u64 * n)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 */
fstrim_limits(ntfs_volume * vol,u64 * discard_alignment,u64 * discard_granularity,u64 * discard_max_bytes)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
align_up(ntfs_volume * vol,LCN lcn,u64 granularity)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
align_down(ntfs_volume * vol,u64 count,u64 granularity)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 */
fstrim(ntfs_volume * vol,void * data,u64 * trimmed)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
ntfs_ioctl(ntfs_inode * ni,unsigned long cmd,void * arg,unsigned int flags,void * data)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