551 lines
15 KiB
C
551 lines
15 KiB
C
/*
|
|
*/
|
|
|
|
#include <libcbd.h>
|
|
|
|
#include <cbdutil.h>
|
|
|
|
#include <getopt.h>
|
|
|
|
/*
|
|
* Parse argument into an unsigned 64-bit value.
|
|
*
|
|
* The following suffixes are recognized (case insensitive and
|
|
* always base two, as in LVM):
|
|
* b bytes
|
|
* s sectors
|
|
* k kilo (1024)
|
|
* m mega (1024^2)
|
|
* g giga (1024^3)
|
|
* t tera (1024^4)
|
|
* p peta (1024^5)
|
|
*
|
|
* If no suffix is given, bytes are assumed.
|
|
*/
|
|
static bool
|
|
parse_numeric_arg(const char* arg, uint64_t* val)
|
|
{
|
|
unsigned long ulval;
|
|
const char* end;
|
|
|
|
ulval = strtoul(arg, (char**)&end, 0);
|
|
if (ulval == ULONG_MAX || end == arg) {
|
|
return false;
|
|
}
|
|
*val = ulval;
|
|
switch (*end) {
|
|
case '\0':
|
|
case 'B':
|
|
case 'b':
|
|
/* No conversion */
|
|
break;
|
|
case 'S':
|
|
case 's':
|
|
*val <<= 9;
|
|
break;
|
|
case 'K':
|
|
case 'k':
|
|
*val <<= 10;
|
|
break;
|
|
case 'M':
|
|
case 'm':
|
|
*val <<= 20;
|
|
break;
|
|
case 'G':
|
|
case 'g':
|
|
*val <<= 30;
|
|
break;
|
|
case 'T':
|
|
case 't':
|
|
*val <<= 40;
|
|
break;
|
|
case 'P':
|
|
case 'p':
|
|
*val <<= 50;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
struct profile {
|
|
const char* name;
|
|
uint pblksize;
|
|
uint lblksize;
|
|
enum cbd_alg alg;
|
|
uint level;
|
|
};
|
|
static struct profile profiles[] = {
|
|
{ "performance", 1*1024, 4*1024, CBD_ALG_LZ4, 1 },
|
|
{ "mixed", 4*1024, 64*1024, CBD_ALG_LZ4, 3 },
|
|
{ "read-mostly", 4*1024, 64*1024, CBD_ALG_ZLIB, 6 },
|
|
{ "archive", 4*1024, 256*1024, CBD_ALG_ZLIB, 9 },
|
|
};
|
|
|
|
static bool
|
|
parse_profile(const char* name,
|
|
uint* pblksize, uint* lblksize,
|
|
enum cbd_alg* alg, uint* level)
|
|
{
|
|
uint n;
|
|
|
|
for (n = 0; n < ARRAY_SIZE(profiles); ++n) {
|
|
struct profile* profile = &profiles[n];
|
|
if (!strcmp(profile->name, name)) {
|
|
*pblksize = profile->pblksize;
|
|
*lblksize = profile->lblksize;
|
|
*alg = profile->alg;
|
|
*level = profile->level;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static uint
|
|
get_shift(uint val, uint base)
|
|
{
|
|
uint shift = 0;
|
|
|
|
while (val > base) {
|
|
++shift;
|
|
base *= 2;
|
|
}
|
|
|
|
return shift;
|
|
}
|
|
|
|
static const char* progname;
|
|
|
|
static void __attribute__((noreturn))
|
|
usage(void)
|
|
{
|
|
fprintf(stderr, "Usage: %s [-h] [-v] <command> [options] <device>\n"
|
|
"\n", progname);
|
|
fprintf(stderr, "Global arguments:\n"
|
|
" -h --help Show this help message and exit\n"
|
|
" -v --verbose Increase verbose level\n"
|
|
"\n");
|
|
fprintf(stderr, "Commands:\n"
|
|
" format [opts] <device> Create (format) a compressed device\n"
|
|
" -P --pbat-size Physical block allocation table size [1]\n"
|
|
" -S --pysical-size Physical size [device size]\n"
|
|
" -c --compress-factor Compression factor [2.0]\n"
|
|
" -l --logical-blksize Logical block size\n"
|
|
" -p --physical-blksize Physical block size\n"
|
|
" -s --size Logical size\n"
|
|
" -z --compress-alg Compression algorithm [lz4]\n"
|
|
" -Z --compress-level Compression level [1]\n"
|
|
" --detect-zeros Detect and free zero blocks at runtime\n"
|
|
" --profile Set -p -l -z -Z automatically\n"
|
|
" --full-init Fully init device (no lazy init)\n"
|
|
" Note:\n"
|
|
" -c and -s are different ways of specifying the compressed device size.\n"
|
|
" Only one may be used, not both.\n"
|
|
" Profiles:\n"
|
|
" performance: 1k pblk, 4k lblk, lz4 level 1\n"
|
|
" mixed: 4k pblk, 64k lblk, lz4 level 3\n"
|
|
" read-mostly: 4k pblk, 64k lblk, zlib level 6\n"
|
|
" archive: 4k pblk, 256k lblk, zlib level 9\n"
|
|
"\n"
|
|
" open [opts] <device> <name> Open an existing compressed device\n"
|
|
" -c --cache-pages Set cache pages\n"
|
|
" -s --sync Open in synchronous mode\n"
|
|
" create [opts] <device> <name> Alias for open\n"
|
|
" close [opts] <name> Close an opened compressed device\n"
|
|
" check [opts] <device> Check and repair a compressed device\n"
|
|
" -f --force Force check even if device seems clean\n"
|
|
" -F --full-check Do a full check, including verifying data\n"
|
|
" -n --assume-no Assume \"no\" no all questions\n"
|
|
" -y --assume-yes Assume \"yes\" to all questions\n"
|
|
" resize [opts] <device> Resize an existing compressed device\n"
|
|
" -s --size New logical size [use all]\n"
|
|
" stats [opts] <name> Show device statistics\n"
|
|
"\n");
|
|
exit(1);
|
|
}
|
|
|
|
static int
|
|
do_format(int argc, char** argv)
|
|
{
|
|
static const char short_opts[] = "P:S:c:l:p:s:z:Z:";
|
|
static const struct option long_opts[] = {
|
|
{ "pbat-size", required_argument, NULL, 'P' },
|
|
{ "physical-size", required_argument, NULL, 'S' },
|
|
{ "compress-factor", required_argument, NULL, 'c' },
|
|
{ "logical-blksize", required_argument, NULL, 'l' },
|
|
{ "physical-blksize", required_argument, NULL, 'p' },
|
|
{ "size", required_argument, NULL, 's' },
|
|
{ "compress-alg", required_argument, NULL, 'z' },
|
|
{ "compress-level", required_argument, NULL, 'Z' },
|
|
{ "detect-zeros", no_argument, NULL, 0x1 },
|
|
{ "profile", required_argument, NULL, 0x2 },
|
|
{ "full-init", no_argument, NULL, 0x3 },
|
|
{ NULL, no_argument, NULL, 0 }
|
|
};
|
|
char opt;
|
|
uint64_t optval;
|
|
uint64_t psize = 0;
|
|
uint64_t lsize = 0;
|
|
uint pblksize = PAGE_SIZE;
|
|
uint lblksize = 16 * PAGE_SIZE;
|
|
uint pbatsize = 1;
|
|
uint16_t flags = 0;
|
|
enum cbd_alg alg = CBD_ALG_LZ4;
|
|
uint level = 1;
|
|
bool full_init = false;
|
|
|
|
uint8_t pshift;
|
|
uint8_t lshift;
|
|
uint8_t pbatshift;
|
|
|
|
const char* dev;
|
|
|
|
while ((opt = getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) {
|
|
switch (opt) {
|
|
case 'P':
|
|
if (!parse_numeric_arg(optarg, &optval)) {
|
|
error("Failed to parse \"%s\"\n", optarg);
|
|
}
|
|
if (optval < 1) {
|
|
error("Size \"%s\" is not a valid pbat len\n", optarg);
|
|
}
|
|
pbatsize = optval;
|
|
break;
|
|
case 'S':
|
|
if (!parse_numeric_arg(optarg, &optval)) {
|
|
error("Failed to parse \"%s\"\n", optarg);
|
|
}
|
|
psize = optval;
|
|
break;
|
|
case 'c':
|
|
error("Implement me!\n");
|
|
break;
|
|
case 'l':
|
|
if (!parse_numeric_arg(optarg, &optval)) {
|
|
error("Failed to parse \"%s\"\n", optarg);
|
|
}
|
|
if (optval & (optval - 1)) {
|
|
error("Size \"%s\" is not a valid block size\n", optarg);
|
|
}
|
|
lblksize = optval;
|
|
break;
|
|
case 'p':
|
|
if (!parse_numeric_arg(optarg, &optval)) {
|
|
error("Failed to parse \"%s\"\n", optarg);
|
|
}
|
|
if (optval & (optval - 1)) {
|
|
error("Size \"%s\" is not a valid block size\n", optarg);
|
|
}
|
|
pblksize = optval;
|
|
break;
|
|
case 's':
|
|
if (!parse_numeric_arg(optarg, &optval)) {
|
|
error("Failed to parse \"%s\"\n", optarg);
|
|
}
|
|
lsize = optval;
|
|
break;
|
|
case 'z':
|
|
alg = CBD_ALG_NONE;
|
|
if (!strcmp(optarg, "lz4")) {
|
|
alg = CBD_ALG_LZ4;
|
|
}
|
|
if (!strcmp(optarg, "zlib")) {
|
|
alg = CBD_ALG_ZLIB;
|
|
}
|
|
if (alg == CBD_ALG_NONE) {
|
|
error("Invalid compression algorithm \"%s\"\n", optarg);
|
|
}
|
|
break;
|
|
case 'Z':
|
|
if (!parse_numeric_arg(optarg, &optval)) {
|
|
error("Failed to parse \"%s\"\n", optarg);
|
|
}
|
|
if (optval < 1 || optval > 9) {
|
|
error("Compression level \"%s\" out of bounds\n", optarg);
|
|
}
|
|
level = optval;
|
|
break;
|
|
case 0x1:
|
|
flags |= CBD_FLAG_DETECT_ZEROS;
|
|
break;
|
|
case 0x2:
|
|
if (!parse_profile(optarg, &pblksize, &lblksize, &alg, &level)) {
|
|
error("Invalid profile \"%s\"\n", optarg);
|
|
}
|
|
break;
|
|
case 0x3:
|
|
full_init = true;
|
|
break;
|
|
default:
|
|
usage();
|
|
}
|
|
}
|
|
pshift = get_shift(pblksize, SECTOR_SIZE);
|
|
if (pshift < PBLK_SHIFT_MIN || pshift > PBLK_SHIFT_MAX) {
|
|
error("Invalid physical block size %u\n", pblksize);
|
|
}
|
|
lshift = get_shift(lblksize, pblksize);
|
|
if (lshift < LBLK_SHIFT_MIN || lshift > LBLK_SHIFT_MAX) {
|
|
error("Invalid logical block size %u\n", lblksize);
|
|
}
|
|
pbatshift = get_shift(pbatsize, 1);
|
|
if (pbatshift < PBAT_SHIFT_MIN || pbatshift > PBAT_SHIFT_MAX) {
|
|
error("Invalid pbat len %u\n", pbatsize);
|
|
}
|
|
if (argc - optind != 1) {
|
|
usage();
|
|
}
|
|
dev = argv[optind++];
|
|
|
|
cbd_format(dev, flags, alg, level,
|
|
pshift, lshift, pbatshift, psize, lsize,
|
|
full_init);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
do_open(int argc, char** argv)
|
|
{
|
|
static const char short_opts[] = "c:s";
|
|
static const struct option long_opts[] = {
|
|
{ "cache-pages", required_argument, NULL, 'c' },
|
|
{ "sync", no_argument, NULL, 's' },
|
|
{ NULL, no_argument, NULL, 0 }
|
|
};
|
|
char opt;
|
|
uint64_t optval;
|
|
uint64_t cache_pages = 0;
|
|
bool sync = false;
|
|
char dev[PATH_MAX];
|
|
const char* name;
|
|
|
|
while ((opt = getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) {
|
|
switch (opt) {
|
|
case 'c':
|
|
if (!parse_numeric_arg(optarg, &optval)) {
|
|
error("Failed to parse \"%s\"\n", optarg);
|
|
}
|
|
if (optval < 1) {
|
|
error("Size \"%s\" is not a valid cache size\n", optarg);
|
|
}
|
|
cache_pages = optval;
|
|
break;
|
|
case 's':
|
|
sync = true;
|
|
break;
|
|
default:
|
|
usage();
|
|
}
|
|
}
|
|
if (argc - optind != 2) {
|
|
usage();
|
|
}
|
|
strcpy(dev, argv[optind]);
|
|
if (dev[0] != '/') {
|
|
sprintf(dev, "/dev/mapper/%s", argv[optind]);
|
|
}
|
|
++optind;
|
|
name = argv[optind++];
|
|
|
|
cbd_open(dev, name, cache_pages, sync);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
do_close(int argc, char** argv)
|
|
{
|
|
const char* dev;
|
|
|
|
if (argc != 2) {
|
|
usage();
|
|
}
|
|
dev = argv[1];
|
|
|
|
cbd_close(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
do_stats(int argc, char** argv)
|
|
{
|
|
const char* name;
|
|
struct cbd_stats stats;
|
|
|
|
if (argc != 2) {
|
|
usage();
|
|
}
|
|
name = argv[1];
|
|
|
|
cbd_stats(name, &stats);
|
|
printf("Stats:\n");
|
|
printf("pblk used: %lu\n", (unsigned long)stats.pblk_used);
|
|
printf("lblk used: %lu\n", (unsigned long)stats.lblk_used);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
do_check(int argc, char** argv)
|
|
{
|
|
static const char short_opts[] = "fFny";
|
|
static const struct option long_opts[] = {
|
|
{ "force", no_argument, NULL, 'f' },
|
|
{ "full-check", no_argument, NULL, 'F' },
|
|
{ "assume-no", no_argument, NULL, 'n' },
|
|
{ "assume-yes", no_argument, NULL, 'y' },
|
|
{ NULL, no_argument, NULL, 0 }
|
|
};
|
|
char opt;
|
|
bool force = false;
|
|
bool full_check = false;
|
|
tristate_t auto_response = t_none;
|
|
|
|
const char* dev;
|
|
|
|
while ((opt = getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) {
|
|
switch (opt) {
|
|
case 'f':
|
|
force = true;
|
|
break;
|
|
case 'F':
|
|
full_check = true;
|
|
break;
|
|
case 'n':
|
|
if (auto_response == t_true) {
|
|
fprintf(stderr, "Cannot specify both -y and -n\n");
|
|
usage();
|
|
}
|
|
auto_response = t_false;
|
|
break;
|
|
case 'y':
|
|
if (auto_response == t_false) {
|
|
fprintf(stderr, "Cannot specify both -y and -n\n");
|
|
usage();
|
|
}
|
|
auto_response = t_true;
|
|
break;
|
|
default:
|
|
usage();
|
|
}
|
|
}
|
|
if (argc - optind != 1) {
|
|
usage();
|
|
}
|
|
dev = argv[optind++];
|
|
|
|
cbd_check(dev, force, auto_response, full_check);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
do_resize(int argc, char** argv)
|
|
{
|
|
static const char short_opts[] = "s:";
|
|
static const struct option long_opts[] = {
|
|
{ "size", required_argument, NULL, 's' },
|
|
{ NULL, no_argument, NULL, 0 }
|
|
};
|
|
char opt;
|
|
uint64_t optval;
|
|
uint64_t lsize = 0;
|
|
|
|
char dev[PATH_MAX];
|
|
|
|
while ((opt = getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) {
|
|
switch (opt) {
|
|
case 's':
|
|
if (!parse_numeric_arg(optarg, &optval)) {
|
|
fprintf(stderr, "Failed to parse \"%s\"\n", optarg);
|
|
usage();
|
|
}
|
|
lsize = optval;
|
|
break;
|
|
default:
|
|
usage();
|
|
}
|
|
}
|
|
if (argc - optind != 1) {
|
|
usage();
|
|
}
|
|
strcpy(dev, argv[optind]);
|
|
if (dev[0] != '/') {
|
|
sprintf(dev, "/dev/mapper/%s", argv[optind]);
|
|
}
|
|
++optind;
|
|
|
|
cbd_resize(dev, lsize);
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct cmd_dispatch
|
|
{
|
|
const char* cmd;
|
|
int (*func)(int, char**);
|
|
};
|
|
|
|
static struct cmd_dispatch dispatch[] =
|
|
{
|
|
{ "format", do_format },
|
|
{ "open", do_open },
|
|
{ "create", do_open },
|
|
{ "close", do_close },
|
|
{ "stats", do_stats },
|
|
{ "check", do_check },
|
|
{ "resize", do_resize },
|
|
};
|
|
|
|
int
|
|
main(int argc, char** argv)
|
|
{
|
|
static const char short_opts[] = "+v";
|
|
static const struct option long_opts[] = {
|
|
{ "verbose", 0, NULL, 'v' },
|
|
{ NULL, 0, NULL, 0 }
|
|
};
|
|
char opt;
|
|
|
|
const char* cmd;
|
|
uint n;
|
|
|
|
progname = argv[0];
|
|
|
|
/* Disable stdout buffering. */
|
|
setbuf(stdout, NULL);
|
|
|
|
while ((opt = getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) {
|
|
switch (opt) {
|
|
case 'v':
|
|
++verbose_level;
|
|
break;
|
|
default:
|
|
usage();
|
|
}
|
|
}
|
|
if (argc - optind < 1) {
|
|
usage();
|
|
}
|
|
cmd = argv[optind];
|
|
|
|
argc -= optind;
|
|
argv += optind;
|
|
optind = 0;
|
|
for (n = 0; n < ARRAY_SIZE(dispatch); ++n) {
|
|
if (!strcmp(cmd, dispatch[n].cmd)) {
|
|
return dispatch[n].func(argc, argv);
|
|
}
|
|
}
|
|
|
|
fprintf(stderr, "Unrecognized command \"%s\"\n", cmd);
|
|
usage();
|
|
/* NOTREACHED */
|
|
}
|