1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 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> 48 __FBSDID("$FreeBSD$"); 49 50 static bool candelete(int fd); 51 static off_t getsize(const char *path); 52 static int opendev(const char *path, int flags); 53 static int trim(const char *path, off_t offset, off_t length, bool dryrun, bool verbose); 54 static void usage(const char *name); 55 56 int 57 main(int argc, char **argv) 58 { 59 off_t offset, length; 60 uint64_t usz; 61 int ch, error; 62 bool dryrun, verbose; 63 char *fname, *name; 64 65 error = 0; 66 length = offset = 0; 67 name = argv[0]; 68 dryrun = verbose = true; 69 70 while ((ch = getopt(argc, argv, "Nfl:o:qr:v")) != -1) 71 switch (ch) { 72 case 'N': 73 dryrun = true; 74 verbose = true; 75 break; 76 case 'f': 77 dryrun = false; 78 break; 79 case 'l': 80 case 'o': 81 if (expand_number(optarg, &usz) == -1 || 82 (off_t)usz < 0 || (usz == 0 && ch == 'l')) 83 errx(EX_USAGE, 84 "invalid %s of the region: %s", 85 ch == 'o' ? "offset" : "length", 86 optarg); 87 if (ch == 'o') 88 offset = (off_t)usz; 89 else 90 length = (off_t)usz; 91 break; 92 case 'q': 93 verbose = false; 94 break; 95 case 'r': 96 if ((length = getsize(optarg)) == 0) 97 errx(EX_USAGE, 98 "invalid zero length reference file" 99 " for the region: %s", optarg); 100 break; 101 case 'v': 102 verbose = true; 103 break; 104 default: 105 usage(name); 106 /* NOTREACHED */ 107 } 108 109 /* 110 * Safety net: do not allow mistakes like 111 * 112 * trim -f /dev/da0 -r rfile 113 * 114 * This would trim whole device then error on non-existing file -r. 115 * Following check prevents this while allowing this form still: 116 * 117 * trim -f -- /dev/da0 -r rfile 118 */ 119 120 if (strcmp(argv[optind-1], "--") != 0) { 121 for (ch = optind; ch < argc; ch++) 122 if (argv[ch][0] == '-') 123 usage(name); 124 } 125 126 argv += optind; 127 argc -= optind; 128 129 if (argc < 1) 130 usage(name); 131 132 while ((fname = *argv++) != NULL) 133 if (trim(fname, offset, length, dryrun, verbose) < 0) 134 error++; 135 136 return (error ? EXIT_FAILURE : EXIT_SUCCESS); 137 } 138 139 static bool 140 candelete(int fd) 141 { 142 struct diocgattr_arg arg; 143 144 strlcpy(arg.name, "GEOM::candelete", sizeof(arg.name)); 145 arg.len = sizeof(arg.value.i); 146 if (ioctl(fd, DIOCGATTR, &arg) == 0) 147 return (arg.value.i != 0); 148 else 149 return (false); 150 } 151 152 static int 153 opendev(const char *path, int flags) 154 { 155 int fd; 156 char *tstr; 157 158 if ((fd = open(path, flags)) < 0) { 159 if (errno == ENOENT && path[0] != '/') { 160 if (asprintf(&tstr, "%s%s", _PATH_DEV, path) < 0) 161 errx(EX_OSERR, "no memory"); 162 fd = open(tstr, flags); 163 free(tstr); 164 } 165 } 166 167 if (fd < 0) 168 err(EX_NOINPUT, "open failed: %s", path); 169 170 return (fd); 171 } 172 173 static off_t 174 getsize(const char *path) 175 { 176 struct stat sb; 177 off_t mediasize; 178 int fd; 179 180 fd = opendev(path, O_RDONLY | O_DIRECT); 181 182 if (fstat(fd, &sb) < 0) 183 err(EX_IOERR, "fstat failed: %s", path); 184 185 if (S_ISREG(sb.st_mode) || S_ISDIR(sb.st_mode)) { 186 close(fd); 187 return (sb.st_size); 188 } 189 190 if (!S_ISCHR(sb.st_mode) && !S_ISBLK(sb.st_mode)) 191 errx(EX_DATAERR, 192 "invalid type of the file " 193 "(not regular, directory nor special device): %s", 194 path); 195 196 if (ioctl(fd, DIOCGMEDIASIZE, &mediasize) < 0) 197 err(EX_UNAVAILABLE, 198 "ioctl(DIOCGMEDIASIZE) failed, probably not a disk: " 199 "%s", path); 200 201 close(fd); 202 return (mediasize); 203 } 204 205 static int 206 trim(const char *path, off_t offset, off_t length, bool dryrun, bool verbose) 207 { 208 off_t arg[2]; 209 int error, fd; 210 211 if (length == 0) 212 length = getsize(path); 213 214 if (verbose) 215 printf("trim %s offset %ju length %ju\n", 216 path, (uintmax_t)offset, (uintmax_t)length); 217 218 if (dryrun) { 219 printf("dry run: add -f to actually perform the operation\n"); 220 return (0); 221 } 222 223 fd = opendev(path, O_WRONLY | O_DIRECT); 224 arg[0] = offset; 225 arg[1] = length; 226 227 error = ioctl(fd, DIOCGDELETE, arg); 228 if (error < 0) { 229 if (errno == EOPNOTSUPP && verbose && !candelete(fd)) 230 warnx("%s: TRIM/UNMAP not supported by driver", path); 231 else 232 warn("ioctl(DIOCGDELETE) failed: %s", path); 233 } 234 close(fd); 235 return (error); 236 } 237 238 static void 239 usage(const char *name) 240 { 241 (void)fprintf(stderr, 242 "usage: %s [-[lo] offset[K|k|M|m|G|g|T|t]] [-r rfile] [-Nfqv] device ...\n", 243 name); 244 exit(EX_USAGE); 245 } 246