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> 48 static bool candelete(int fd); 49 static off_t getsize(const char *path); 50 static int opendev(const char *path, int flags); 51 static int trim(const char *path, off_t offset, off_t length, bool dryrun, bool verbose); 52 static void usage(const char *name); 53 54 int 55 main(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 137 static bool 138 candelete(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 150 static int 151 opendev(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 171 static off_t 172 getsize(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 203 static int 204 trim(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 236 static void 237 usage(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