Extend the functionality of bin/fstrim
Improvements: * Introduce new command-line parameters for the offset and length of the trimmed region * Introduce a new command-line parameter that must be specified in order to trim a block/character device instead of a file system * By default, warn users that trim can potentially destroy data * Display the number of bytes trimmed even if the ioctl returns an error Change-Id: I9eec535abe74f7ef09c927292a120016f4156684 Reviewed-on: https://review.haiku-os.org/c/haiku/+/4154 Tested-by: Commit checker robot <no-reply+buildbot@haiku-os.org> Reviewed-by: Adrien Destugues <pulkomandy@gmail.com>
This commit is contained in:
parent
f04ae5ab1f
commit
3e19f197fa
@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Copyright 2013, Axel Dörfler, axeld@pinc-software.de.
|
||||
* Distributed under the terms of the MIT license.
|
||||
* Copyright 2021 David Sebek, dasebek@gmail.com
|
||||
* Copyright 2013 Axel Dörfler, axeld@pinc-software.de
|
||||
* All rights reserved. Distributed under the terms of the MIT License.
|
||||
*/
|
||||
|
||||
|
||||
@ -12,6 +13,9 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <Drivers.h>
|
||||
|
||||
#include <AutoDeleter.h>
|
||||
@ -19,41 +23,183 @@
|
||||
|
||||
static struct option const kLongOptions[] = {
|
||||
{"help", no_argument, 0, 'h'},
|
||||
{"offset", required_argument, 0, 'o'},
|
||||
{"length", required_argument, 0, 'l'},
|
||||
{"discard-device", no_argument, 0, 'd'},
|
||||
{"force", no_argument, 0, 'f'},
|
||||
{"verbose", no_argument, 0, 'v'},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
|
||||
extern const char *__progname;
|
||||
static const char *kProgramName = __progname;
|
||||
extern const char* __progname;
|
||||
static const char* kProgramName = __progname;
|
||||
|
||||
|
||||
void
|
||||
usage(int returnValue)
|
||||
PrintUsage(void)
|
||||
{
|
||||
fprintf(stderr, "Usage: %s <path-to-mounted-file-system>\n", kProgramName);
|
||||
exit(returnValue);
|
||||
fprintf(stderr, "Usage: %s [options] <path-to-mounted-file-system>\n",
|
||||
kProgramName);
|
||||
fprintf(stderr, "\n");
|
||||
fprintf(stderr, "%s reports unused blocks to a storage device.\n",
|
||||
kProgramName);
|
||||
fprintf(stderr, "\n");
|
||||
fprintf(stderr, "List of options:\n");
|
||||
fprintf(stderr, " -o, --offset <num> Start of the trimmed region in bytes (default: 0)\n");
|
||||
fprintf(stderr, " -l, --length <num> Length of the trimmed region in bytes. Trimming will stop\n");
|
||||
fprintf(stderr, " when a file system/device boundary is reached.\n");
|
||||
fprintf(stderr, " (default: trim until the end)\n");
|
||||
fprintf(stderr, " --discard-device Trim a block or character device directly instead of\n");
|
||||
fprintf(stderr, " a file system. DANGEROUS: erases data on the device!\n");
|
||||
fprintf(stderr, "\n");
|
||||
fprintf(stderr, " -f, --force Do not ask user for confirmation of dangerous operations\n");
|
||||
fprintf(stderr, " -v, --verbose Enable verbose messages\n");
|
||||
fprintf(stderr, " -h, --help Display this help\n");
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
IsDirectory(const int fd)
|
||||
{
|
||||
struct stat fdStat;
|
||||
if (fstat(fd, &fdStat) == -1) {
|
||||
fprintf(stderr, "%s: fstat failed: %s\n", kProgramName,
|
||||
strerror(errno));
|
||||
return false;
|
||||
}
|
||||
return S_ISDIR(fdStat.st_mode);
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
IsBlockDevice(const int fd)
|
||||
{
|
||||
struct stat fdStat;
|
||||
if (fstat(fd, &fdStat) == -1) {
|
||||
fprintf(stderr, "%s: fstat failed: %s\n", kProgramName,
|
||||
strerror(errno));
|
||||
return false;
|
||||
}
|
||||
return S_ISBLK(fdStat.st_mode);
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
IsCharacterDevice(const int fd)
|
||||
{
|
||||
struct stat fdStat;
|
||||
if (fstat(fd, &fdStat) == -1) {
|
||||
fprintf(stderr, "%s: fstat failed: %s\n", kProgramName,
|
||||
strerror(errno));
|
||||
return false;
|
||||
}
|
||||
return S_ISCHR(fdStat.st_mode);
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
YesNoPrompt(const char* message)
|
||||
{
|
||||
char* buffer;
|
||||
size_t bufferSize;
|
||||
ssize_t inputLength;
|
||||
|
||||
if (message != NULL)
|
||||
printf("%s\n", message);
|
||||
|
||||
while (true) {
|
||||
printf("Answer [yes/NO]: ");
|
||||
|
||||
buffer = NULL;
|
||||
bufferSize = 0;
|
||||
inputLength = getline(&buffer, &bufferSize, stdin);
|
||||
|
||||
MemoryDeleter deleter(buffer);
|
||||
|
||||
if (inputLength == -1) {
|
||||
fprintf(stderr, "%s: getline failed: %s\n", kProgramName,
|
||||
strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (strncasecmp(buffer, "yes\n", bufferSize) == 0)
|
||||
return 1;
|
||||
|
||||
if (strncasecmp(buffer, "no\n", bufferSize) == 0
|
||||
|| strncmp(buffer, "\n", bufferSize) == 0)
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
ParseUint64(const char* string, uint64* value)
|
||||
{
|
||||
uint64 parsedValue;
|
||||
char dummy;
|
||||
|
||||
if (string == NULL || value == NULL)
|
||||
return false;
|
||||
|
||||
if (sscanf(string, "%" B_SCNu64 "%c", &parsedValue, &dummy) == 1) {
|
||||
*value = parsedValue;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
main(int argc, char** argv)
|
||||
{
|
||||
bool discardDevice = false;
|
||||
bool force = false;
|
||||
bool verbose = false;
|
||||
uint64 offset = 0;
|
||||
uint64 length = UINT64_MAX;
|
||||
|
||||
int c;
|
||||
while ((c = getopt_long(argc, argv, "h", kLongOptions, NULL)) != -1) {
|
||||
while ((c = getopt_long(argc, argv, "ho:l:fv", kLongOptions, NULL)) != -1) {
|
||||
switch (c) {
|
||||
case 0:
|
||||
break;
|
||||
case 'o':
|
||||
if (!ParseUint64(optarg, &offset)) {
|
||||
fprintf(stderr, "%s: Invalid offset value\n", kProgramName);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
break;
|
||||
case 'l':
|
||||
if (!ParseUint64(optarg, &length)) {
|
||||
fprintf(stderr, "%s: Invalid length value\n", kProgramName);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
break;
|
||||
case 'd':
|
||||
discardDevice = true;
|
||||
break;
|
||||
case 'f':
|
||||
force = true;
|
||||
break;
|
||||
case 'v':
|
||||
verbose = true;
|
||||
break;
|
||||
case 'h':
|
||||
usage(0);
|
||||
PrintUsage();
|
||||
return EXIT_SUCCESS;
|
||||
break;
|
||||
default:
|
||||
usage(1);
|
||||
PrintUsage();
|
||||
return EXIT_FAILURE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (argc - optind < 1)
|
||||
usage(1);
|
||||
if (argc - optind < 1) {
|
||||
PrintUsage();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
const char* path = argv[optind++];
|
||||
|
||||
int fd = open(path, O_RDONLY);
|
||||
@ -65,18 +211,60 @@ main(int argc, char** argv)
|
||||
|
||||
FileDescriptorCloser closer(fd);
|
||||
|
||||
if (IsDirectory(fd)) {
|
||||
if (discardDevice) {
|
||||
fprintf(stderr, "%s: Block or character device requested but %s"
|
||||
" is a directory\n", kProgramName, path);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (!force && YesNoPrompt("Trim support in Haiku is experimental and"
|
||||
" may result in data loss.\nContinue anyway?") != 1) {
|
||||
fprintf(stderr, "%s: Operation canceled by the user\n",
|
||||
kProgramName);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
} else if (IsBlockDevice(fd) || IsCharacterDevice(fd)) {
|
||||
if (!discardDevice) {
|
||||
fprintf(stderr, "%s: --discard-device must be specified to trim"
|
||||
" a block or character device\n", kProgramName);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (!force && YesNoPrompt("Do you really want to PERMANENTLY ERASE"
|
||||
" data from the specified device?") != 1) {
|
||||
fprintf(stderr, "%s: Operation canceled by the user\n",
|
||||
kProgramName);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "%s: %s is neither a directory nor a block or"
|
||||
" character device\n", kProgramName, path);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
fs_trim_data trimData;
|
||||
trimData.range_count = 1;
|
||||
trimData.ranges[0].offset = 0;
|
||||
trimData.ranges[0].size = UINT64_MAX;
|
||||
trimData.ranges[0].offset = offset;
|
||||
trimData.ranges[0].size = length;
|
||||
trimData.trimmed_size = 0;
|
||||
|
||||
if (verbose) {
|
||||
printf("Range to trim (bytes): offset = %" B_PRIu64
|
||||
", length = %" B_PRIu64 "\n", offset, length);
|
||||
}
|
||||
|
||||
int retval = EXIT_SUCCESS;
|
||||
|
||||
if (ioctl(fd, B_TRIM_DEVICE, &trimData, sizeof(fs_trim_data)) != 0) {
|
||||
fprintf(stderr, "%s: Trimming failed: %s\n", kProgramName,
|
||||
strerror(errno));
|
||||
return EXIT_FAILURE;
|
||||
retval = EXIT_FAILURE;
|
||||
}
|
||||
|
||||
printf("Trimmed %" B_PRIu64 " bytes from device.\n", trimData.trimmed_size);
|
||||
return EXIT_SUCCESS;
|
||||
printf("Trimmed %" B_PRIu64 " bytes from device%s.\n",
|
||||
trimData.trimmed_size,
|
||||
retval == EXIT_SUCCESS ? "" : " (number may be inaccurate)");
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user