xref: /netbsd-src/sbin/blkdiscard/blkdiscard.c (revision d3789543466c1fc9b2703e1746ab85d655beccb3)
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