1343118Seugen/*- 2343118Seugen * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3343118Seugen * 4343118Seugen * Copyright (c) 2019 Eugene Grosbein <eugen@FreeBSD.org>. 5345375Seugen * Contains code written by Alan Somers <asomers@FreeBSD.org>. 6343118Seugen * 7343118Seugen * Redistribution and use in source and binary forms, with or without 8343118Seugen * modification, are permitted provided that the following conditions 9343118Seugen * are met: 10343118Seugen * 1. Redistributions of source code must retain the above copyright 11343118Seugen * notice, this list of conditions and the following disclaimer. 12343118Seugen * 2. Redistributions in binary form must reproduce the above copyright 13343118Seugen * notice, this list of conditions and the following disclaimer in the 14343118Seugen * documentation and/or other materials provided with the distribution. 15343118Seugen * 16343118Seugen * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17343118Seugen * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18343118Seugen * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19343118Seugen * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20343118Seugen * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21343118Seugen * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22343118Seugen * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23343118Seugen * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24343118Seugen * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25343118Seugen * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26343118Seugen * SUCH DAMAGE. 27343118Seugen * 28343118Seugen */ 29343118Seugen 30343118Seugen#include <sys/disk.h> 31343118Seugen#include <sys/ioctl.h> 32343118Seugen#include <sys/stat.h> 33343118Seugen 34343118Seugen#include <err.h> 35343118Seugen#include <errno.h> 36343118Seugen#include <fcntl.h> 37343118Seugen#include <libutil.h> 38343118Seugen#include <limits.h> 39343118Seugen#include <paths.h> 40343118Seugen#include <stdbool.h> 41343118Seugen#include <stdio.h> 42343118Seugen#include <stdlib.h> 43345375Seugen#include <string.h> 44343118Seugen#include <sysexits.h> 45343118Seugen#include <unistd.h> 46343118Seugen 47343118Seugen#include <sys/cdefs.h> 48343118Seugen__FBSDID("$FreeBSD: stable/11/usr.sbin/trim/trim.c 345375 2019-03-21 11:32:52Z eugen $"); 49343118Seugen 50345375Seugenstatic bool candelete(int fd); 51343118Seugenstatic off_t getsize(const char *path); 52343118Seugenstatic int opendev(const char *path, int flags); 53343118Seugenstatic int trim(const char *path, off_t offset, off_t length, bool dryrun, bool verbose); 54343118Seugenstatic void usage(const char *name); 55343118Seugen 56343118Seugenint 57343118Seugenmain(int argc, char **argv) 58343118Seugen{ 59343118Seugen off_t offset, length; 60343118Seugen uint64_t usz; 61343118Seugen int ch, error; 62343118Seugen bool dryrun, verbose; 63343118Seugen char *fname, *name; 64343118Seugen 65343118Seugen error = 0; 66343118Seugen length = offset = 0; 67343118Seugen name = argv[0]; 68343118Seugen dryrun = verbose = true; 69343118Seugen 70343118Seugen while ((ch = getopt(argc, argv, "Nfl:o:qr:v")) != -1) 71343118Seugen switch (ch) { 72343118Seugen case 'N': 73343118Seugen dryrun = true; 74343118Seugen verbose = true; 75343118Seugen break; 76343118Seugen case 'f': 77343118Seugen dryrun = false; 78343118Seugen break; 79343118Seugen case 'l': 80343118Seugen case 'o': 81343118Seugen if (expand_number(optarg, &usz) == -1 || 82343118Seugen (off_t)usz < 0 || (usz == 0 && ch == 'l')) 83343118Seugen errx(EX_USAGE, 84343118Seugen "invalid %s of the region: %s", 85343118Seugen ch == 'o' ? "offset" : "length", 86343118Seugen optarg); 87343118Seugen if (ch == 'o') 88343118Seugen offset = (off_t)usz; 89343118Seugen else 90343118Seugen length = (off_t)usz; 91343118Seugen break; 92343118Seugen case 'q': 93343118Seugen verbose = false; 94343118Seugen break; 95343118Seugen case 'r': 96343118Seugen if ((length = getsize(optarg)) == 0) 97343118Seugen errx(EX_USAGE, 98343118Seugen "invalid zero length reference file" 99343118Seugen " for the region: %s", optarg); 100343118Seugen break; 101343118Seugen case 'v': 102343118Seugen verbose = true; 103343118Seugen break; 104343118Seugen default: 105343118Seugen usage(name); 106343118Seugen /* NOTREACHED */ 107343118Seugen } 108343118Seugen 109345375Seugen /* 110345375Seugen * Safety net: do not allow mistakes like 111345375Seugen * 112345375Seugen * trim -f /dev/da0 -r rfile 113345375Seugen * 114345375Seugen * This would trim whole device then error on non-existing file -r. 115345375Seugen * Following check prevents this while allowing this form still: 116345375Seugen * 117345375Seugen * trim -f -- /dev/da0 -r rfile 118345375Seugen */ 119345375Seugen 120345375Seugen if (strcmp(argv[optind-1], "--") != 0) { 121345375Seugen for (ch = optind; ch < argc; ch++) 122345375Seugen if (argv[ch][0] == '-') 123345375Seugen usage(name); 124345375Seugen } 125345375Seugen 126343118Seugen argv += optind; 127343118Seugen argc -= optind; 128343118Seugen 129343118Seugen if (argc < 1) 130343118Seugen usage(name); 131343118Seugen 132343118Seugen while ((fname = *argv++) != NULL) 133343118Seugen if (trim(fname, offset, length, dryrun, verbose) < 0) 134343118Seugen error++; 135343118Seugen 136343118Seugen return (error ? EXIT_FAILURE : EXIT_SUCCESS); 137343118Seugen} 138343118Seugen 139345375Seugenstatic bool 140345375Seugencandelete(int fd) 141345375Seugen{ 142345375Seugen struct diocgattr_arg arg; 143345375Seugen 144345375Seugen strlcpy(arg.name, "GEOM::candelete", sizeof(arg.name)); 145345375Seugen arg.len = sizeof(arg.value.i); 146345375Seugen if (ioctl(fd, DIOCGATTR, &arg) == 0) 147345375Seugen return (arg.value.i != 0); 148345375Seugen else 149345375Seugen return (false); 150345375Seugen} 151345375Seugen 152343118Seugenstatic int 153343118Seugenopendev(const char *path, int flags) 154343118Seugen{ 155343118Seugen int fd; 156343118Seugen char *tstr; 157343118Seugen 158343118Seugen if ((fd = open(path, flags)) < 0) { 159343118Seugen if (errno == ENOENT && path[0] != '/') { 160343118Seugen if (asprintf(&tstr, "%s%s", _PATH_DEV, path) < 0) 161343118Seugen errx(EX_OSERR, "no memory"); 162343118Seugen fd = open(tstr, flags); 163343118Seugen free(tstr); 164343118Seugen } 165343118Seugen } 166343118Seugen 167343118Seugen if (fd < 0) 168343118Seugen err(EX_NOINPUT, "open failed: %s", path); 169343118Seugen 170343118Seugen return (fd); 171343118Seugen} 172343118Seugen 173343118Seugenstatic off_t 174343118Seugengetsize(const char *path) 175343118Seugen{ 176343118Seugen struct stat sb; 177343118Seugen off_t mediasize; 178343118Seugen int fd; 179343118Seugen 180343118Seugen fd = opendev(path, O_RDONLY | O_DIRECT); 181343118Seugen 182343118Seugen if (fstat(fd, &sb) < 0) 183343118Seugen err(EX_IOERR, "fstat failed: %s", path); 184343118Seugen 185343118Seugen if (S_ISREG(sb.st_mode) || S_ISDIR(sb.st_mode)) { 186343118Seugen close(fd); 187343118Seugen return (sb.st_size); 188343118Seugen } 189343118Seugen 190343118Seugen if (!S_ISCHR(sb.st_mode) && !S_ISBLK(sb.st_mode)) 191343118Seugen errx(EX_DATAERR, 192343118Seugen "invalid type of the file " 193343118Seugen "(not regular, directory nor special device): %s", 194343118Seugen path); 195343118Seugen 196343118Seugen if (ioctl(fd, DIOCGMEDIASIZE, &mediasize) < 0) 197343118Seugen err(EX_UNAVAILABLE, 198343118Seugen "ioctl(DIOCGMEDIASIZE) failed, probably not a disk: " 199343118Seugen "%s", path); 200343118Seugen 201343118Seugen close(fd); 202343118Seugen return (mediasize); 203343118Seugen} 204343118Seugen 205343118Seugenstatic int 206343118Seugentrim(const char *path, off_t offset, off_t length, bool dryrun, bool verbose) 207343118Seugen{ 208343118Seugen off_t arg[2]; 209343118Seugen int error, fd; 210343118Seugen 211343118Seugen if (length == 0) 212343118Seugen length = getsize(path); 213343118Seugen 214343118Seugen if (verbose) 215343118Seugen printf("trim %s offset %ju length %ju\n", 216343118Seugen path, (uintmax_t)offset, (uintmax_t)length); 217343118Seugen 218343118Seugen if (dryrun) { 219343118Seugen printf("dry run: add -f to actually perform the operation\n"); 220343118Seugen return (0); 221343118Seugen } 222343118Seugen 223343118Seugen fd = opendev(path, O_WRONLY | O_DIRECT); 224343118Seugen arg[0] = offset; 225343118Seugen arg[1] = length; 226343118Seugen 227343118Seugen error = ioctl(fd, DIOCGDELETE, arg); 228345375Seugen if (error < 0) { 229345375Seugen if (errno == EOPNOTSUPP && verbose && !candelete(fd)) 230345375Seugen warnx("%s: TRIM/UNMAP not supported by driver", path); 231345375Seugen else 232345375Seugen warn("ioctl(DIOCGDELETE) failed: %s", path); 233345375Seugen } 234343118Seugen close(fd); 235343118Seugen return (error); 236343118Seugen} 237343118Seugen 238343118Seugenstatic void 239343118Seugenusage(const char *name) 240343118Seugen{ 241343118Seugen (void)fprintf(stderr, 242343118Seugen "usage: %s [-[lo] offset[K|k|M|m|G|g|T|t]] [-r rfile] [-Nfqv] device ...\n", 243343118Seugen name); 244343118Seugen exit(EX_USAGE); 245343118Seugen} 246