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
25static 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
36extern const char* __progname;
37static const char* kProgramName = __progname;
38
39
40void
41PrintUsage(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
63bool
64IsDirectory(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
76bool
77IsBlockDevice(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
89bool
90IsCharacterDevice(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
102int
103YesNoPrompt(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
137bool
138ParseUint64(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
154int
155main(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