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