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