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. */
89static 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
107static 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
123static 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 */
138static 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
218not_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
229static 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
238static 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 */
255static 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;
385free_out:
386	free(buf);
387	return ret;
388}
389
390#endif /* FITRIM && BLKDISCARD */
391
392int 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