1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2019 Eugene Grosbein <eugen@FreeBSD.org>.
5 * Contains code written by Alan Somers <asomers@FreeBSD.org>.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 *
28 */
29
30#include <sys/disk.h>
31#include <sys/ioctl.h>
32#include <sys/stat.h>
33
34#include <err.h>
35#include <errno.h>
36#include <fcntl.h>
37#include <libutil.h>
38#include <limits.h>
39#include <paths.h>
40#include <stdbool.h>
41#include <stdio.h>
42#include <stdlib.h>
43#include <string.h>
44#include <sysexits.h>
45#include <unistd.h>
46
47#include <sys/cdefs.h>
48static bool	candelete(int fd);
49static off_t	getsize(const char *path);
50static int	opendev(const char *path, int flags);
51static int	trim(const char *path, off_t offset, off_t length, bool dryrun, bool verbose);
52static void	usage(const char *name);
53
54int
55main(int argc, char **argv)
56{
57	off_t offset, length;
58	uint64_t usz;
59	int ch, error;
60	bool dryrun, verbose;
61	char *fname, *name;
62
63	error = 0;
64	length = offset = 0;
65	name = argv[0];
66	dryrun = verbose = true;
67
68	while ((ch = getopt(argc, argv, "Nfl:o:qr:v")) != -1)
69		switch (ch) {
70		case 'N':
71			dryrun = true;
72			verbose = true;
73			break;
74		case 'f':
75			dryrun = false;
76			break;
77		case 'l':
78		case 'o':
79			if (expand_number(optarg, &usz) == -1 ||
80					(off_t)usz < 0 || (usz == 0 && ch == 'l'))
81				errx(EX_USAGE,
82					"invalid %s of the region: %s",
83					ch == 'o' ? "offset" : "length",
84					optarg);
85			if (ch == 'o')
86				offset = (off_t)usz;
87			else
88				length = (off_t)usz;
89			break;
90		case 'q':
91			verbose = false;
92			break;
93		case 'r':
94			if ((length = getsize(optarg)) == 0)
95				errx(EX_USAGE,
96					"invalid zero length reference file"
97					" for the region: %s", optarg);
98			break;
99		case 'v':
100			verbose = true;
101			break;
102		default:
103			usage(name);
104			/* NOTREACHED */
105		}
106
107	/*
108	 * Safety net: do not allow mistakes like
109	 *
110	 *	trim -f /dev/da0 -r rfile
111	 *
112	 * This would trim whole device then error on non-existing file -r.
113	 * Following check prevents this while allowing this form still:
114	 *
115	 *	trim -f -- /dev/da0 -r rfile
116	 */
117
118	if (strcmp(argv[optind-1], "--") != 0) {
119		for (ch = optind; ch < argc; ch++)
120			if (argv[ch][0] == '-')
121				usage(name);
122	}
123
124	argv += optind;
125	argc -= optind;
126
127	if (argc < 1)
128		usage(name);
129
130	while ((fname = *argv++) != NULL)
131		if (trim(fname, offset, length, dryrun, verbose) < 0)
132			error++;
133
134	return (error ? EXIT_FAILURE : EXIT_SUCCESS);
135}
136
137static bool
138candelete(int fd)
139{
140	struct diocgattr_arg arg;
141
142	strlcpy(arg.name, "GEOM::candelete", sizeof(arg.name));
143	arg.len = sizeof(arg.value.i);
144	if (ioctl(fd, DIOCGATTR, &arg) == 0)
145		return (arg.value.i != 0);
146	else
147		return (false);
148}
149
150static int
151opendev(const char *path, int flags)
152{
153	int fd;
154	char *tstr;
155
156	if ((fd = open(path, flags)) < 0) {
157		if (errno == ENOENT && path[0] != '/') {
158			if (asprintf(&tstr, "%s%s", _PATH_DEV, path) < 0)
159				errx(EX_OSERR, "no memory");
160			fd = open(tstr, flags);
161			free(tstr);
162		}
163	}
164
165	if (fd < 0)
166		err(EX_NOINPUT, "open failed: %s", path);
167
168	return (fd);
169}
170
171static off_t
172getsize(const char *path)
173{
174	struct stat sb;
175	off_t mediasize;
176	int fd;
177
178	fd = opendev(path, O_RDONLY | O_DIRECT);
179
180	if (fstat(fd, &sb) < 0)
181		err(EX_IOERR, "fstat failed: %s", path);
182
183	if (S_ISREG(sb.st_mode) || S_ISDIR(sb.st_mode)) {
184		close(fd);
185		return (sb.st_size);
186	}
187
188	if (!S_ISCHR(sb.st_mode) && !S_ISBLK(sb.st_mode))
189		errx(EX_DATAERR,
190			"invalid type of the file "
191			"(not regular, directory nor special device): %s",
192			path);
193
194	if (ioctl(fd, DIOCGMEDIASIZE, &mediasize) < 0)
195		err(EX_UNAVAILABLE,
196			"ioctl(DIOCGMEDIASIZE) failed, probably not a disk: "
197			"%s", path);
198
199	close(fd);
200	return (mediasize);
201}
202
203static int
204trim(const char *path, off_t offset, off_t length, bool dryrun, bool verbose)
205{
206	off_t arg[2];
207	int error, fd;
208
209	if (length == 0)
210		length = getsize(path);
211
212	if (verbose)
213		printf("trim %s offset %ju length %ju\n",
214		    path, (uintmax_t)offset, (uintmax_t)length);
215
216	if (dryrun) {
217		printf("dry run: add -f to actually perform the operation\n");
218		return (0);
219	}
220
221	fd = opendev(path, O_RDWR | O_DIRECT);
222	arg[0] = offset;
223	arg[1] = length;
224
225	error = ioctl(fd, DIOCGDELETE, arg);
226	if (error < 0) {
227		if (errno == EOPNOTSUPP && verbose && !candelete(fd))
228			warnx("%s: TRIM/UNMAP not supported by driver", path);
229		else
230			warn("ioctl(DIOCGDELETE) failed: %s", path);
231	}
232	close(fd);
233	return (error);
234}
235
236static void
237usage(const char *name)
238{
239	(void)fprintf(stderr,
240	    "usage: %s [-[lo] offset[K|k|M|m|G|g|T|t]] [-r rfile] [-Nfqv] device ...\n",
241	    name);
242	exit(EX_USAGE);
243}
244