1 /* $NetBSD: blkdiscard.c,v 1.4 2024/02/08 20:51:24 andvar Exp $ */
2
3 /*
4 * Copyright (c) 2019, 2020, 2022, 2024 Matthew R. Green
5 * All rights reserved.
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 * 3. The name of the author may not be used to endorse or promote products
16 * derived from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
23 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
25 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * SUCH DAMAGE.
29 */
30
31 /* from: $eterna: fdiscard-stuff.c,v 1.3 2020/12/25 23:40:19 mrg Exp $ */
32
33 /* fdiscard(2) front-end -- TRIM support. */
34
35 #include <sys/param.h>
36 #include <sys/stat.h>
37 #include <sys/disk.h>
38 #include <sys/disklabel.h>
39 #include <sys/ioctl.h>
40
41 #include <fcntl.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <unistd.h>
46 #include <err.h>
47 #include <errno.h>
48 #include <stdbool.h>
49
50 static bool secure = false;
51 static bool zeroout = false;
52 static char *zeros = NULL;
53 static unsigned ttywidth = 80;
54
55 #define FDISCARD_VERSION 20240113u
56
57 static void __dead
usage(const char * msg)58 usage(const char* msg)
59 {
60 FILE *out = stdout;
61 int rv = 0;
62
63 if (msg) {
64 out = stderr;
65 rv = 1;
66 fprintf(out, "%s", msg);
67 }
68 fprintf(out, "Usage: blkdiscard [-hnRsVvz] [-l length] "
69 "[-m chunk_bytes]\n"
70 " [-o first_byte] <file>\n");
71 exit(rv);
72 }
73
74 static void __dead
version(void)75 version(void)
76 {
77
78 printf("NetBSD blkdiscard %u\n", FDISCARD_VERSION);
79 exit(0);
80 }
81
82 static void
write_one(int fd,off_t discard_size,off_t first_byte)83 write_one(int fd, off_t discard_size, off_t first_byte)
84 {
85
86 if (secure)
87 /* not yet */;
88 else if (zeroout) {
89 if (pwrite(fd, zeros, discard_size, first_byte) != discard_size)
90 err(1, "pwrite");
91 } else if (fdiscard(fd, first_byte, discard_size) != 0)
92 err(1, "fdiscard");
93 }
94
95 int
main(int argc,char * argv[])96 main(int argc, char *argv[])
97 {
98 off_t first_byte = 0, end_offset = 0;
99 /* doing a terabyte at a time leads to ATA timeout issues */
100 off_t max_per_call = 32 * 1024 * 1024;
101 off_t discard_size;
102 off_t size = 0;
103 off_t length = 0;
104 int64_t val;
105 int i;
106 bool verbose = false;
107 struct stat sb;
108
109 /* Default /sbin/blkdiscard to be "run" */
110 bool norun = strcmp(getprogname(), "blkdiscard") != 0;
111
112 while ((i = getopt(argc, argv, "f:hl:m:no:p:RsvVz")) != -1) {
113 switch (i) {
114 case 'l':
115 if (dehumanize_number(optarg, &val) == -1 || val < 0)
116 usage("bad -s\n");
117 length = val;
118 break;
119 case 'm':
120 case 'p':
121 if (dehumanize_number(optarg, &val) == -1 || val < 0)
122 usage("bad -m\n");
123 max_per_call = val;
124 break;
125 case 'f':
126 case 'o':
127 if (dehumanize_number(optarg, &val) == -1 || val < 0)
128 usage("bad -f\n");
129 first_byte = val;
130 break;
131 case 'n':
132 norun = true;
133 break;
134 case 'R':
135 norun = false;
136 break;
137 case 's':
138 secure = true;
139 break;
140 case 'V':
141 version();
142 case 'v':
143 verbose = true;
144 break;
145 case 'z':
146 zeroout = true;
147 break;
148 case 'h':
149 usage(NULL);
150 default:
151 usage("bad options\n");
152 }
153 }
154 argc -= optind;
155 argv += optind;
156
157 if (secure)
158 usage("blkdiscard: secure erase not yet implemented\n");
159 if (zeroout) {
160 zeros = calloc(1, max_per_call);
161 if (!zeros)
162 errx(1, "Unable to allocate %lld bytes for zeros",
163 (long long)max_per_call);
164 }
165
166 if (length)
167 end_offset = first_byte + length;
168
169 if (argc != 1)
170 usage("not one arg left\n");
171
172 char *name = argv[0];
173 int fd = open(name, O_RDWR);
174 if (fd < 0)
175 err(1, "open on %s", name);
176
177 if (size == 0) {
178 struct dkwedge_info dkw;
179
180 if (ioctl(fd, DIOCGWEDGEINFO, &dkw) == 0) {
181 size = dkw.dkw_size * DEV_BSIZE;
182 if (verbose)
183 printf("%s: wedge size is %lld\n", name,
184 (long long)size);
185 }
186 }
187
188 if (size == 0 && fstat(fd, &sb) != -1) {
189 struct disklabel dl;
190
191 if (ioctl(fd, DIOCGDINFO, &dl) != -1) {
192 unsigned part = DISKPART(sb.st_rdev);
193
194 size = (uint64_t)dl.d_partitions[part].p_size *
195 dl.d_secsize;
196 if (verbose)
197 printf("%s: partition %u disklabel size is"
198 " %lld\n", name, part, (long long)size);
199 }
200
201 if (size == 0) {
202 size = sb.st_size;
203 if (verbose)
204 printf("%s: stat size is %lld\n", name,
205 (long long)size);
206 }
207 }
208
209 if (size == 0) {
210 fprintf(stderr, "unable to determine size.\n");
211 exit(1);
212 }
213
214 size -= first_byte;
215
216 if (end_offset) {
217 if (end_offset > size) {
218 fprintf(stderr, "end_offset %lld > size %lld\n",
219 (long long)end_offset, (long long)size);
220 usage("");
221 }
222 size = end_offset;
223 }
224
225 if (verbose) {
226 struct winsize winsize;
227
228 printf("%sgoing to %s on %s from byte %lld for "
229 "%lld bytes, %lld at a time\n",
230 norun ? "not " : "",
231 secure ? "secure erase" :
232 zeroout ? "zero out" : "fdiscard(2)",
233 name, (long long)first_byte, (long long)size,
234 (long long)max_per_call);
235
236 if (ioctl(fileno(stdout), TIOCGWINSZ, &winsize) != -1 &&
237 winsize.ws_col > 1)
238 ttywidth = winsize.ws_col - 1;
239 }
240
241 unsigned loop = 0;
242 while (size > 0) {
243 if (size > max_per_call)
244 discard_size = max_per_call;
245 else
246 discard_size = size;
247
248 if (!norun)
249 write_one(fd, discard_size, first_byte);
250
251 first_byte += discard_size;
252 size -= discard_size;
253 if (verbose) {
254 printf(".");
255 fflush(stdout);
256 if (++loop >= ttywidth) {
257 loop = 0;
258 printf("\n");
259 }
260 }
261 }
262 if (loop != 0)
263 printf("\n");
264
265 if (close(fd) != 0)
266 warnx("close failed: %s", strerror(errno));
267
268 return 0;
269 }
270