xref: /freebsd/usr.sbin/trim/trim.c (revision 9d433cb875e9540b072d3fb200f32b364e18cf40)
12b5691eeSEugene Grosbein /*-
22b5691eeSEugene Grosbein  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
32b5691eeSEugene Grosbein  *
42b5691eeSEugene Grosbein  * Copyright (c) 2019 Eugene Grosbein <eugen@FreeBSD.org>.
5fa707ac7SEugene Grosbein  * Contains code written by Alan Somers <asomers@FreeBSD.org>.
62b5691eeSEugene Grosbein  *
72b5691eeSEugene Grosbein  * Redistribution and use in source and binary forms, with or without
82b5691eeSEugene Grosbein  * modification, are permitted provided that the following conditions
92b5691eeSEugene Grosbein  * are met:
102b5691eeSEugene Grosbein  * 1. Redistributions of source code must retain the above copyright
112b5691eeSEugene Grosbein  *    notice, this list of conditions and the following disclaimer.
122b5691eeSEugene Grosbein  * 2. Redistributions in binary form must reproduce the above copyright
132b5691eeSEugene Grosbein  *    notice, this list of conditions and the following disclaimer in the
142b5691eeSEugene Grosbein  *    documentation and/or other materials provided with the distribution.
152b5691eeSEugene Grosbein  *
162b5691eeSEugene Grosbein  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
172b5691eeSEugene Grosbein  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
182b5691eeSEugene Grosbein  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
192b5691eeSEugene Grosbein  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
202b5691eeSEugene Grosbein  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
212b5691eeSEugene Grosbein  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
222b5691eeSEugene Grosbein  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
232b5691eeSEugene Grosbein  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
242b5691eeSEugene Grosbein  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
252b5691eeSEugene Grosbein  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
262b5691eeSEugene Grosbein  * SUCH DAMAGE.
272b5691eeSEugene Grosbein  *
282b5691eeSEugene Grosbein  */
292b5691eeSEugene Grosbein 
302b5691eeSEugene Grosbein #include <sys/disk.h>
312b5691eeSEugene Grosbein #include <sys/ioctl.h>
322b5691eeSEugene Grosbein #include <sys/stat.h>
332b5691eeSEugene Grosbein 
342b5691eeSEugene Grosbein #include <err.h>
352b5691eeSEugene Grosbein #include <errno.h>
362b5691eeSEugene Grosbein #include <fcntl.h>
372b5691eeSEugene Grosbein #include <libutil.h>
382b5691eeSEugene Grosbein #include <limits.h>
392b5691eeSEugene Grosbein #include <paths.h>
402b5691eeSEugene Grosbein #include <stdbool.h>
412b5691eeSEugene Grosbein #include <stdio.h>
422b5691eeSEugene Grosbein #include <stdlib.h>
437f654915SEugene Grosbein #include <string.h>
442b5691eeSEugene Grosbein #include <sysexits.h>
452b5691eeSEugene Grosbein #include <unistd.h>
462b5691eeSEugene Grosbein 
472b5691eeSEugene Grosbein #include <sys/cdefs.h>
482b5691eeSEugene Grosbein __FBSDID("$FreeBSD$");
492b5691eeSEugene Grosbein 
50fa707ac7SEugene Grosbein static bool	candelete(int fd);
512b5691eeSEugene Grosbein static off_t	getsize(const char *path);
522b5691eeSEugene Grosbein static int	opendev(const char *path, int flags);
532b5691eeSEugene Grosbein static int	trim(const char *path, off_t offset, off_t length, bool dryrun, bool verbose);
542b5691eeSEugene Grosbein static void	usage(const char *name);
552b5691eeSEugene Grosbein 
562b5691eeSEugene Grosbein int
572b5691eeSEugene Grosbein main(int argc, char **argv)
582b5691eeSEugene Grosbein {
592b5691eeSEugene Grosbein 	off_t offset, length;
602b5691eeSEugene Grosbein 	uint64_t usz;
612b5691eeSEugene Grosbein 	int ch, error;
622b5691eeSEugene Grosbein 	bool dryrun, verbose;
632b5691eeSEugene Grosbein 	char *fname, *name;
642b5691eeSEugene Grosbein 
652b5691eeSEugene Grosbein 	error = 0;
662b5691eeSEugene Grosbein 	length = offset = 0;
672b5691eeSEugene Grosbein 	name = argv[0];
682b5691eeSEugene Grosbein 	dryrun = verbose = true;
692b5691eeSEugene Grosbein 
702b5691eeSEugene Grosbein 	while ((ch = getopt(argc, argv, "Nfl:o:qr:v")) != -1)
712b5691eeSEugene Grosbein 		switch (ch) {
722b5691eeSEugene Grosbein 		case 'N':
732b5691eeSEugene Grosbein 			dryrun = true;
742b5691eeSEugene Grosbein 			verbose = true;
752b5691eeSEugene Grosbein 			break;
762b5691eeSEugene Grosbein 		case 'f':
772b5691eeSEugene Grosbein 			dryrun = false;
782b5691eeSEugene Grosbein 			break;
792b5691eeSEugene Grosbein 		case 'l':
802b5691eeSEugene Grosbein 		case 'o':
812b5691eeSEugene Grosbein 			if (expand_number(optarg, &usz) == -1 ||
822b5691eeSEugene Grosbein 					(off_t)usz < 0 || (usz == 0 && ch == 'l'))
832b5691eeSEugene Grosbein 				errx(EX_USAGE,
842b5691eeSEugene Grosbein 					"invalid %s of the region: %s",
852b5691eeSEugene Grosbein 					ch == 'o' ? "offset" : "length",
862b5691eeSEugene Grosbein 					optarg);
872b5691eeSEugene Grosbein 			if (ch == 'o')
882b5691eeSEugene Grosbein 				offset = (off_t)usz;
892b5691eeSEugene Grosbein 			else
902b5691eeSEugene Grosbein 				length = (off_t)usz;
912b5691eeSEugene Grosbein 			break;
922b5691eeSEugene Grosbein 		case 'q':
932b5691eeSEugene Grosbein 			verbose = false;
942b5691eeSEugene Grosbein 			break;
952b5691eeSEugene Grosbein 		case 'r':
962b5691eeSEugene Grosbein 			if ((length = getsize(optarg)) == 0)
972b5691eeSEugene Grosbein 				errx(EX_USAGE,
982b5691eeSEugene Grosbein 					"invalid zero length reference file"
992b5691eeSEugene Grosbein 					" for the region: %s", optarg);
1002b5691eeSEugene Grosbein 			break;
1012b5691eeSEugene Grosbein 		case 'v':
1022b5691eeSEugene Grosbein 			verbose = true;
1032b5691eeSEugene Grosbein 			break;
1042b5691eeSEugene Grosbein 		default:
1052b5691eeSEugene Grosbein 			usage(name);
1062b5691eeSEugene Grosbein 			/* NOTREACHED */
1072b5691eeSEugene Grosbein 		}
1082b5691eeSEugene Grosbein 
1097f654915SEugene Grosbein 	/*
1107f654915SEugene Grosbein 	 * Safety net: do not allow mistakes like
1117f654915SEugene Grosbein 	 *
1127f654915SEugene Grosbein 	 *	trim -f /dev/da0 -r rfile
1137f654915SEugene Grosbein 	 *
1147f654915SEugene Grosbein 	 * This would trim whole device then error on non-existing file -r.
1157f654915SEugene Grosbein 	 * Following check prevents this while allowing this form still:
1167f654915SEugene Grosbein 	 *
1177f654915SEugene Grosbein 	 *	trim -f -- /dev/da0 -r rfile
1187f654915SEugene Grosbein 	 */
1197f654915SEugene Grosbein 
1207f654915SEugene Grosbein 	if (strcmp(argv[optind-1], "--") != 0) {
1217f654915SEugene Grosbein 		for (ch = optind; ch < argc; ch++)
1227f654915SEugene Grosbein 			if (argv[ch][0] == '-')
1237f654915SEugene Grosbein 				usage(name);
1247f654915SEugene Grosbein 	}
1257f654915SEugene Grosbein 
1262b5691eeSEugene Grosbein 	argv += optind;
1272b5691eeSEugene Grosbein 	argc -= optind;
1282b5691eeSEugene Grosbein 
1292b5691eeSEugene Grosbein 	if (argc < 1)
1302b5691eeSEugene Grosbein 		usage(name);
1312b5691eeSEugene Grosbein 
1322b5691eeSEugene Grosbein 	while ((fname = *argv++) != NULL)
1332b5691eeSEugene Grosbein 		if (trim(fname, offset, length, dryrun, verbose) < 0)
1342b5691eeSEugene Grosbein 			error++;
1352b5691eeSEugene Grosbein 
1362b5691eeSEugene Grosbein 	return (error ? EXIT_FAILURE : EXIT_SUCCESS);
1372b5691eeSEugene Grosbein }
1382b5691eeSEugene Grosbein 
139fa707ac7SEugene Grosbein static bool
140fa707ac7SEugene Grosbein candelete(int fd)
141fa707ac7SEugene Grosbein {
142fa707ac7SEugene Grosbein 	struct diocgattr_arg arg;
143fa707ac7SEugene Grosbein 
144fa707ac7SEugene Grosbein 	strlcpy(arg.name, "GEOM::candelete", sizeof(arg.name));
145fa707ac7SEugene Grosbein 	arg.len = sizeof(arg.value.i);
146fa707ac7SEugene Grosbein 	if (ioctl(fd, DIOCGATTR, &arg) == 0)
147fa707ac7SEugene Grosbein 		return (arg.value.i != 0);
148fa707ac7SEugene Grosbein 	else
149fa707ac7SEugene Grosbein 		return (false);
150fa707ac7SEugene Grosbein }
151fa707ac7SEugene Grosbein 
1522b5691eeSEugene Grosbein static int
1532b5691eeSEugene Grosbein opendev(const char *path, int flags)
1542b5691eeSEugene Grosbein {
1552b5691eeSEugene Grosbein 	int fd;
1562b5691eeSEugene Grosbein 	char *tstr;
1572b5691eeSEugene Grosbein 
1582b5691eeSEugene Grosbein 	if ((fd = open(path, flags)) < 0) {
1592b5691eeSEugene Grosbein 		if (errno == ENOENT && path[0] != '/') {
1602b5691eeSEugene Grosbein 			if (asprintf(&tstr, "%s%s", _PATH_DEV, path) < 0)
1612b5691eeSEugene Grosbein 				errx(EX_OSERR, "no memory");
1622b5691eeSEugene Grosbein 			fd = open(tstr, flags);
1632b5691eeSEugene Grosbein 			free(tstr);
1642b5691eeSEugene Grosbein 		}
1652b5691eeSEugene Grosbein 	}
1662b5691eeSEugene Grosbein 
1672b5691eeSEugene Grosbein 	if (fd < 0)
1682b5691eeSEugene Grosbein 		err(EX_NOINPUT, "open failed: %s", path);
1692b5691eeSEugene Grosbein 
1702b5691eeSEugene Grosbein 	return (fd);
1712b5691eeSEugene Grosbein }
1722b5691eeSEugene Grosbein 
1732b5691eeSEugene Grosbein static off_t
1742b5691eeSEugene Grosbein getsize(const char *path)
1752b5691eeSEugene Grosbein {
1762b5691eeSEugene Grosbein 	struct stat sb;
1772b5691eeSEugene Grosbein 	off_t mediasize;
1782b5691eeSEugene Grosbein 	int fd;
1792b5691eeSEugene Grosbein 
1802b5691eeSEugene Grosbein 	fd = opendev(path, O_RDONLY | O_DIRECT);
1812b5691eeSEugene Grosbein 
1822b5691eeSEugene Grosbein 	if (fstat(fd, &sb) < 0)
1832b5691eeSEugene Grosbein 		err(EX_IOERR, "fstat failed: %s", path);
1842b5691eeSEugene Grosbein 
1852b5691eeSEugene Grosbein 	if (S_ISREG(sb.st_mode) || S_ISDIR(sb.st_mode)) {
1862b5691eeSEugene Grosbein 		close(fd);
1872b5691eeSEugene Grosbein 		return (sb.st_size);
1882b5691eeSEugene Grosbein 	}
1892b5691eeSEugene Grosbein 
1902b5691eeSEugene Grosbein 	if (!S_ISCHR(sb.st_mode) && !S_ISBLK(sb.st_mode))
1912b5691eeSEugene Grosbein 		errx(EX_DATAERR,
1922b5691eeSEugene Grosbein 			"invalid type of the file "
1932b5691eeSEugene Grosbein 			"(not regular, directory nor special device): %s",
1942b5691eeSEugene Grosbein 			path);
1952b5691eeSEugene Grosbein 
1962b5691eeSEugene Grosbein 	if (ioctl(fd, DIOCGMEDIASIZE, &mediasize) < 0)
1972b5691eeSEugene Grosbein 		err(EX_UNAVAILABLE,
1982b5691eeSEugene Grosbein 			"ioctl(DIOCGMEDIASIZE) failed, probably not a disk: "
1992b5691eeSEugene Grosbein 			"%s", path);
2002b5691eeSEugene Grosbein 
2012b5691eeSEugene Grosbein 	close(fd);
2022b5691eeSEugene Grosbein 	return (mediasize);
2032b5691eeSEugene Grosbein }
2042b5691eeSEugene Grosbein 
2052b5691eeSEugene Grosbein static int
2062b5691eeSEugene Grosbein trim(const char *path, off_t offset, off_t length, bool dryrun, bool verbose)
2072b5691eeSEugene Grosbein {
2082b5691eeSEugene Grosbein 	off_t arg[2];
2092b5691eeSEugene Grosbein 	int error, fd;
2102b5691eeSEugene Grosbein 
2112b5691eeSEugene Grosbein 	if (length == 0)
2122b5691eeSEugene Grosbein 		length = getsize(path);
2132b5691eeSEugene Grosbein 
2142b5691eeSEugene Grosbein 	if (verbose)
2152b5691eeSEugene Grosbein 		printf("trim %s offset %ju length %ju\n",
2162b5691eeSEugene Grosbein 		    path, (uintmax_t)offset, (uintmax_t)length);
2172b5691eeSEugene Grosbein 
2182b5691eeSEugene Grosbein 	if (dryrun) {
2192b5691eeSEugene Grosbein 		printf("dry run: add -f to actually perform the operation\n");
2202b5691eeSEugene Grosbein 		return (0);
2212b5691eeSEugene Grosbein 	}
2222b5691eeSEugene Grosbein 
223*9d433cb8SAllan Jude 	fd = opendev(path, O_RDWR | O_DIRECT);
2242b5691eeSEugene Grosbein 	arg[0] = offset;
2252b5691eeSEugene Grosbein 	arg[1] = length;
2262b5691eeSEugene Grosbein 
2272b5691eeSEugene Grosbein 	error = ioctl(fd, DIOCGDELETE, arg);
228fa707ac7SEugene Grosbein 	if (error < 0) {
229fa707ac7SEugene Grosbein 		if (errno == EOPNOTSUPP && verbose && !candelete(fd))
230fa707ac7SEugene Grosbein 			warnx("%s: TRIM/UNMAP not supported by driver", path);
231fa707ac7SEugene Grosbein 		else
2322b5691eeSEugene Grosbein 			warn("ioctl(DIOCGDELETE) failed: %s", path);
233fa707ac7SEugene Grosbein 	}
2342b5691eeSEugene Grosbein 	close(fd);
2352b5691eeSEugene Grosbein 	return (error);
2362b5691eeSEugene Grosbein }
2372b5691eeSEugene Grosbein 
2382b5691eeSEugene Grosbein static void
2392b5691eeSEugene Grosbein usage(const char *name)
2402b5691eeSEugene Grosbein {
2412b5691eeSEugene Grosbein 	(void)fprintf(stderr,
2422b5691eeSEugene Grosbein 	    "usage: %s [-[lo] offset[K|k|M|m|G|g|T|t]] [-r rfile] [-Nfqv] device ...\n",
2432b5691eeSEugene Grosbein 	    name);
2442b5691eeSEugene Grosbein 	exit(EX_USAGE);
2452b5691eeSEugene Grosbein }
246