xref: /haiku/src/add-ons/kernel/file_systems/ntfs/libntfs/ioctl.c (revision 5ac9b506412b11afb993bb52d161efe7666958a5)
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