xref: /haiku/src/bin/fstrim.cpp (revision ed24eb5ff12640d052171c6a7feba37fab8a75d1)
1 /*
2  * Copyright 2021 David Sebek, dasebek@gmail.com
3  * Copyright 2013 Axel Dörfler, axeld@pinc-software.de
4  * All rights reserved. Distributed under the terms of the MIT License.
5  */
6 
7 
8 #include <errno.h>
9 #include <fcntl.h>
10 #include <getopt.h>
11 #include <stdint.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15 
16 #include <sys/types.h>
17 #include <sys/stat.h>
18 
19 #include <Drivers.h>
20 
21 #include <AutoDeleter.h>
22 #include <StringForSize.h>
23 
24 
25 static struct option const kLongOptions[] = {
26 	{"help", no_argument, 0, 'h'},
27 	{"offset", required_argument, 0, 'o'},
28 	{"length", required_argument, 0, 'l'},
29 	{"discard-device", no_argument, 0, 'd'},
30 	{"force", no_argument, 0, 'f'},
31 	{"verbose", no_argument, 0, 'v'},
32 	{NULL}
33 };
34 
35 
36 extern const char* __progname;
37 static const char* kProgramName = __progname;
38 
39 
40 void
41 PrintUsage(void)
42 {
43 	fprintf(stderr, "Usage: %s [options] <path-to-mounted-file-system>\n",
44 		kProgramName);
45 	fprintf(stderr, "\n");
46 	fprintf(stderr, "%s reports unused blocks to a storage device.\n",
47 		kProgramName);
48 	fprintf(stderr, "\n");
49 	fprintf(stderr, "List of options:\n");
50 	fprintf(stderr, " -o, --offset <num>  Start of the trimmed region in bytes (default: 0)\n");
51 	fprintf(stderr, " -l, --length <num>  Length of the trimmed region in bytes. Trimming will stop\n");
52 	fprintf(stderr, "                     when a file system/device boundary is reached.\n");
53 	fprintf(stderr, "                     (default: trim until the end)\n");
54 	fprintf(stderr, " --discard-device    Trim a block or character device directly instead of\n");
55 	fprintf(stderr, "                     a file system. DANGEROUS: erases data on the device!\n");
56 	fprintf(stderr, "\n");
57 	fprintf(stderr, " -f, --force         Do not ask user for confirmation of dangerous operations\n");
58 	fprintf(stderr, " -v, --verbose       Enable verbose messages\n");
59 	fprintf(stderr, " -h, --help          Display this help\n");
60 }
61 
62 
63 bool
64 IsDirectory(const int fd)
65 {
66 	struct stat fdStat;
67 	if (fstat(fd, &fdStat) == -1) {
68 		fprintf(stderr, "%s: fstat failed: %s\n", kProgramName,
69 			strerror(errno));
70 		return false;
71 	}
72 	return S_ISDIR(fdStat.st_mode);
73 }
74 
75 
76 bool
77 IsBlockDevice(const int fd)
78 {
79 	struct stat fdStat;
80 	if (fstat(fd, &fdStat) == -1) {
81 		fprintf(stderr, "%s: fstat failed: %s\n", kProgramName,
82 			strerror(errno));
83 		return false;
84 	}
85 	return S_ISBLK(fdStat.st_mode);
86 }
87 
88 
89 bool
90 IsCharacterDevice(const int fd)
91 {
92 	struct stat fdStat;
93 	if (fstat(fd, &fdStat) == -1) {
94 		fprintf(stderr, "%s: fstat failed: %s\n", kProgramName,
95 			strerror(errno));
96 		return false;
97 	}
98 	return S_ISCHR(fdStat.st_mode);
99 }
100 
101 
102 int
103 YesNoPrompt(const char* message)
104 {
105 	char* buffer;
106 	size_t bufferSize;
107 	ssize_t inputLength;
108 
109 	if (message != NULL)
110 		printf("%s\n", message);
111 
112 	while (true) {
113 		printf("Answer [yes/NO]: ");
114 
115 		buffer = NULL;
116 		bufferSize = 0;
117 		inputLength = getline(&buffer, &bufferSize, stdin);
118 
119 		MemoryDeleter deleter(buffer);
120 
121 		if (inputLength == -1) {
122 			fprintf(stderr, "%s: getline failed: %s\n", kProgramName,
123 				strerror(errno));
124 			return -1;
125 		}
126 
127 		if (strncasecmp(buffer, "yes\n", bufferSize) == 0)
128 			return 1;
129 
130 		if (strncasecmp(buffer, "no\n", bufferSize) == 0
131 			|| strncmp(buffer, "\n", bufferSize) == 0)
132 			return 0;
133 	}
134 }
135 
136 
137 bool
138 ParseUint64(const char* string, uint64* value)
139 {
140 	uint64 parsedValue;
141 	char dummy;
142 
143 	if (string == NULL || value == NULL)
144 		return false;
145 
146 	if (sscanf(string, "%" B_SCNu64 "%c", &parsedValue, &dummy) == 1) {
147 		*value = parsedValue;
148 		return true;
149 	}
150 	return false;
151 }
152 
153 
154 int
155 main(int argc, char** argv)
156 {
157 	bool discardDevice = false;
158 	bool force = false;
159 	bool verbose = false;
160 	uint64 offset = 0;
161 	uint64 length = UINT64_MAX;
162 
163 	int c;
164 	while ((c = getopt_long(argc, argv, "ho:l:fv", kLongOptions, NULL)) != -1) {
165 		switch (c) {
166 			case 0:
167 				break;
168 			case 'o':
169 				if (!ParseUint64(optarg, &offset)) {
170 					fprintf(stderr, "%s: Invalid offset value\n", kProgramName);
171 					return EXIT_FAILURE;
172 				}
173 				break;
174 			case 'l':
175 				if (!ParseUint64(optarg, &length)) {
176 					fprintf(stderr, "%s: Invalid length value\n", kProgramName);
177 					return EXIT_FAILURE;
178 				}
179 				break;
180 			case 'd':
181 				discardDevice = true;
182 				break;
183 			case 'f':
184 				force = true;
185 				break;
186 			case 'v':
187 				verbose = true;
188 				break;
189 			case 'h':
190 				PrintUsage();
191 				return EXIT_SUCCESS;
192 				break;
193 			default:
194 				PrintUsage();
195 				return EXIT_FAILURE;
196 				break;
197 		}
198 	}
199 
200 	if (argc - optind < 1) {
201 		PrintUsage();
202 		return EXIT_FAILURE;
203 	}
204 	const char* path = argv[optind++];
205 
206 	int fd = open(path, O_RDONLY);
207 	if (fd < 0) {
208 		fprintf(stderr, "%s: Could not access path: %s\n", kProgramName,
209 			strerror(errno));
210 		return EXIT_FAILURE;
211 	}
212 
213 	FileDescriptorCloser closer(fd);
214 
215 	if (IsDirectory(fd)) {
216 		if (discardDevice) {
217 			fprintf(stderr, "%s: Block or character device requested but %s"
218 				" is a directory\n", kProgramName, path);
219 			return EXIT_FAILURE;
220 		}
221 
222 		if (!force && YesNoPrompt("Trim support in Haiku is experimental and"
223 				" may result in data loss.\nContinue anyway?") != 1) {
224 			fprintf(stderr, "%s: Operation canceled by the user\n",
225 				kProgramName);
226 			return EXIT_SUCCESS;
227 		}
228 	} else if (IsBlockDevice(fd) || IsCharacterDevice(fd)) {
229 		if (!discardDevice) {
230 			fprintf(stderr, "%s: --discard-device must be specified to trim"
231 				" a block or character device\n", kProgramName);
232 			return EXIT_FAILURE;
233 		}
234 
235 		if (!force && YesNoPrompt("Do you really want to PERMANENTLY ERASE"
236 				" data from the specified device?") != 1) {
237 			fprintf(stderr, "%s: Operation canceled by the user\n",
238 				kProgramName);
239 			return EXIT_SUCCESS;
240 		}
241 	} else {
242 		fprintf(stderr, "%s: %s is neither a directory nor a block or"
243 			" character device\n", kProgramName, path);
244 		return EXIT_FAILURE;
245 	}
246 
247 	fs_trim_data trimData;
248 	trimData.range_count = 1;
249 	trimData.ranges[0].offset = offset;
250 	trimData.ranges[0].size = length;
251 	trimData.trimmed_size = 0;
252 
253 	if (verbose) {
254 		printf("Range to trim (bytes): offset = %" B_PRIu64
255 			", length = %" B_PRIu64 "\n", offset, length);
256 	}
257 
258 	int retval = EXIT_SUCCESS;
259 
260 	if (ioctl(fd, B_TRIM_DEVICE, &trimData, sizeof(fs_trim_data)) != 0) {
261 		fprintf(stderr, "%s: Trimming failed: %s\n", kProgramName,
262 			strerror(errno));
263 		retval = EXIT_FAILURE;
264 	}
265 
266 	if (retval == EXIT_SUCCESS || trimData.trimmed_size > 0) {
267 		char trimmedSize[128];
268 		string_for_size(trimData.trimmed_size, trimmedSize, 128);
269 
270 		printf("Trimmed %" B_PRIu64 " bytes (%s) from device%s.\n",
271 			trimData.trimmed_size, trimmedSize,
272 			retval == EXIT_SUCCESS ? "" : " (number may be inaccurate)");
273 	}
274 
275 	return retval;
276 }
277