blob: 7dfb66ac6d380997e985e45e89aa63d69651219b [file] [log] [blame]
Steve Kondik79165c32015-11-09 19:43:00 -08001/**
2 * ioctl.c - Processing of ioctls
3 *
4 * This module is part of ntfs-3g library
5 *
Steve Kondike68cb602016-08-28 00:45:36 -07006 * Copyright (c) 2014-2015 Jean-Pierre Andre
7 * Copyright (c) 2014 Red Hat, Inc.
Steve Kondik79165c32015-11-09 19:43:00 -08008 *
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
53#ifdef HAVE_SETXATTR
54#include <sys/xattr.h>
55#endif
56
57#ifdef HAVE_SYS_TYPES_H
58#include <sys/types.h>
59#endif
60
61#ifdef HAVE_SYS_STAT_H
62#include <sys/stat.h>
63#endif
64
65#ifdef HAVE_LINUX_FS_H
66#include <linux/fs.h>
67#endif
68
69#include "compat.h"
70#include "debug.h"
71#include "bitmap.h"
72#include "attrib.h"
73#include "inode.h"
74#include "layout.h"
75#include "volume.h"
76#include "index.h"
77#include "logging.h"
78#include "ntfstime.h"
79#include "unistr.h"
80#include "dir.h"
81#include "security.h"
82#include "ioctl.h"
83#include "misc.h"
84
85#if defined(FITRIM) && defined(BLKDISCARD)
86
87/* Issue a TRIM request to the underlying device for the given clusters. */
88static int fstrim_clusters(ntfs_volume *vol, LCN lcn, s64 length)
89{
90 struct ntfs_device *dev = vol->dev;
91 uint64_t range[2];
92
93 ntfs_log_debug("fstrim_clusters: %lld length %lld\n",
94 (long long) lcn, (long long) length);
95
96 range[0] = lcn << vol->cluster_size_bits;
97 range[1] = length << vol->cluster_size_bits;
98
99 if (dev->d_ops->ioctl(dev, BLKDISCARD, range) == -1) {
100 ntfs_log_debug("fstrim_one_cluster: ioctl failed: %m\n");
101 return -errno;
102 }
103 return 0;
104}
105
106static int read_line(const char *path, char *line, size_t max_bytes)
107{
108 FILE *fp;
109
110 fp = fopen(path, "r");
111 if (fp == NULL)
112 return -errno;
113 if (fgets(line, max_bytes, fp) == NULL) {
114 int ret = -EIO; /* fgets doesn't set errno */
115 fclose(fp);
116 return ret;
117 }
118 fclose (fp);
119 return 0;
120}
121
122static int read_u64(const char *path, u64 *n)
123{
124 char line[64];
125 int ret;
126
127 ret = read_line(path, line, sizeof line);
128 if (ret)
129 return ret;
130 if (sscanf(line, "%" SCNu64, n) != 1)
131 return -EINVAL;
132 return 0;
133}
134
135/* Find discard limits for current backing device.
136 */
137static int fstrim_limits(ntfs_volume *vol,
138 u64 *discard_alignment,
139 u64 *discard_granularity,
140 u64 *discard_max_bytes)
141{
142 struct stat statbuf;
143 char path1[80], path2[80];
144 int ret;
145
146 /* Stat the backing device. Caller has ensured it is a block device. */
147 if (stat(vol->dev->d_name, &statbuf) == -1) {
148 ntfs_log_debug("fstrim_limits: could not stat %s\n",
149 vol->dev->d_name);
150 return -errno;
151 }
152
153 /* For whole devices,
154 * /sys/dev/block/MAJOR:MINOR/discard_alignment
155 * /sys/dev/block/MAJOR:MINOR/queue/discard_granularity
156 * /sys/dev/block/MAJOR:MINOR/queue/discard_max_bytes
157 * will exist.
158 * For partitions, we also need to check the parent device:
159 * /sys/dev/block/MAJOR:MINOR/../queue/discard_granularity
160 * /sys/dev/block/MAJOR:MINOR/../queue/discard_max_bytes
161 */
162 snprintf(path1, sizeof path1, "/sys/dev/block/%d:%d",
163 major(statbuf.st_rdev), minor(statbuf.st_rdev));
164
165 snprintf(path2, sizeof path2, "%s/discard_alignment", path1);
166 ret = read_u64(path2, discard_alignment);
167 if (ret) {
168 if (ret != -ENOENT)
169 return ret;
170 else
171 /* We would expect this file to exist on all
172 * modern kernels. But for the sake of very
173 * old kernels:
174 */
175 goto not_found;
176 }
177
178 snprintf(path2, sizeof path2, "%s/queue/discard_granularity", path1);
179 ret = read_u64(path2, discard_granularity);
180 if (ret) {
181 if (ret != -ENOENT)
182 return ret;
183 else {
184 snprintf(path2, sizeof path2,
185 "%s/../queue/discard_granularity", path1);
186 ret = read_u64(path2, discard_granularity);
187 if (ret) {
188 if (ret != -ENOENT)
189 return ret;
190 else
191 goto not_found;
192 }
193 }
194 }
195
196 snprintf(path2, sizeof path2, "%s/queue/discard_max_bytes", path1);
197 ret = read_u64(path2, discard_max_bytes);
198 if (ret) {
199 if (ret != -ENOENT)
200 return ret;
201 else {
202 snprintf(path2, sizeof path2,
203 "%s/../queue/discard_max_bytes", path1);
204 ret = read_u64(path2, discard_max_bytes);
205 if (ret) {
206 if (ret != -ENOENT)
207 return ret;
208 else
209 goto not_found;
210 }
211 }
212 }
213
214 return 0;
215
216not_found:
217 /* If we reach here then we didn't find the device. This is
218 * not an error, but set discard_max_bytes = 0 to indicate
219 * that discard is not available.
220 */
221 *discard_alignment = 0;
222 *discard_granularity = 0;
223 *discard_max_bytes = 0;
224 return 0;
225}
226
227#define FSTRIM_BUFSIZ 4096
228
229/* Trim the filesystem.
230 *
231 * Free blocks between 'start' and 'start+len-1' (both byte offsets)
232 * are found and TRIM requests are sent to the block device. 'minlen'
233 * is the minimum continguous free range to discard.
234 */
Steve Kondike68cb602016-08-28 00:45:36 -0700235static int fstrim(ntfs_volume *vol, void *data, u64 *trimmed)
Steve Kondik79165c32015-11-09 19:43:00 -0800236{
237 struct fstrim_range *range = data;
238 u64 start = range->start;
239 u64 len = range->len;
240 u64 minlen = range->minlen;
241 u64 discard_alignment, discard_granularity, discard_max_bytes;
242 u8 *buf = NULL;
243 LCN start_buf;
244 int ret;
245
246 ntfs_log_debug("fstrim: start=%llu len=%llu minlen=%llu\n",
247 (unsigned long long) start,
248 (unsigned long long) len,
249 (unsigned long long) minlen);
250
Steve Kondike68cb602016-08-28 00:45:36 -0700251 *trimmed = 0;
252
Steve Kondik79165c32015-11-09 19:43:00 -0800253 /* Fail if user tries to use the fstrim -o/-l/-m options.
254 * XXX We could fix these limitations in future.
255 */
256 if (start != 0 || len != (uint64_t)-1) {
257 ntfs_log_debug("fstrim: setting start or length is not supported\n");
258 return -EINVAL;
259 }
260 if (minlen > vol->cluster_size) {
261 ntfs_log_debug("fstrim: minlen > cluster size is not supported\n");
262 return -EINVAL;
263 }
264
265 /* Only block devices are supported. It would be possible to
266 * support backing files (ie. without using loop) but the
267 * ioctls used to punch holes in files are completely
268 * different.
269 */
270 if (!NDevBlock(vol->dev)) {
271 ntfs_log_debug("fstrim: not supported for non-block-device\n");
272 return -EOPNOTSUPP;
273 }
274
275 ret = fstrim_limits(vol, &discard_alignment,
276 &discard_granularity, &discard_max_bytes);
277 if (ret)
278 return ret;
279 if (discard_alignment != 0) {
280 ntfs_log_debug("fstrim: backing device is not aligned for discards\n");
281 return -EOPNOTSUPP;
282 }
283 if (discard_granularity > vol->cluster_size) {
284 ntfs_log_debug("fstrim: discard granularity of backing device is larger than cluster size\n");
285 return -EOPNOTSUPP;
286 }
287 if (discard_max_bytes == 0) {
288 ntfs_log_debug("fstrim: backing device does not support discard (discard_max_bytes == 0)\n");
289 return -EOPNOTSUPP;
290 }
291
292 /* Sync the device before doing anything. */
293 ret = ntfs_device_sync(vol->dev);
294 if (ret)
295 return ret;
296
297 /* Read through the bitmap. */
298 buf = ntfs_malloc(FSTRIM_BUFSIZ);
299 if (buf == NULL)
300 return -errno;
301 for (start_buf = 0; start_buf < vol->nr_clusters;
302 start_buf += FSTRIM_BUFSIZ * 8) {
303 s64 count;
304 s64 br;
305 LCN end_buf, start_lcn;
306
307 /* start_buf is LCN of first cluster in the current buffer.
308 * end_buf is LCN of last cluster + 1 in the current buffer.
309 */
310 end_buf = start_buf + FSTRIM_BUFSIZ*8;
311 if (end_buf > vol->nr_clusters)
312 end_buf = vol->nr_clusters;
313 count = (end_buf - start_buf) / 8;
314
315 br = ntfs_attr_pread(vol->lcnbmp_na, start_buf/8, count, buf);
316 if (br != count) {
317 if (br >= 0)
318 ret = -EIO;
319 else
320 ret = -errno;
321 goto free_out;
322 }
323
324 /* Trim the clusters in large as possible blocks, but
325 * not larger than discard_max_bytes.
326 */
327 for (start_lcn = start_buf; start_lcn < end_buf; ++start_lcn) {
328 if (!ntfs_bit_get(buf, start_lcn-start_buf)) {
329 LCN end_lcn;
330
331 /* Cluster 'start_lcn' is not in use,
332 * find end of this run.
333 */
334 end_lcn = start_lcn+1;
335 while (end_lcn < end_buf &&
336 (u64) (end_lcn-start_lcn) << vol->cluster_size_bits
337 < discard_max_bytes &&
338 !ntfs_bit_get(buf, end_lcn-start_buf))
339 end_lcn++;
340
341 ret = fstrim_clusters(vol,
342 start_lcn, end_lcn-start_lcn);
343 if (ret)
344 goto free_out;
345
Steve Kondike68cb602016-08-28 00:45:36 -0700346 *trimmed += (end_lcn - start_lcn)
347 << vol->cluster_size_bits;
Steve Kondik79165c32015-11-09 19:43:00 -0800348 start_lcn = end_lcn-1;
349 }
350 }
351 }
352
353 ret = 0;
354free_out:
355 free(buf);
356 return ret;
357}
358
359#endif /* FITRIM && BLKDISCARD */
360
Steve Kondikeb6631b2016-08-28 01:00:02 -0700361int ntfs_ioctl(ntfs_inode *ni, unsigned int cmd, void *arg __attribute__((unused)),
Steve Kondik79165c32015-11-09 19:43:00 -0800362 unsigned int flags __attribute__((unused)), void *data)
363{
364 int ret = 0;
365
366 switch (cmd) {
367#if defined(FITRIM) && defined(BLKDISCARD)
368 case FITRIM:
369 if (!ni || !data)
370 ret = -EINVAL;
Steve Kondike68cb602016-08-28 00:45:36 -0700371 else {
372 u64 trimmed;
373 struct fstrim_range *range = (struct fstrim_range*)data;
374
375 ret = fstrim(ni->vol, data, &trimmed);
376 range->len = trimmed;
377 }
Steve Kondik79165c32015-11-09 19:43:00 -0800378 break;
379#else
Steve Kondike68cb602016-08-28 00:45:36 -0700380#warning Trimming not supported : FITRIM or BLKDISCARD not defined
Steve Kondik79165c32015-11-09 19:43:00 -0800381#endif
382 default :
383 ret = -EINVAL;
384 break;
385 }
386 return (ret);
387}