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.
|
* Copyright 2021 David Sebek, dasebek@gmail.com
|
||||||
* Distributed under the terms of the MIT license.
|
* 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 <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
#include <Drivers.h>
|
#include <Drivers.h>
|
||||||
|
|
||||||
#include <AutoDeleter.h>
|
#include <AutoDeleter.h>
|
||||||
@ -19,41 +23,183 @@
|
|||||||
|
|
||||||
static struct option const kLongOptions[] = {
|
static struct option const kLongOptions[] = {
|
||||||
{"help", no_argument, 0, 'h'},
|
{"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}
|
{NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
extern const char *__progname;
|
extern const char* __progname;
|
||||||
static const char *kProgramName = __progname;
|
static const char* kProgramName = __progname;
|
||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
usage(int returnValue)
|
PrintUsage(void)
|
||||||
{
|
{
|
||||||
fprintf(stderr, "Usage: %s <path-to-mounted-file-system>\n", kProgramName);
|
fprintf(stderr, "Usage: %s [options] <path-to-mounted-file-system>\n",
|
||||||
exit(returnValue);
|
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
|
int
|
||||||
main(int argc, char** argv)
|
main(int argc, char** argv)
|
||||||
{
|
{
|
||||||
|
bool discardDevice = false;
|
||||||
|
bool force = false;
|
||||||
|
bool verbose = false;
|
||||||
|
uint64 offset = 0;
|
||||||
|
uint64 length = UINT64_MAX;
|
||||||
|
|
||||||
int c;
|
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) {
|
switch (c) {
|
||||||
case 0:
|
case 0:
|
||||||
break;
|
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':
|
case 'h':
|
||||||
usage(0);
|
PrintUsage();
|
||||||
|
return EXIT_SUCCESS;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
usage(1);
|
PrintUsage();
|
||||||
|
return EXIT_FAILURE;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argc - optind < 1)
|
if (argc - optind < 1) {
|
||||||
usage(1);
|
PrintUsage();
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
const char* path = argv[optind++];
|
const char* path = argv[optind++];
|
||||||
|
|
||||||
int fd = open(path, O_RDONLY);
|
int fd = open(path, O_RDONLY);
|
||||||
@ -65,18 +211,60 @@ main(int argc, char** argv)
|
|||||||
|
|
||||||
FileDescriptorCloser closer(fd);
|
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;
|
fs_trim_data trimData;
|
||||||
trimData.range_count = 1;
|
trimData.range_count = 1;
|
||||||
trimData.ranges[0].offset = 0;
|
trimData.ranges[0].offset = offset;
|
||||||
trimData.ranges[0].size = UINT64_MAX;
|
trimData.ranges[0].size = length;
|
||||||
trimData.trimmed_size = 0;
|
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) {
|
if (ioctl(fd, B_TRIM_DEVICE, &trimData, sizeof(fs_trim_data)) != 0) {
|
||||||
fprintf(stderr, "%s: Trimming failed: %s\n", kProgramName,
|
fprintf(stderr, "%s: Trimming failed: %s\n", kProgramName,
|
||||||
strerror(errno));
|
strerror(errno));
|
||||||
return EXIT_FAILURE;
|
retval = EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("Trimmed %" B_PRIu64 " bytes from device.\n", trimData.trimmed_size);
|
printf("Trimmed %" B_PRIu64 " bytes from device%s.\n",
|
||||||
return EXIT_SUCCESS;
|
trimData.trimmed_size,
|
||||||
|
retval == EXIT_SUCCESS ? "" : " (number may be inaccurate)");
|
||||||
|
|
||||||
|
return retval;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user