xref: /netbsd-src/sbin/blkdiscard/blkdiscard.c (revision afab4e300d3a9fb07dd8c80daf53d0feb3345706)
1 /*	$NetBSD: blkdiscard.c,v 1.1 2022/02/07 09:33:26 mrg Exp $	*/
2 
3 /*
4  * Copyright (c) 2019, 2020, 2022 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 <assert.h>
49 #include <stdbool.h>
50 
51 static bool secure = false;
52 static bool zeroout = false;
53 static char *zeros = NULL;
54 
55 #define FDISCARD_VERSION	20220206u
56 
57 static void __dead
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
75 version(void)
76 {
77 
78 	printf("NetBSD blkdiscard %u\n", FDISCARD_VERSION);
79 	exit(0);
80 }
81 
82 static void
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
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 
108 	/* Default /sbin/blkdiscard to be "run" */
109 	bool norun = strcmp(getprogname(), "blkdiscard") != 0;
110 
111 	while ((i = getopt(argc, argv, "f:hl:m:no:p:RsvVz")) != -1) {
112 		switch (i) {
113 		case 'l':
114 			if (dehumanize_number(optarg, &val) == -1 || val < 0)
115 				usage("bad -s\n");
116 			length = val;
117 			break;
118 		case 'm':
119 		case 'p':
120 			if (dehumanize_number(optarg, &val) == -1 || val < 0)
121 				usage("bad -m\n");
122 			max_per_call = val;
123 			break;
124 		case 'f':
125 		case 'o':
126 			if (dehumanize_number(optarg, &val) == -1 || val < 0)
127 				usage("bad -f\n");
128 			first_byte = val;
129 			break;
130 		case 'n':
131 			norun = true;
132 			break;
133 		case 'R':
134 			norun = false;
135 			break;
136 		case 's':
137 			secure = true;
138 			break;
139 		case 'V':
140 			version();
141 		case 'v':
142 			verbose = true;
143 			break;
144 		case 'z':
145 			zeroout = true;
146 			break;
147 		case 'h':
148 			usage(NULL);
149 		default:
150 			usage("bad options\n");
151 		}
152 	}
153 	argc -= optind;
154 	argv += optind;
155 
156 	if (secure)
157 		usage("blkdiscard: secure erase not yet implemnted\n");
158 	if (zeroout) {
159 		zeros = calloc(1, max_per_call);
160 		if (!zeros)
161 			errx(1, "Unable to allocate %lld bytes for zeros",
162 			    (long long)max_per_call);
163 	}
164 
165 	if (length)
166 		end_offset = first_byte + length;
167 
168 	if (argc != 1)
169 		usage("not one arg left\n");
170 
171 	char *name = argv[0];
172 	int fd = open(name, O_RDWR);
173 	if (fd < 0)
174 		err(1, "open on %s", name);
175 
176 	if (size == 0) {
177 		struct dkwedge_info dkw;
178 		if (ioctl(fd, DIOCGWEDGEINFO, &dkw) == 0) {
179 			size = dkw.dkw_size * DEV_BSIZE;
180 			if (verbose)
181 				printf("%s: wedge size is %lld\n", name,
182 				    (long long)size);
183 		}
184 	}
185 
186 	if (size == 0) {
187 		struct disklabel dl;
188 		if (ioctl(fd, DIOCGDINFO, &dl) != -1) {
189 			char partchar = name[strlen(name)-1];
190 			assert(partchar >= 'a' && partchar <= 'p');
191 			int part = partchar - 'a';
192 
193 			size = (uint64_t)dl.d_partitions[part].p_size *
194 			    dl.d_secsize;
195 			if (verbose)
196 				printf("%s: disklabel size is %lld\n", name,
197 				    (long long)size);
198 		}
199 	}
200 
201 	if (size == 0) {
202 		struct stat sb;
203 		if (fstat(fd, &sb) != -1) {
204 			size = sb.st_size;
205 			if (verbose)
206 				printf("%s: stat size is %lld\n", name,
207 				    (long long)size);
208 		}
209 	}
210 
211 	if (size == 0) {
212 		fprintf(stderr, "unable to determine size.\n");
213 		exit(1);
214 	}
215 
216 	size -= first_byte;
217 
218 	if (end_offset) {
219 		if (end_offset > size) {
220 			fprintf(stderr, "end_offset %lld > size %lld\n",
221 			    (long long)end_offset, (long long)size);
222 			usage("");
223 		}
224 		size = end_offset;
225 	}
226 
227 	if (verbose)
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 	int loop = 0;
237 	while (size > 0) {
238 		if (size > max_per_call)
239 			discard_size = max_per_call;
240 		else
241 			discard_size = size;
242 
243 		if (!norun)
244 			write_one(fd, discard_size, first_byte);
245 
246 		first_byte += discard_size;
247 		size -= discard_size;
248 		if (verbose) {
249 			printf(".");
250 			fflush(stdout);
251 			if (loop++ > 100) {
252 				loop = 0;
253 				printf("\n");
254 			}
255 		}
256 	}
257 	if (loop != 0)
258 		printf("\n");
259 
260 	if (close(fd) != 0)
261 		warnx("close failed: %s", strerror(errno));
262 
263 	return 0;
264 }
265