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