/* */ #include #include #include /* * 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; } static bool parse_boolean_arg(const char* arg, uint64_t* val) { if (!strcmp(arg, "1") || !strcasecmp(arg, "true") || !strcasecmp(arg, "on") || !strcasecmp(arg, "yes")) { *val = 1; return true; } else if (!strcmp(arg, "0") || !strcasecmp(arg, "false") || !strcasecmp(arg, "off") || !strcasecmp(arg, "no")) { *val = 0; return true; } return false; } 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] [options] \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] 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] Open an existing compressed device\n" " -c --cache-pages Set cache pages\n" " -s --sync Open in synchronous mode\n" " create [opts] Alias for open\n" " close [opts] Close an opened compressed device\n" " check [opts] 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] Resize an existing compressed device\n" " -s --size New logical size [use all]\n" " stats [opts] 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", optional_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: optval = 1; if (optarg) { if (!parse_boolean_arg(optarg, &optval)) { error("Failed to parse \"%s\"\n", optarg); } } if (optval) { 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 */ }