xref: /freebsd/usr.sbin/trim/trim.c (revision 7f654915764168b33235ccec82f76c06ed1e466c)
12b5691eeSEugene Grosbein /*-
22b5691eeSEugene Grosbein  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
32b5691eeSEugene Grosbein  *
42b5691eeSEugene Grosbein  * Copyright (c) 2019 Eugene Grosbein <eugen@FreeBSD.org>.
52b5691eeSEugene Grosbein  * All rights reserved.
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>
43*7f654915SEugene 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 
502b5691eeSEugene Grosbein static off_t	getsize(const char *path);
512b5691eeSEugene Grosbein static int	opendev(const char *path, int flags);
522b5691eeSEugene Grosbein static int	trim(const char *path, off_t offset, off_t length, bool dryrun, bool verbose);
532b5691eeSEugene Grosbein static void	usage(const char *name);
542b5691eeSEugene Grosbein 
552b5691eeSEugene Grosbein int
562b5691eeSEugene Grosbein main(int argc, char **argv)
572b5691eeSEugene Grosbein {
582b5691eeSEugene Grosbein 	off_t offset, length;
592b5691eeSEugene Grosbein 	uint64_t usz;
602b5691eeSEugene Grosbein 	int ch, error;
612b5691eeSEugene Grosbein 	bool dryrun, verbose;
622b5691eeSEugene Grosbein 	char *fname, *name;
632b5691eeSEugene Grosbein 
642b5691eeSEugene Grosbein 	error = 0;
652b5691eeSEugene Grosbein 	length = offset = 0;
662b5691eeSEugene Grosbein 	name = argv[0];
672b5691eeSEugene Grosbein 	dryrun = verbose = true;
682b5691eeSEugene Grosbein 
692b5691eeSEugene Grosbein 	while ((ch = getopt(argc, argv, "Nfl:o:qr:v")) != -1)
702b5691eeSEugene Grosbein 		switch (ch) {
712b5691eeSEugene Grosbein 		case 'N':
722b5691eeSEugene Grosbein 			dryrun = true;
732b5691eeSEugene Grosbein 			verbose = true;
742b5691eeSEugene Grosbein 			break;
752b5691eeSEugene Grosbein 		case 'f':
762b5691eeSEugene Grosbein 			dryrun = false;
772b5691eeSEugene Grosbein 			break;
782b5691eeSEugene Grosbein 		case 'l':
792b5691eeSEugene Grosbein 		case 'o':
802b5691eeSEugene Grosbein 			if (expand_number(optarg, &usz) == -1 ||
812b5691eeSEugene Grosbein 					(off_t)usz < 0 || (usz == 0 && ch == 'l'))
822b5691eeSEugene Grosbein 				errx(EX_USAGE,
832b5691eeSEugene Grosbein 					"invalid %s of the region: %s",
842b5691eeSEugene Grosbein 					ch == 'o' ? "offset" : "length",
852b5691eeSEugene Grosbein 					optarg);
862b5691eeSEugene Grosbein 			if (ch == 'o')
872b5691eeSEugene Grosbein 				offset = (off_t)usz;
882b5691eeSEugene Grosbein 			else
892b5691eeSEugene Grosbein 				length = (off_t)usz;
902b5691eeSEugene Grosbein 			break;
912b5691eeSEugene Grosbein 		case 'q':
922b5691eeSEugene Grosbein 			verbose = false;
932b5691eeSEugene Grosbein 			break;
942b5691eeSEugene Grosbein 		case 'r':
952b5691eeSEugene Grosbein 			if ((length = getsize(optarg)) == 0)
962b5691eeSEugene Grosbein 				errx(EX_USAGE,
972b5691eeSEugene Grosbein 					"invalid zero length reference file"
982b5691eeSEugene Grosbein 					" for the region: %s", optarg);
992b5691eeSEugene Grosbein 			break;
1002b5691eeSEugene Grosbein 		case 'v':
1012b5691eeSEugene Grosbein 			verbose = true;
1022b5691eeSEugene Grosbein 			break;
1032b5691eeSEugene Grosbein 		default:
1042b5691eeSEugene Grosbein 			usage(name);
1052b5691eeSEugene Grosbein 			/* NOTREACHED */
1062b5691eeSEugene Grosbein 		}
1072b5691eeSEugene Grosbein 
108*7f654915SEugene Grosbein 	/*
109*7f654915SEugene Grosbein 	 * Safety net: do not allow mistakes like
110*7f654915SEugene Grosbein 	 *
111*7f654915SEugene Grosbein 	 *	trim -f /dev/da0 -r rfile
112*7f654915SEugene Grosbein 	 *
113*7f654915SEugene Grosbein 	 * This would trim whole device then error on non-existing file -r.
114*7f654915SEugene Grosbein 	 * Following check prevents this while allowing this form still:
115*7f654915SEugene Grosbein 	 *
116*7f654915SEugene Grosbein 	 *	trim -f -- /dev/da0 -r rfile
117*7f654915SEugene Grosbein 	 */
118*7f654915SEugene Grosbein 
119*7f654915SEugene Grosbein 	if (strcmp(argv[optind-1], "--") != 0) {
120*7f654915SEugene Grosbein 		for (ch = optind; ch < argc; ch++)
121*7f654915SEugene Grosbein 			if (argv[ch][0] == '-')
122*7f654915SEugene Grosbein 				usage(name);
123*7f654915SEugene Grosbein 	}
124*7f654915SEugene Grosbein 
1252b5691eeSEugene Grosbein 	argv += optind;
1262b5691eeSEugene Grosbein 	argc -= optind;
1272b5691eeSEugene Grosbein 
1282b5691eeSEugene Grosbein 	if (argc < 1)
1292b5691eeSEugene Grosbein 		usage(name);
1302b5691eeSEugene Grosbein 
1312b5691eeSEugene Grosbein 	while ((fname = *argv++) != NULL)
1322b5691eeSEugene Grosbein 		if (trim(fname, offset, length, dryrun, verbose) < 0)
1332b5691eeSEugene Grosbein 			error++;
1342b5691eeSEugene Grosbein 
1352b5691eeSEugene Grosbein 	return (error ? EXIT_FAILURE : EXIT_SUCCESS);
1362b5691eeSEugene Grosbein }
1372b5691eeSEugene Grosbein 
1382b5691eeSEugene Grosbein static int
1392b5691eeSEugene Grosbein opendev(const char *path, int flags)
1402b5691eeSEugene Grosbein {
1412b5691eeSEugene Grosbein 	int fd;
1422b5691eeSEugene Grosbein 	char *tstr;
1432b5691eeSEugene Grosbein 
1442b5691eeSEugene Grosbein 	if ((fd = open(path, flags)) < 0) {
1452b5691eeSEugene Grosbein 		if (errno == ENOENT && path[0] != '/') {
1462b5691eeSEugene Grosbein 			if (asprintf(&tstr, "%s%s", _PATH_DEV, path) < 0)
1472b5691eeSEugene Grosbein 				errx(EX_OSERR, "no memory");
1482b5691eeSEugene Grosbein 			fd = open(tstr, flags);
1492b5691eeSEugene Grosbein 			free(tstr);
1502b5691eeSEugene Grosbein 		}
1512b5691eeSEugene Grosbein 	}
1522b5691eeSEugene Grosbein 
1532b5691eeSEugene Grosbein 	if (fd < 0)
1542b5691eeSEugene Grosbein 		err(EX_NOINPUT, "open failed: %s", path);
1552b5691eeSEugene Grosbein 
1562b5691eeSEugene Grosbein 	return (fd);
1572b5691eeSEugene Grosbein }
1582b5691eeSEugene Grosbein 
1592b5691eeSEugene Grosbein static off_t
1602b5691eeSEugene Grosbein getsize(const char *path)
1612b5691eeSEugene Grosbein {
1622b5691eeSEugene Grosbein 	struct stat sb;
1632b5691eeSEugene Grosbein 	off_t mediasize;
1642b5691eeSEugene Grosbein 	int fd;
1652b5691eeSEugene Grosbein 
1662b5691eeSEugene Grosbein 	fd = opendev(path, O_RDONLY | O_DIRECT);
1672b5691eeSEugene Grosbein 
1682b5691eeSEugene Grosbein 	if (fstat(fd, &sb) < 0)
1692b5691eeSEugene Grosbein 		err(EX_IOERR, "fstat failed: %s", path);
1702b5691eeSEugene Grosbein 
1712b5691eeSEugene Grosbein 	if (S_ISREG(sb.st_mode) || S_ISDIR(sb.st_mode)) {
1722b5691eeSEugene Grosbein 		close(fd);
1732b5691eeSEugene Grosbein 		return (sb.st_size);
1742b5691eeSEugene Grosbein 	}
1752b5691eeSEugene Grosbein 
1762b5691eeSEugene Grosbein 	if (!S_ISCHR(sb.st_mode) && !S_ISBLK(sb.st_mode))
1772b5691eeSEugene Grosbein 		errx(EX_DATAERR,
1782b5691eeSEugene Grosbein 			"invalid type of the file "
1792b5691eeSEugene Grosbein 			"(not regular, directory nor special device): %s",
1802b5691eeSEugene Grosbein 			path);
1812b5691eeSEugene Grosbein 
1822b5691eeSEugene Grosbein 	if (ioctl(fd, DIOCGMEDIASIZE, &mediasize) < 0)
1832b5691eeSEugene Grosbein 		err(EX_UNAVAILABLE,
1842b5691eeSEugene Grosbein 			"ioctl(DIOCGMEDIASIZE) failed, probably not a disk: "
1852b5691eeSEugene Grosbein 			"%s", path);
1862b5691eeSEugene Grosbein 
1872b5691eeSEugene Grosbein 	close(fd);
1882b5691eeSEugene Grosbein 	return (mediasize);
1892b5691eeSEugene Grosbein }
1902b5691eeSEugene Grosbein 
1912b5691eeSEugene Grosbein static int
1922b5691eeSEugene Grosbein trim(const char *path, off_t offset, off_t length, bool dryrun, bool verbose)
1932b5691eeSEugene Grosbein {
1942b5691eeSEugene Grosbein 	off_t arg[2];
1952b5691eeSEugene Grosbein 	int error, fd;
1962b5691eeSEugene Grosbein 
1972b5691eeSEugene Grosbein 	if (length == 0)
1982b5691eeSEugene Grosbein 		length = getsize(path);
1992b5691eeSEugene Grosbein 
2002b5691eeSEugene Grosbein 	if (verbose)
2012b5691eeSEugene Grosbein 		printf("trim %s offset %ju length %ju\n",
2022b5691eeSEugene Grosbein 		    path, (uintmax_t)offset, (uintmax_t)length);
2032b5691eeSEugene Grosbein 
2042b5691eeSEugene Grosbein 	if (dryrun) {
2052b5691eeSEugene Grosbein 		printf("dry run: add -f to actually perform the operation\n");
2062b5691eeSEugene Grosbein 		return (0);
2072b5691eeSEugene Grosbein 	}
2082b5691eeSEugene Grosbein 
2092b5691eeSEugene Grosbein 	fd = opendev(path, O_WRONLY | O_DIRECT);
2102b5691eeSEugene Grosbein 	arg[0] = offset;
2112b5691eeSEugene Grosbein 	arg[1] = length;
2122b5691eeSEugene Grosbein 
2132b5691eeSEugene Grosbein 	error = ioctl(fd, DIOCGDELETE, arg);
2142b5691eeSEugene Grosbein 	if (error < 0)
2152b5691eeSEugene Grosbein 		warn("ioctl(DIOCGDELETE) failed: %s", path);
2162b5691eeSEugene Grosbein 
2172b5691eeSEugene Grosbein 	close(fd);
2182b5691eeSEugene Grosbein 	return (error);
2192b5691eeSEugene Grosbein }
2202b5691eeSEugene Grosbein 
2212b5691eeSEugene Grosbein static void
2222b5691eeSEugene Grosbein usage(const char *name)
2232b5691eeSEugene Grosbein {
2242b5691eeSEugene Grosbein 	(void)fprintf(stderr,
2252b5691eeSEugene Grosbein 	    "usage: %s [-[lo] offset[K|k|M|m|G|g|T|t]] [-r rfile] [-Nfqv] device ...\n",
2262b5691eeSEugene Grosbein 	    name);
2272b5691eeSEugene Grosbein 	exit(EX_USAGE);
2282b5691eeSEugene Grosbein }
229