+/* SPDX-License-Identifier: GPL-2.0+ */
+#include "misma.h"
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <lopsub.h>
+#include <sys/mman.h>
+#include <math.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/ioctl.h>
+
+#include "misma.lsg.h"
+
+enum interval_type {
+ IT_CREATE,
+ IT_TRIM,
+ IT_MAX_AGE,
+ NUM_INTERVAL_TYPES
+};
+
+struct snapshot_config {
+ struct percentage_pair thresholds;
+ unsigned interval[NUM_INTERVAL_TYPES];
+};
+static struct snapshot_config global_config = {
+ .thresholds = {.data = 95, .meta = 95},
+ .interval = {
+ [IT_CREATE] = 6 * 3600,
+ [IT_TRIM] = 0,
+ [IT_MAX_AGE] = 86400 * 365
+ }
+};
+
+enum event_type {ET_CREATE, ET_CHECK, ET_TRIM, NUM_EVENT_TYPES};
+
+struct volume_group {
+ char *name;
+ struct snapshot_config config;
+};
+static unsigned num_vgs;
+static struct volume_group *volume_group; /* num_vgs elements */
+
+static const char *vgname(unsigned vgid)
+{
+ return volume_group[vgid].name;
+}
+
+/* sequential search is good enough */
+static unsigned get_vgid(const char *name)
+{
+ for (unsigned n = 0; n < num_vgs; n++)
+ if (!strcmp(name, volume_group[n].name))
+ return n;
+ return ~0U;
+}
+
+/* insert only if it not exists already */
+static unsigned insert_vg(const char *name)
+{
+ struct volume_group *vg;
+ unsigned vgid = get_vgid(name);
+
+ if (vgid != ~0U)
+ return vgid;
+ INFO_LOG("vg #%u: %s\n", num_vgs, name);
+ num_vgs++;
+ volume_group = xrealloc(volume_group, num_vgs
+ * sizeof(struct volume_group));
+ vg = volume_group + num_vgs - 1;
+ memset(vg, 0, sizeof(struct volume_group));
+ vg->name = xstrdup(name);
+ return num_vgs - 1;
+}
+
+struct thin_pool {
+ char *name;
+ unsigned vgid;
+ struct snapshot_config config;
+ struct percentage_pair utilization;
+ enum lvm_scope threshold_scope;
+};
+static unsigned num_pools;
+static struct thin_pool *thin_pool; /* num_pools elements */
+
+static unsigned get_poolid(const char *name, const char *vg_name)
+{
+ for (unsigned n = 0; n < num_pools; n++) {
+ struct thin_pool *pool = thin_pool + n;
+ if (!strcmp(name, pool->name) && !strcmp(vg_name,
+ vgname(pool->vgid)))
+ return n;
+ }
+ return ~0U;
+}
+
+/* vg of pool must have been inserted already */
+static unsigned insert_pool(const char *name, const char *vgname)
+{
+ struct thin_pool *pool;
+ unsigned poolid = get_poolid(name, vgname);
+
+ if (poolid != ~0U)
+ return poolid;
+ INFO_LOG("pool #%u: %s/%s\n", num_pools, vgname, name);
+ num_pools++;
+ thin_pool = xrealloc(thin_pool, num_pools * sizeof(struct thin_pool));
+ pool = thin_pool + num_pools - 1;
+ memset(pool, 0, sizeof(struct thin_pool));
+ pool->name = xstrdup(name);
+ pool->vgid = get_vgid(vgname);
+ if (pool->vgid == ~0U)
+ die("invalid vg: %s", vgname);
+ return num_pools - 1;
+}
+
+struct snapshot {
+ unsigned seq;
+ uint64_t epoch;
+};
+
+struct origin {
+ char *name;
+ unsigned vgid;
+ unsigned poolid;
+ struct snapshot_config config;
+ enum lvm_scope iscope[NUM_INTERVAL_TYPES]; /* interval scopes */
+ uint64_t last_event[NUM_EVENT_TYPES]; /* epochs */
+ unsigned last_seq;
+ unsigned num_slots;
+ struct snapshot *snapshot;
+};
+static unsigned num_origins;
+static struct origin *origin;
+#define FOR_EACH_ORIGIN(_n) for (_n = 0; _n < num_origins; _n++)
+
+static unsigned check_seconds = 60;
+
+static unsigned interval_length(enum interval_type it, const struct origin *o)
+{
+ switch (o->iscope[it]) {
+ case LS_GLOBAL: return global_config.interval[it];
+ case LS_VG: return volume_group[o->vgid].config.interval[it];
+ case LS_POOL: return thin_pool[o->poolid].config.interval[it];
+ case LS_ORIGIN: return o->config.interval[it];
+ default: assert(0);
+ }
+}
+
+static unsigned get_oid(const char *name, const char *vg_name)
+{
+ unsigned n;
+ FOR_EACH_ORIGIN(n) {
+ struct origin *o = origin + n;
+ if (!strcmp(name, o->name) && !strcmp(vg_name, vgname(o->vgid)))
+ return n;
+ }
+ return ~0U;
+}
+
+/* vg must have been inserted already */
+static unsigned insert_origin(const char *name, const char *vgname,
+ const char *poolname)
+{
+ struct origin *o;
+ unsigned oid = get_oid(name, vgname);
+
+ assert(oid == ~0U);
+ INFO_LOG("origin #%u: %s/%s, pool: %s\n", num_origins, vgname, name,
+ poolname);
+ num_origins++;
+ origin = xrealloc(origin, num_origins * sizeof(struct origin));
+ o = origin + num_origins - 1;
+ memset(o, 0, sizeof(struct origin));
+ o->name = xstrdup(name);
+ o->vgid = get_vgid(vgname);
+ assert(o->vgid != ~0U);
+ o->poolid = get_poolid(poolname, vgname);
+ assert(o->poolid != ~0U);
+ return num_origins - 1;
+}
+
+struct event {
+ enum event_type type;
+ uint64_t epoch;
+ struct origin *origin;
+};
+
+static int event_compare(const void *d1, const void *d2)
+{
+ const struct event *a = d1, *b = d2;
+
+ if (a->epoch < b->epoch)
+ return 1;
+ if (a->epoch > b->epoch)
+ return -1;
+ return 0;
+}
+
+static char *config_file;
+
+#define FOR_EACH_SLOT_REVERSE(_j, _o) for ( \
+ unsigned _j = _o->num_slots - 1; _j != -1U; _j--)
+
+static unsigned loglevel_arg_val = LL_WARNING;
+
+/* lopsub */
+static const struct lls_command *subcmd;
+static struct lls_parse_result *lpr, *sublpr;
+#define CMD_PTR(_cname) lls_cmd(LSG_MISMA_CMD_ ## _cname, misma_suite)
+#define OPT_RESULT(_cname, _oname) (lls_opt_result(\
+ LSG_MISMA_ ## _cname ## _OPT_ ## _oname, \
+ (CMD_PTR(_cname) == CMD_PTR(MISMA))? lpr : sublpr))
+#define OPT_GIVEN(_cname, _oname) (lls_opt_given(OPT_RESULT(_cname, _oname)))
+#define OPT_UINT32_VAL(_cname, _oname) (lls_uint32_val(0, \
+ OPT_RESULT(_cname, _oname)))
+#define OPT_STRING_VAL_N(_n, _cname, _oname) (lls_string_val(_n, \
+ OPT_RESULT(_cname, _oname)))
+#define OPT_STRING_VAL(_cname, _oname) (OPT_STRING_VAL_N(0, _cname, _oname))
+
+struct misma_user_data {bool (*handler)(void);};
+#define EXPORT_CMD_HANDLER(_cmd) const struct misma_user_data \
+ lsg_misma_com_ ## _cmd ## _user_data = { \
+ .handler = com_ ## _cmd \
+ };
+
+/* does not allocate memory */
+void misma_log(int ll, const char* fmt,...)
+{
+ va_list argp;
+ time_t t1;
+ struct tm *tm;
+ char str[255] = "";
+
+ if (ll < loglevel_arg_val)
+ return;
+ if (subcmd == CMD_PTR(RUN)) {
+ time(&t1);
+ tm = localtime(&t1);
+ strftime(str, sizeof(str), "%b %d %H:%M:%S", tm);
+ fprintf(stderr, "%s ", str);
+ }
+ va_start(argp, fmt);
+ vfprintf(stderr, fmt, argp);
+ va_end(argp);
+}
+static const char *exit_hook;
+
+__attribute__ ((noreturn))
+static void run_exit_hook_and_die(const char *str)
+{
+ char *arg;
+ char *argv[] = {"/bin/sh", "-c", NULL, NULL};
+ const char *tmp;
+
+ if (exit_hook) {
+ /*
+ * Prevent helpers from calling us again via die() or
+ * die_errno(), which would result in a crash due to an endless
+ * call stack.
+ */
+ tmp = exit_hook;
+ exit_hook = NULL;
+ arg = msg("%s '%s'", tmp, str);
+ argv[2] = arg;
+ xexec(argv, NULL);
+ }
+ exit(EXIT_FAILURE);
+}
+
+void die(const char *fmt, ...)
+{
+ char *str;
+ va_list argp;
+ int ret;
+
+ va_start(argp, fmt);
+ ret = vasprintf(&str, fmt, argp);
+ va_end(argp);
+ if (ret < 0) { /* give up */
+ EMERG_LOG("OOM\n");
+ exit(EXIT_FAILURE);
+ }
+ misma_log(LL_EMERG, "%s\n", str);
+ run_exit_hook_and_die(str);
+}
+
+void die_errno(const char *fmt, ...)
+{
+ char *str;
+ va_list argp;
+ int ret, save_errno = errno;
+
+ va_start(argp, fmt);
+ ret = vasprintf(&str, fmt, argp);
+ va_end(argp);
+ if (ret < 0) {
+ EMERG_LOG("OOM\n");
+ exit(EXIT_FAILURE);
+ }
+ misma_log(LL_EMERG, "%s: %s\n", str, strerror(save_errno));
+ run_exit_hook_and_die(str);
+}
+
+__attribute__ ((const))
+static uint32_t ffz(uint32_t v)
+{
+ uint32_t ret = 0;
+
+ assert(v != (uint32_t)-1);
+ if ((v & 0xffff) == 0xffff) {
+ ret += 16;
+ v >>= 16;
+ }
+ if ((v & 0xff) == 0xff) {
+ ret += 8;
+ v >>= 8;
+ }
+ if ((v & 0xf) == 0xf) {
+ ret += 4;
+ v >>= 4;
+ }
+ if ((v & 0x3) == 0x3) {
+ ret += 2;
+ v >>= 2;
+ }
+ if ((v & 0x1) == 0x1)
+ ret += 1;
+ return ret;
+}
+
+static bool slot_is_used(unsigned slot, const struct origin *o)
+{
+ return o->snapshot[slot].seq != 0;
+}
+
+static void mark_slot_unused(unsigned slot, struct origin *o)
+{
+ o->snapshot[slot].seq = 0;
+}
+
+/* Use highest numbered unused slot, or default if all slots are used. */
+static unsigned get_slot(unsigned seq, const struct origin *o)
+{
+ unsigned mod;
+ FOR_EACH_SLOT_REVERSE(sl, o)
+ if (!slot_is_used(sl, o))
+ return sl;
+ /* all slots used */
+ mod = (1 << o->num_slots) - 1;
+ return ffz(seq % mod);
+}
+
+/*
+ * We specify --autobackup n to avoid filling up /etc/lvm/archive with tons of
+ * useless backup configurations.
+ */
+static bool remove_snapshot(unsigned sl, struct origin *o, bool dry_run)
+{
+ struct snapshot *snap = o->snapshot + sl;
+ bool success;
+ char *arg = msg("%s/misma-%s.%u", vgname(o->vgid), o->name, snap->seq);
+ char *argv[] = {
+ "lvremove",
+ "--yes",
+ "--quiet",
+ "--quiet",
+ "--autobackup",
+ "n",
+ arg,
+ NULL
+ };
+ if (dry_run) {
+ printf("dry-run: would remove snapshot %s\n", arg);
+ free(arg);
+ return true;
+ }
+ NOTICE_LOG("removing snapshot %s\n", arg);
+ success = xexec(argv, NULL);
+ free(arg);
+ if (success)
+ mark_slot_unused(sl, o);
+ return success;
+}
+
+static int slot_compare(const void *a, const void *b, void *data)
+{
+ const struct snapshot *s1 = a, *s2 = b;
+ struct origin *o = data;
+
+ if (!slot_is_used(s1 - o->snapshot, o))
+ return -1;
+ if (!slot_is_used(s2 - o->snapshot, o))
+ return 1;
+ if (s1->seq < s2->seq)
+ return 1;
+ if (s1->seq > s2->seq)
+ return -1;
+ return 0;
+}
+
+static void sort_slots(struct origin *o)
+{
+ qsort_r(o->snapshot, o->num_slots, sizeof(struct snapshot),
+ slot_compare, o);
+}
+
+/*
+ * sleazy (adj.): 1640s, "downy, fuzzy," later "flimsy, unsubstantial" (1660s).
+ *
+ * A sleazy snapshot is one whose distance (with respect to creation time) to
+ * its sibling snapshots is minimal.
+ */
+static bool remove_sleazy_snapshot(struct origin *o, bool dry_run)
+{
+ unsigned sl, victim = 0;
+ uint64_t score = 0;
+ bool have_victim = false;
+ struct snapshot *prev = NULL, *next = NULL;
+
+ sort_slots(o);
+ for (sl = 0; sl < o->num_slots; sl++)
+ if (slot_is_used(sl, o))
+ break;
+ for (; sl < o->num_slots; prev = o->snapshot + sl, sl++) {
+ uint64_t dist;
+ struct snapshot *s = o->snapshot + sl;
+
+ assert(slot_is_used(sl, o));
+ next = sl == o->num_slots - 1? NULL : s + 1;
+ if (!prev && !next)
+ dist = 1;
+ else if (!prev)
+ dist = 10 * (s->epoch - next->epoch);
+ else if (!next)
+ dist = 10 * (prev->epoch - s->epoch);
+ else
+ dist = prev->epoch - next->epoch;
+ DEBUG_LOG("seq %u, slot %u, epoch %" PRIu64 ", score %" PRIu64"\n",
+ s->seq, sl, s->epoch, dist);
+ if (!have_victim || dist < score) {
+ have_victim = true;
+ victim = sl;
+ score = dist;
+ }
+ }
+ if (!have_victim) {
+ INFO_LOG("no snapshots\n");
+ return false;
+ }
+ NOTICE_LOG("victim: seq %u, slot %u, score %" PRIu64 "\n",
+ o->snapshot[victim].seq, victim, score);
+ if (!remove_snapshot(victim, o, dry_run))
+ return false;
+ sort_slots(o);
+ return true;
+}
+
+static void set_interval(enum interval_type it, const struct time_arg *ta)
+{
+ enum lvm_scope scope = ta->lvmspec.scope;
+ unsigned vgid, poolid, oid, n;
+
+ if (scope == LS_GLOBAL) {
+ NOTICE_LOG("default interval #%u: %u seconds\n", it,
+ ta->seconds);
+ global_config.interval[it] = ta->seconds;
+ return;
+ }
+ vgid = get_vgid(ta->lvmspec.vg);
+ if (vgid == ~0U)
+ die("invalid vg in lvmspec: %s", ta->lvmspec.vg);
+ switch (scope) {
+ case LS_VG:
+ volume_group[vgid].config.interval[it] = ta->seconds;
+ break;
+ case LS_POOL:
+ poolid = get_poolid(ta->lvmspec.pool, vgname(vgid));
+ if (poolid == ~0U)
+ die("invalid pool in lvmspec: %s", ta->lvmspec.pool);
+ thin_pool[poolid].config.interval[it] = ta->seconds;
+ break;
+ case LS_ORIGIN:
+ oid = get_oid(ta->lvmspec.tlv, vgname(vgid));
+ if (oid == ~0U)
+ die("invalid tlv in lvmspec: %s", ta->lvmspec.tlv);
+ origin[oid].config.interval[it] = ta->seconds;
+ break;
+ default:
+ assert(0);
+ }
+ /*
+ * Narrow the scope of all matching origins for which it is currently
+ * set to a wider scope.
+ */
+ FOR_EACH_ORIGIN(n) {
+ struct origin *o = origin + n;
+ if (o->iscope[it] >= scope)
+ continue; /* already set to more narrow scope */
+ switch (scope) {
+ case LS_ORIGIN:
+ if (n != oid)
+ continue;
+ break;
+ case LS_POOL:
+ if (poolid != o->poolid || vgid != o->vgid)
+ continue;
+ break;
+ case LS_VG:
+ if (vgid != o->vgid)
+ continue;
+ break;
+ default:
+ assert(0);
+ }
+ NOTICE_LOG("interval #%u for %s/%s: %u seconds\n", it,
+ vgname(o->vgid), o->name, ta->seconds);
+ o->iscope[it] = scope;
+ }
+}
+
+struct lv_info {
+ char *vg, *lv, *pool, *origin;
+ uint64_t time;
+};
+
+static void free_lv_info(struct lv_info *lv)
+{
+ free(lv->vg);
+ free(lv->lv);
+ free(lv->pool);
+ free(lv->origin);
+}
+
+static void parse_lvs_line(const char *line, struct lv_info *result)
+{
+ char *tmp = xstrdup(line), *p = tmp + 2, *comma;
+
+ comma = strchr(p, ',');
+ assert(comma && comma != p);
+ *comma = '\0';
+ result->vg = xstrdup(p);
+ p = comma + 1;
+ comma = strchr(p, ',');
+ assert(comma);
+ *comma = '\0';
+ result->lv = xstrdup(p);
+ p = comma + 1;
+ comma = strchr(p, ',');
+ assert(comma);
+ *comma = '\0';
+ result->pool = xstrdup(p);
+ p = comma + 1;
+ comma = strchr(p, ',');
+ assert(comma);
+ *comma = '\0';
+ result->origin = xstrdup(p);
+ p = comma + 1;
+ assert(sscanf(p, "%" PRIu64, &result->time) == 1);
+ free(tmp);
+}
+
+static void init_origins(void)
+{
+ unsigned n, oid;
+ char *argv[] = {
+ "lvs",
+ "--select", NULL,
+ "--noheading",
+ "--separator", ",",
+ "--readonly",
+ "--unquoted",
+ "-o", "vgname,lvname,pool_lv,origin,lvtime",
+ "-O", "-lv_time",
+ "--config", "report/time_format=%s",
+ NULL
+ };
+ char *buf, *tmp, *line, *select_string = NULL;
+ struct line_iter liter;
+ struct lv_info lv;
+
+ if (OPT_GIVEN(MISMA, ORIGIN) == 0)
+ die("--origin not given");
+
+ /* create argument to --select */
+ for (n = 0; n < OPT_GIVEN(MISMA, ORIGIN); n++) {
+ char *tmp2, *slash;
+ const char *arg = OPT_STRING_VAL_N(n, MISMA, ORIGIN);
+
+ tmp = xstrdup(arg),
+ slash = strchr(tmp, '/');
+ if (!slash || slash == tmp || !slash[1])
+ die("--origin arg must be of the form vg/tlv");
+ *slash = '\0';
+ tmp2 = msg("%s%s (vg_name=%s && (lv_name=%s ||"
+ "(origin=%s && lv_name =~ misma-%s.[0-9]+)))",
+ select_string? select_string : "",
+ select_string? " || " : "" ,
+ tmp, slash + 1, slash + 1, slash + 1
+ );
+ free(tmp);
+ free(select_string);
+ select_string = tmp2;
+ }
+ argv[2] = select_string;
+ if (!xexec(argv, &buf))
+ die("lvs failure");
+ tmp = xstrdup(buf);
+ line_iter_init(&liter, tmp);
+ /* insert vgs and pools */
+ while ((line = line_iter_get(&liter))) {
+ parse_lvs_line(line, &lv);
+ DEBUG_LOG("vg: %s, lv: %s, pool: %s, origin: %s, "
+ "time: %" PRIu64"\n",
+ lv.vg, lv.lv, lv.pool, lv.origin, lv.time);
+ if (lv.origin[0] == '\0') { /* origin */
+ insert_vg(lv.vg);
+ if (lv.pool[0] == '\0')
+ die("%s/%s is no thin LV", lv.vg, lv.lv);
+ insert_pool(lv.pool, lv.vg);
+ }
+ free_lv_info(&lv);
+ }
+ free(tmp);
+ tmp = xstrdup(buf);
+ line_iter_init(&liter, tmp);
+ /* insert origins */
+ while ((line = line_iter_get(&liter))) {
+ parse_lvs_line(line, &lv);
+ if (lv.origin[0] == '\0')
+ insert_origin(lv.lv, lv.vg, lv.pool);
+ free_lv_info(&lv);
+ }
+ free(tmp);
+ /* check that all given origins exist */
+ for (n = 0; n < OPT_GIVEN(MISMA, ORIGIN); n++) {
+ const char *arg = OPT_STRING_VAL_N(n, MISMA, ORIGIN);
+ char *slash;
+
+ tmp = xstrdup(arg),
+ slash = strchr(tmp, '/');
+ *slash = '\0';
+ oid = get_oid(slash + 1, tmp);
+ free(tmp);
+ if (oid == ~0U)
+ die("origin %s does not exist", arg);
+ }
+ tmp = xstrdup(buf);
+ line_iter_init(&liter, tmp);
+ /* allocate and init snapshot arrays */
+ while ((line = line_iter_get(&liter))) {
+ char *fmt;
+ struct snapshot *s;
+ struct origin *o;
+
+ parse_lvs_line(line, &lv);
+ if (lv.origin[0] == '\0') { /* no snapshot */
+ free_lv_info(&lv);
+ continue;
+ }
+ oid = get_oid(lv.origin, lv.vg);
+ assert(oid != ~0U);
+ o = origin + oid;
+ o->num_slots++;
+ o->snapshot = xrealloc(o->snapshot, o->num_slots
+ * sizeof(struct snapshot));
+ s = o->snapshot + o->num_slots - 1;
+ fmt = msg("misma-%s.%%u", lv.origin);
+ if (sscanf(lv.lv, fmt, &s->seq) != 1)
+ die("parse error: %s", lv.lv);
+ free(fmt);
+ s->epoch = lv.time;
+ if (s->seq > o->last_seq)
+ o->last_seq = s->seq;
+ if (s->epoch > o->last_event[ET_CREATE])
+ o->last_event[ET_CREATE] = s->epoch;
+ free_lv_info(&lv);
+ }
+ free(tmp);
+}
+
+static void die_lopsub(int lopsub_ret, char **errctx)
+{
+ const char *m = lls_strerror(-lopsub_ret);
+ if (*errctx)
+ ERROR_LOG("%s: %s\n", *errctx, m);
+ else
+ ERROR_LOG("%s\n", m);
+ free(*errctx);
+ *errctx = NULL;
+ die("lopsub error");
+}
+
+static void parse_options(int argc, char **argv, const struct lls_command *cmd,
+ struct lls_parse_result **lprp)
+{
+ int ret, fd = -1;
+ struct stat statbuf;
+ void *map;
+ size_t sz;
+ int cf_argc;
+ char **cf_argv, *errctx = NULL;
+ const char *subcmd_name;
+ struct lls_parse_result *merged_lpr, *cf_lpr;
+
+ ret = lls_parse(argc, argv, cmd, lprp, &errctx);
+ if (ret < 0)
+ die_lopsub(ret, &errctx);
+ if (!config_file) {
+ if (OPT_GIVEN(MISMA, CONFIG_FILE))
+ config_file = xstrdup(OPT_STRING_VAL(MISMA,
+ CONFIG_FILE));
+ else {
+ const char *home = getenv("HOME");
+ if (!home || !*home)
+ die("fatal: HOME is unset or empty");
+ config_file = msg("%s/.mismarc", home);
+ }
+ }
+ ret = open(config_file, O_RDONLY);
+ if (ret < 0) {
+ if (errno != ENOENT || OPT_GIVEN(MISMA, CONFIG_FILE))
+ die_errno("can not open config file %s", config_file);
+ /* no config file -- nothing to do */
+ ret = 0;
+ goto success;
+ }
+ fd = ret;
+ ret = fstat(fd, &statbuf);
+ if (ret < 0)
+ die_errno("failed to stat config file %s", config_file);
+ sz = statbuf.st_size;
+ if (sz == 0) { /* config file is empty -- nothing to do */
+ ret = 0;
+ goto success;
+ }
+ map = mmap(NULL, sz, PROT_READ, MAP_PRIVATE, fd, 0);
+ if (map == MAP_FAILED)
+ die_errno("failed to mmap config file %s", config_file);
+ subcmd_name = (cmd == CMD_PTR(MISMA))? NULL : lls_command_name(cmd);
+ ret = lls_convert_config(map, sz, subcmd_name, &cf_argv,
+ &errctx);
+ munmap(map, sz);
+ if (ret < 0) {
+ ERROR_LOG("failed to convert config file %s\n", config_file);
+ die_lopsub(ret, &errctx);
+ }
+ cf_argc = ret;
+ ret = lls_parse(cf_argc, cf_argv, cmd, &cf_lpr, &errctx);
+ lls_free_argv(cf_argv);
+ if (ret < 0)
+ die_lopsub(ret, &errctx);
+ /* command line options override config file options */
+ ret = lls_merge(*lprp, cf_lpr, cmd, &merged_lpr, &errctx);
+ if (ret < 0)
+ die_lopsub(ret, &errctx);
+ lls_free_parse_result(cf_lpr, cmd);
+ lls_free_parse_result(*lprp, cmd);
+ *lprp = merged_lpr;
+success:
+ if (fd >= 0)
+ close(fd);
+}
+
+static void get_utilization(void)
+{
+ char *select_string = NULL, *buf, *line;
+ unsigned n;
+ char *argv[] = {
+ "lvs",
+ "--select", NULL,
+ "--noheading", "--unquoted",
+ "-o", "vgname,lvname,data_percent,metadata_percent",
+ NULL
+ };
+ struct line_iter liter;
+
+ for (n = 0; n < num_pools; n++) {
+ const struct thin_pool *pool = thin_pool + n;
+ char *tmp = msg("%s%s (vg_name = %s && lv_name = %s)",
+ (n == 0)? "" : select_string, (n == 0)? "" : "||",
+ vgname(pool->vgid), pool->name);
+ free(select_string);
+ select_string = tmp;
+ }
+ argv[2] = select_string;
+ if (!xexec(argv, &buf))
+ die("lvs failure");
+ free(select_string);
+ line_iter_init(&liter, buf);
+ while ((line = line_iter_get(&liter))) {
+ struct percentage_pair *u;
+ struct thin_pool *pool;
+ unsigned poolid;
+ float data, meta;
+ size_t len = strlen(line);
+ char *vg = xmalloc(len), *lv = xmalloc(len);
+ if (sscanf(line, "%s %s %f %f", vg, lv, &data, &meta) != 4)
+ die("cannot parse lvs line: %s", line);
+ poolid = get_poolid(lv, vg);
+ free(vg);
+ free(lv);
+ assert(poolid != ~0U);
+ pool = thin_pool + poolid;
+ u = &pool->utilization;
+ u->data = data + 0.5;
+ u->meta = meta + 0.5;
+ INFO_LOG("pool %s/%s utilization: %u/%u\n",
+ vgname(pool->vgid), pool->name, u->data, u->meta);
+ }
+ free(buf);
+}
+
+static bool pool_is_full(const struct thin_pool *pool)
+{
+ bool ret;
+ struct percentage_pair t, u = pool->utilization;
+
+ if (pool->threshold_scope == LS_GLOBAL)
+ t = global_config.thresholds;
+ else if (pool->threshold_scope == LS_VG)
+ t = volume_group[pool->vgid].config.thresholds;
+ else
+ t = pool->config.thresholds;
+ ret = u.data > t.data || u.meta > t.meta;
+ if (ret) {
+ NOTICE_LOG("pool %s/%s utilization: %u/%u, threshold: %u/%u\n",
+ vgname(pool->vgid), pool->name,
+ u.data, u.meta, t.data, t.meta);
+ WARNING_LOG("pool %s/%s exceeds utilization thresholds\n",
+ vgname(pool->vgid), pool->name);
+ }
+ return ret;
+}
+
+static void check_utilization(void)
+{
+ bool found_full_pool, removed_snapshot;
+
+again:
+ found_full_pool = false;
+ removed_snapshot = false;
+ get_utilization();
+ for (unsigned n = 0; n < num_pools; n++) {
+ unsigned m;
+ const struct thin_pool *pool = thin_pool + n;
+ if (!pool_is_full(pool))
+ continue;
+ found_full_pool = true;
+ FOR_EACH_ORIGIN(m) {
+ struct origin *o = origin + m;
+ if (o->poolid != n)
+ continue;
+ if (remove_sleazy_snapshot(o, false))
+ removed_snapshot = true;
+ }
+ }
+ if (!found_full_pool)
+ return;
+ if (removed_snapshot)
+ goto again;
+ INFO_LOG("full pool found, but nothing to remove\n");
+}
+
+static bool create_snapshot(struct origin *o, bool dry_run)
+{
+ unsigned seq = o->last_seq + 1;
+ char *name = msg("misma-%s.%u", o->name, seq);
+ char *vg_origin = msg("%s/%s", vgname(o->vgid), o->name);
+ char *argv[] = {
+ "lvcreate",
+ "--type",
+ "thin",
+ "--quiet",
+ "--quiet",
+ "-s",
+ "--autobackup",
+ "n",
+ "-n",
+ name,
+ vg_origin,
+ NULL
+ };
+ if (dry_run) {
+ printf("dry-run: would create snapshot #%u of origin %s\n",
+ seq, vg_origin);
+ free(name);
+ free(vg_origin);
+ return true;
+ }
+ NOTICE_LOG("creating snapshot %s/%s\n", vgname(o->vgid), name);
+ if (!xexec(argv, NULL))
+ die("could not create snapshot");
+ free(name);
+ free(vg_origin);
+ return true;
+}
+
+static void signal_handler(int signo)
+{
+ die("caught signal %d, terminating", signo);
+}
+
+#ifndef FITRIM
+struct fstrim_range {uint64_t start; uint64_t len; uint64_t minlen;};
+#define FITRIM _IOWR('X', 121, struct fstrim_range)
+#endif
+static bool trim_filesystem(struct origin *o, bool dry_run)
+{
+ struct stat sb;
+ char *dev;
+ unsigned majo, mino;
+ int fd;
+ char *buf;
+ struct line_iter liter;
+ char *line, *mp = NULL;
+ struct fstrim_range range = {.len = ULLONG_MAX};
+
+ dev = msg("/dev/%s/%s", vgname(o->vgid), o->name);
+ if (stat(dev, &sb) < 0) {
+ WARNING_LOG("stat(%s): %m\n", dev);
+ free(dev);
+ return false;
+ }
+ if ((sb.st_mode & S_IFMT) != S_IFBLK) {
+ WARNING_LOG("not a block device: %s\n", dev);
+ free(dev);
+ return false;
+ }
+ free(dev);
+ majo = major(sb.st_rdev);
+ mino = minor(sb.st_rdev);
+ fd = open("/proc/self/mountinfo", O_RDONLY);
+ if (fd < 0) {
+ WARNING_LOG("open(/proc/self/mountinfo): %m\n");
+ return false;
+ }
+ if (!fd2buf(fd, &buf)) {
+ WARNING_LOG("fd2buf error\n");
+ close(fd);
+ return false;
+ }
+ close(fd);
+ line_iter_init(&liter, buf);
+ /* 13 15 0:5 / /proc */
+ while ((line = line_iter_get(&liter))) {
+ unsigned id, parent, mmajo, mmino;
+ size_t len = strlen(line);
+ char *mountroot = xmalloc(len), *target = xmalloc(len);
+
+ if (sscanf(line, "%u %u %u:%u %s %s", &id, &parent, &mmajo,
+ &mmino, mountroot, target) != 6) {
+ WARNING_LOG("parse mountinfo line: %s\n", line);
+ free(mountroot);
+ free(target);
+ return false;
+ }
+ free(mountroot);
+ if (mmajo == majo && mmino == mino) {
+ mp = target;
+ break;
+ }
+ free(target);
+ }
+ free(buf);
+ if (!mp) {
+ WARNING_LOG("unable to find mountpoint of origin\n");
+ return false;
+ }
+ if (dry_run) {
+ printf("%s\n", mp);
+ free(mp);
+ return true;
+ }
+ fd = open(mp, O_RDONLY);
+ if (fd < 0) {
+ WARNING_LOG("open(%s): %m\n", mp);
+ free(mp);
+ return false;
+ }
+ if (ioctl(fd, FITRIM, &range)) {
+ WARNING_LOG("ioctl(FITRIM, %s): %m\n", mp);
+ close(fd);
+ free(mp);
+ return false;
+ }
+ close(fd);
+ NOTICE_LOG("trimmed %s\n", mp);
+ free(mp);
+ return true;
+}
+
+static void set_threshold(const struct threshold_arg *ta)
+{
+ enum lvm_scope scope = ta->lvmspec.scope;
+ unsigned poolid = 0, vgid;
+
+ if (scope == LS_GLOBAL) {
+ global_config.thresholds = ta->threshold;
+ return;
+ }
+ vgid = get_vgid(ta->lvmspec.vg);
+ if (vgid == ~0U)
+ die("invalid vg in lvmspec: %s", ta->lvmspec.vg);
+ if (scope == LS_VG) {
+ volume_group[vgid].config.thresholds = ta->threshold;
+ } else {
+ assert(scope == LS_POOL);
+ poolid = get_poolid(ta->lvmspec.pool, vgname(vgid));
+ if (poolid == ~0U)
+ die("invalid pool in lvmspec: %s", ta->lvmspec.pool);
+ thin_pool[poolid].config.thresholds = ta->threshold;
+ }
+ /*
+ * Narrow the scope of all matching pools for which it is currently
+ * set to a wider scope.
+ */
+ for (unsigned n = 0; n < num_pools; n++) {
+ struct thin_pool *p = thin_pool + n;
+ if (p->threshold_scope >= scope)
+ continue; /* already set to more narrow scope */
+ if (vgid != p->vgid)
+ continue;
+ if (scope == LS_POOL && poolid != n)
+ continue;
+ NOTICE_LOG("threshold for pool %s/%s: %u/%u\n",
+ vgname(vgid), p->name, ta->threshold.data,
+ ta->threshold.meta);
+ p->threshold_scope = scope;
+ }
+}
+
+static void log_event(const void *d)
+{
+ const struct event *e = d;
+
+ if (e->origin)
+ DEBUG_LOG("(%s,%u): %" PRIu64 "\n", e->origin->name,
+ e->type, e->epoch);
+ else
+ DEBUG_LOG("(utilization): %" PRIu64 "\n", e->epoch);
+}
+
+static unsigned check_run_options(void)
+{
+ struct time_arg ta;
+ const char *arg;
+ unsigned n, num_events = 0;
+
+ for (n = 0; n < OPT_GIVEN(RUN, THRESHOLD); n++) {
+ struct threshold_arg tha;
+ arg = OPT_STRING_VAL_N(n, RUN, THRESHOLD);
+ parse_threshold_arg(arg,"--threshold", &tha);
+ set_threshold(&tha);
+ free_lvmspec(&tha.lvmspec);
+ }
+ if (OPT_GIVEN(RUN, CHECK_INTERVAL)) {
+ arg = OPT_STRING_VAL(RUN, CHECK_INTERVAL);
+ check_seconds = parse_timespec(arg, "check-interval");
+ check_range(check_seconds, 10, 86400, "check-interval");
+ }
+ for (n = 0; n < OPT_GIVEN(RUN, TRIM_INTERVAL); n++) {
+ arg = OPT_STRING_VAL_N(n, RUN, TRIM_INTERVAL);
+ parse_time_arg(arg, "--trim-interval", &ta);
+ if (ta.seconds > 0)
+ check_range(ta.seconds, 60, ~0U, "trim-interval");
+ set_interval(IT_TRIM, &ta);
+ free_lvmspec(&ta.lvmspec);
+ }
+ for (n = 0; n < OPT_GIVEN(RUN, CREATE_INTERVAL); n++) {
+ arg = OPT_STRING_VAL_N(n, RUN, CREATE_INTERVAL);
+ parse_time_arg(arg, "--create-interval", &ta);
+ check_range(ta.seconds, 60, 86400 * 365, "create-interval");
+ set_interval(IT_CREATE, &ta);
+ free_lvmspec(&ta.lvmspec);
+ }
+ for (n = 0; n < OPT_GIVEN(RUN, MAX_AGE); n++) {
+ arg = OPT_STRING_VAL_N(n, RUN, MAX_AGE);
+ parse_time_arg(arg, "--max-age", &ta);
+ check_range(ta.seconds, 86400, 86400 * 20 * 365, "max-age");
+ set_interval(IT_MAX_AGE, &ta);
+ free_lvmspec(&ta.lvmspec);
+ }
+ FOR_EACH_ORIGIN(n) {
+ struct origin *o = origin + n;
+ uint32_t ma, cr, max_slots; /* max age, create interval */
+
+ INFO_LOG("found %u snapshots of origin %s/%s\n",
+ o->num_slots, vgname(o->vgid), o->name);
+ /* set number of slots */
+ ma = interval_length(IT_MAX_AGE, o);
+ cr = interval_length(IT_CREATE, o);
+ if (ma / 3 < cr)
+ die("%s/%s: max-age/create ratio too small",
+ vgname(o->vgid), o->name);
+ max_slots = 1 + ceil(log2((double)ma / cr + 1));
+ assert(max_slots > 2);
+ assert(max_slots < 30);
+ if (o->num_slots > max_slots)
+ die("%s/%s: too many snapshots", vgname(o->vgid),
+ o->name);
+ if (o->num_slots < max_slots) {
+ unsigned diff = max_slots - o->num_slots;
+ o->snapshot = xrealloc(o->snapshot, max_slots
+ * sizeof(struct snapshot));
+ memset(o->snapshot + o->num_slots, 0,
+ diff * sizeof(struct snapshot));
+ o->num_slots = max_slots;
+ }
+ INFO_LOG("%s/%s: using %u slots\n", vgname(o->vgid), o->name,
+ o->num_slots);
+ if (interval_length(IT_TRIM, o) > 0)
+ num_events++;
+ }
+ return num_events + 1 + num_origins;
+}
+
+static void dispatch_create_event(struct origin *o)
+{
+ unsigned seq, sl;
+ const struct thin_pool *pool;
+ uint64_t now;
+
+ pool = thin_pool + o->poolid;
+ if (pool_is_full(pool)) {
+ WARNING_LOG("%s/%s: creation suspended\n", vgname(o->vgid),
+ o->name);
+ return;
+ }
+ seq = o->last_seq + 1, sl = get_slot(seq, o);
+ if (slot_is_used(sl, o) && !remove_snapshot(sl, o, false))
+ die("%s/%s: unable to free slot\n", vgname(o->vgid), o->name);
+ now = time(NULL);
+ create_snapshot(o, false);
+ o->snapshot[sl].seq = seq;
+ o->snapshot[sl].epoch = now;
+ o->last_seq = seq;
+ o->last_event[ET_CREATE] = now;
+}
+
+/* We leak the fd but that's OK as long as we're only called once. */
+static int silence_lvm(void)
+{
+ char *val;
+ int fd = open("/dev/null", O_RDWR);
+
+ if (fd < 0)
+ die_errno("open(/dev/null)");
+ val = msg("%d", fd);
+ setenv("LVM_ERR_FD", val, true /* overwrite */);
+ free(val);
+ return fd;
+}
+
+__attribute__ ((noreturn))
+static bool com_run(void)
+{
+ int fd = -1;
+ unsigned n, num_events;
+ struct event **ep;
+ struct event **event; /* At most 2 * num_origins + 1 */
+ struct heap *event_heap;
+ uint64_t now = time(NULL);
+
+ num_events = check_run_options();
+ event = xmalloc(num_events * sizeof(struct event *));
+ ep = event;
+ (*ep) = xmalloc(sizeof(struct event));
+ (*ep)->type = ET_CHECK;
+ (*ep)->origin = NULL;
+ (*ep)->epoch = 0;
+ log_event(*ep);
+ ep++;
+ FOR_EACH_ORIGIN(n) {
+ struct origin *o = origin + n;
+ (*ep) = xmalloc(sizeof(struct event));
+ (*ep)->type = ET_CREATE;
+ (*ep)->origin = o;
+ (*ep)->epoch = o->last_event[ET_CREATE]
+ + interval_length(IT_CREATE, o);
+ log_event(*ep);
+ ep++;
+ if (interval_length(IT_TRIM, o) == 0)
+ continue;
+ (*ep) = xmalloc(sizeof(struct event));
+ (*ep)->type = ET_TRIM;
+ (*ep)->origin = o;
+ (*ep)->epoch = now + interval_length(IT_TRIM, o);
+ log_event(*ep);
+ ep++;
+ }
+ event_heap = heap_init(&event, num_events, event_compare);
+ if (get_misma_pid(config_file) > 0)
+ die("already running");
+ if (OPT_GIVEN(RUN, DAEMON))
+ fd = daemonize(OPT_STRING_VAL(RUN, LOGFILE));
+ if (!misma_lock(config_file))
+ die("already running");
+ if (signal(SIGINT, &signal_handler) == SIG_ERR)
+ die_errno("signal handler for SIGINT");
+ if (signal(SIGTERM, &signal_handler) == SIG_ERR)
+ die_errno("signal handler for SIGTERM");
+ if (signal(SIGHUP, &signal_handler) == SIG_ERR)
+ die_errno("signal handler for SIGHUP");
+ if (fd >= 0) {
+ if (write(fd, "\0", 1) < 0)
+ die_errno("write");
+ close(fd);
+ }
+ exit_hook = OPT_STRING_VAL(RUN, EXIT_HOOK);
+ if (OPT_GIVEN(RUN, SUPPRESS_LVM_WARNINGS))
+ silence_lvm();
+ for (;;) {
+ struct event *e = heap_min(event_heap);
+ struct origin *o;
+
+ now = time(NULL);
+ if (e->epoch > now) {
+ INFO_LOG("sleeping %" PRIu64 " seconds\n",
+ e->epoch - now);
+ sleep(e->epoch - now);
+ continue;
+ }
+ e = heap_extract_min(event_heap);
+ o = e->origin;
+ switch (e->type) {
+ case ET_CHECK:
+ INFO_LOG("next event: check\n");
+ check_utilization();
+ now = time(NULL);
+ e->epoch = now + check_seconds;
+ break;
+ case ET_TRIM:
+ INFO_LOG("next event: trim %s/%s\n",
+ vgname(o->vgid), o->name);
+ trim_filesystem(o, false /* dry-run */);
+ e->origin->last_event[ET_TRIM] = now;
+ e->epoch = now + interval_length(IT_TRIM, o);
+ break;
+ case ET_CREATE:
+ INFO_LOG("next event: create %s/%s\n", vgname(o->vgid),
+ o->name);
+ dispatch_create_event(o);
+ e->epoch = now + interval_length(IT_CREATE, o);
+ break;
+ default: assert(0);
+ }
+ heap_insert(e, event_heap);
+ heap_dump(event_heap, log_event);
+ sleep(3);
+ }
+}
+EXPORT_CMD_HANDLER(run);
+
+static void seconds_to_human(int64_t diff, char *buf)
+{
+ if (diff > 2 * 86400 * 365)
+ sprintf(buf, "%3" PRId64 " years ", diff / (86400 * 365));
+ else if (diff > 2 * 86400 * 60)
+ sprintf(buf, "%3" PRId64 " months ", diff / (86400 * 60));
+ else if (diff > 2 * 86400 * 7)
+ sprintf(buf, "%3" PRId64 " weeks ", diff / (86400 * 7));
+ else if (diff > 2 * 86400)
+ sprintf(buf, "%3" PRId64 " days ", diff / 86400);
+ else if (diff > 2 * 3600)
+ sprintf(buf, "%3" PRId64 " hours ", diff / 3600);
+ else if (diff > 2 * 60)
+ sprintf(buf, "%3" PRId64 " minutes", diff / 60);
+ else
+ sprintf(buf, "%3" PRId64 " second%s", diff, diff == 1? "" : "s");
+}
+
+static bool origin_matches_lvmspec(const struct origin *o,
+ const struct lvmspec *spec)
+{
+ if (spec->scope == LS_GLOBAL)
+ return true;
+ if (strcmp(spec->vg, vgname(o->vgid)))
+ return false;
+ if (spec->scope == LS_VG)
+ return true;
+ if (spec->scope == LS_ORIGIN)
+ return !strcmp(spec->tlv, o->name);
+ return !strcmp(spec->pool, thin_pool[o->poolid].name);
+}
+
+static bool for_each_matching_origin(bool (*func)(struct origin *, bool),
+ bool dry_run)
+{
+ unsigned k, n, num_args = lls_num_inputs(sublpr);
+ struct lvmspec *spec = NULL; /* STFU gcc-12.3.0 */
+ bool match = false;
+
+ if (num_args > 0)
+ spec = xmalloc(num_args * sizeof(*spec));
+ for (k = 0; k < num_args; k++)
+ parse_lvmspec(lls_input(k, sublpr), "create/rm", spec + k);
+ FOR_EACH_ORIGIN(n) {
+ struct origin *o = origin + n;
+ for (k = 0; k < num_args; k++)
+ if (origin_matches_lvmspec(o, spec + k))
+ break;
+ if (num_args == 0 || k < num_args) {
+ func(o, dry_run);
+ match = true;
+ }
+ }
+ free(spec);
+ if (!match && num_args > 0)
+ printf("no matches\n");
+ return match;
+}
+
+static bool list_snapshots(struct origin *o, bool l_given)
+{
+ if (!l_given)
+ printf("%s/%s:\n", vgname(o->vgid), o->name);
+ FOR_EACH_SLOT_REVERSE(sl, o) {
+ char buf[32];
+ struct tm *tm;
+ struct snapshot *s = o->snapshot + sl;
+ time_t t;
+
+ assert(slot_is_used(sl, o));
+ if (l_given) {
+ printf("/dev/%s/misma-%s.%u\t", vgname(o->vgid),
+ o->name, s->seq);
+ t = s->epoch;
+ tm = localtime(&t);
+ strftime(buf, sizeof(buf), "%F %R", tm);
+ printf("%s", buf);
+ } else
+ printf("%8u ", s->seq);
+ t = time(NULL);
+ seconds_to_human(t - s->epoch, buf);
+ printf(" %s\n", buf);
+ }
+ return true;
+}
+
+static bool com_ls(void)
+{
+ return for_each_matching_origin(list_snapshots,
+ OPT_GIVEN(LS, LONG));
+}
+EXPORT_CMD_HANDLER(ls);
+
+static bool com_create(void)
+{
+ if (!misma_lock(config_file))
+ die("already running");
+ return for_each_matching_origin(create_snapshot,
+ OPT_GIVEN(CREATE, DRY_RUN));
+}
+EXPORT_CMD_HANDLER(create);
+
+static bool com_rm(void)
+{
+ if (!misma_lock(config_file))
+ die("already running");
+ return for_each_matching_origin(remove_sleazy_snapshot,
+ OPT_GIVEN(RM, DRY_RUN));
+}
+EXPORT_CMD_HANDLER(rm);
+
+static bool com_kill(void)
+{
+ pid_t pid;
+ unsigned sig = OPT_UINT32_VAL(KILL, SIGNAL);
+ unsigned ms = 32;
+
+ pid = get_misma_pid(config_file);
+ if (pid == 0)
+ die("no misma run process to send signal to");
+ NOTICE_LOG("sending signal %u to pid %d\n", sig, pid);
+ if (kill(pid, sig) < 0)
+ die_errno("kill");
+ if (!OPT_GIVEN(KILL, WAIT))
+ return true;
+ while (ms < 5000) {
+ struct timespec ts = {
+ .tv_sec = ms / 1000,
+ .tv_nsec = (ms % 1000) * 1000 * 1000
+ };
+ if (nanosleep(&ts, NULL) < 0)
+ return false;
+ if (kill(pid, 0) < 0)
+ return errno == ESRCH;
+ ms *= 2;
+ }
+ return false;
+}
+EXPORT_CMD_HANDLER(kill);
+
+#define LSG_MISMA_CMD(_name) #_name
+static const char * const subcommand_names[] = {LSG_MISMA_SUBCOMMANDS NULL};
+#undef LSG_MISMA_CMD
+
+static void show_subcommand_summary(bool verbose)
+{
+ int i;
+
+ printf("Available subcommands:\n");
+ if (verbose) {
+ const struct lls_command *cmd;
+ for (i = 1; (cmd = lls_cmd(i, misma_suite)); i++) {
+ const char *purpose = lls_purpose(cmd);
+ const char *name = lls_command_name(cmd);
+ printf("%-12s%s\n", name, purpose);
+ }
+ } else {
+ unsigned n = 8;
+ printf("\t");
+ for (i = 0; i < LSG_NUM_MISMA_SUBCOMMANDS; i++) {
+ if (i > 0)
+ n += printf(", ");
+ if (n > 70) {
+ printf("\n\t");
+ n = 8;
+ }
+ n += printf("%s", subcommand_names[i]);
+ }
+ printf("\n");
+ }
+}
+
+static bool com_trim(void)
+{
+ if (!misma_lock(config_file))
+ die("already running");
+ return for_each_matching_origin(trim_filesystem,
+ OPT_GIVEN(TRIM, DRY_RUN));
+}
+EXPORT_CMD_HANDLER(trim);
+
+static bool com_help(void)
+{
+ int ret;
+ char *errctx, *help;
+ const char *arg;
+ const struct lls_command *cmd;
+
+ ret = lls_check_arg_count(sublpr, 0, 1, &errctx);
+ if (ret < 0)
+ die_lopsub(ret, &errctx);
+ if (lls_num_inputs(sublpr) == 0) {
+ show_subcommand_summary(OPT_GIVEN(HELP, LONG));
+ return true;
+ }
+ arg = lls_input(0, sublpr);
+ ret = lls_lookup_subcmd(arg, misma_suite, &errctx);
+ if (ret < 0)
+ die_lopsub(ret, &errctx);
+ cmd = lls_cmd(ret, misma_suite);
+ if (OPT_GIVEN(HELP, LONG))
+ help = lls_long_help(cmd);
+ else
+ help = lls_short_help(cmd);
+ printf("%s\n", help);
+ free(help);
+ return true;
+}
+EXPORT_CMD_HANDLER(help);
+
+static bool com_configtest(void)
+{
+ printf("Syntax Ok\n");
+ return true;
+}
+EXPORT_CMD_HANDLER(configtest);
+
+static bool com_utilization(void)
+{
+ get_utilization();
+ for (unsigned n = 0; n < num_pools; n++) {
+ struct thin_pool *p = thin_pool + n;
+ printf("%s/%s: %u%%/%u%%\n",
+ vgname(p->vgid), p->name, p->utilization.data,
+ p->utilization.meta);
+ }
+ return true;
+}
+EXPORT_CMD_HANDLER(utilization);
+
+const char *GET_VERSION(void);
+static void handle_version_and_help(void)
+{
+ char *help;
+
+ if (OPT_GIVEN(MISMA, VERSION)) {
+ printf(PACKAGE " %s\n"
+ "Copyright (C) " COPYRIGHT_YEAR " " AUTHOR ".\n"
+ "License: " LICENSE ": <" LICENSE_URL ">.\n"
+ "This is free software: you are free to change and redistribute it.\n"
+ "There is NO WARRANTY, to the extent permitted by law.\n"
+ "\n"
+ "Web page: " URL "\n"
+ "Clone URL: " CLONE_URL "\n"
+ "Gitweb: " GITWEB_URL "\n"
+ "Author's Home Page: " HOME_URL "\n"
+ "Send feedback to: " AUTHOR " <" EMAIL ">\n"
+ ,
+ GET_VERSION()
+ );
+ exit(EXIT_SUCCESS);
+ }
+ if (OPT_GIVEN(MISMA, DETAILED_HELP))
+ help = lls_long_help(CMD_PTR(MISMA));
+ else if (OPT_GIVEN(MISMA, HELP))
+ help = lls_short_help(CMD_PTR(MISMA));
+ else
+ return;
+ printf("%s\n", help);
+ free(help);
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char **argv)
+{
+ unsigned num_inputs;
+ int ret;
+ char *errctx;
+ const struct misma_user_data *ud;
+
+ valid_fd012();
+ parse_options(argc, argv, CMD_PTR(MISMA), &lpr);
+ loglevel_arg_val = OPT_UINT32_VAL(MISMA, LOGLEVEL);
+ handle_version_and_help();
+ num_inputs = lls_num_inputs(lpr);
+ if (num_inputs == 0) {
+ show_subcommand_summary(true /* verbose */);
+ exit(EXIT_SUCCESS);
+ }
+ ret = lls_lookup_subcmd(argv[argc - num_inputs], misma_suite, &errctx);
+ if (ret < 0)
+ die_lopsub(ret, &errctx);
+ subcmd = lls_cmd(ret, misma_suite);
+ parse_options(num_inputs, argv + argc - num_inputs, subcmd, &sublpr);
+ if (subcmd != CMD_PTR(HELP))
+ init_origins();
+ ud = lls_user_data(subcmd);
+ exit(ud->handler()? EXIT_SUCCESS : EXIT_FAILURE);
+}