1 /* SPDX-License-Identifier: GPL-2.0+ */
10 #include <sys/sysmacros.h>
11 #include <sys/ioctl.h>
13 #include "misma.lsg.h"
22 struct snapshot_config {
23 struct percentage_pair thresholds;
24 unsigned interval[NUM_INTERVAL_TYPES];
26 static struct snapshot_config global_config = {
27 .thresholds = {.data = 95, .meta = 95},
29 [IT_CREATE] = 6 * 3600,
31 [IT_MAX_AGE] = 86400 * 365
35 enum event_type {ET_CREATE, ET_CHECK, ET_TRIM, NUM_EVENT_TYPES};
39 struct snapshot_config config;
41 static unsigned num_vgs;
42 static struct volume_group *volume_group; /* num_vgs elements */
44 static const char *vgname(unsigned vgid)
46 return volume_group[vgid].name;
49 /* sequential search is good enough */
50 static unsigned get_vgid(const char *name)
52 for (unsigned n = 0; n < num_vgs; n++)
53 if (!strcmp(name, volume_group[n].name))
58 /* insert only if it not exists already */
59 static unsigned insert_vg(const char *name)
61 struct volume_group *vg;
62 unsigned vgid = get_vgid(name);
66 INFO_LOG("vg #%u: %s\n", num_vgs, name);
68 volume_group = xrealloc(volume_group, num_vgs
69 * sizeof(struct volume_group));
70 vg = volume_group + num_vgs - 1;
71 memset(vg, 0, sizeof(struct volume_group));
72 vg->name = xstrdup(name);
79 struct snapshot_config config;
80 struct percentage_pair utilization;
81 enum lvm_scope threshold_scope;
83 static unsigned num_pools;
84 static struct thin_pool *thin_pool; /* num_pools elements */
86 static unsigned get_poolid(const char *name, const char *vg_name)
88 for (unsigned n = 0; n < num_pools; n++) {
89 struct thin_pool *pool = thin_pool + n;
90 if (!strcmp(name, pool->name) && !strcmp(vg_name,
97 /* vg of pool must have been inserted already */
98 static unsigned insert_pool(const char *name, const char *vgname)
100 struct thin_pool *pool;
101 unsigned poolid = get_poolid(name, vgname);
105 INFO_LOG("pool #%u: %s/%s\n", num_pools, vgname, name);
107 thin_pool = xrealloc(thin_pool, num_pools * sizeof(struct thin_pool));
108 pool = thin_pool + num_pools - 1;
109 memset(pool, 0, sizeof(struct thin_pool));
110 pool->name = xstrdup(name);
111 pool->vgid = get_vgid(vgname);
112 if (pool->vgid == ~0U)
113 die("invalid vg: %s", vgname);
114 return num_pools - 1;
126 struct snapshot_config config;
127 enum lvm_scope iscope[NUM_INTERVAL_TYPES]; /* interval scopes */
128 uint64_t last_event[NUM_EVENT_TYPES]; /* epochs */
131 struct snapshot *snapshot;
133 static unsigned num_origins;
134 static struct origin *origin;
135 #define FOR_EACH_ORIGIN(_n) for (_n = 0; _n < num_origins; _n++)
137 static unsigned check_seconds = 60;
139 static unsigned interval_length(enum interval_type it, const struct origin *o)
141 switch (o->iscope[it]) {
142 case LS_GLOBAL: return global_config.interval[it];
143 case LS_VG: return volume_group[o->vgid].config.interval[it];
144 case LS_POOL: return thin_pool[o->poolid].config.interval[it];
145 case LS_ORIGIN: return o->config.interval[it];
150 static unsigned get_oid(const char *name, const char *vg_name)
154 struct origin *o = origin + n;
155 if (!strcmp(name, o->name) && !strcmp(vg_name, vgname(o->vgid)))
161 /* vg must have been inserted already */
162 static unsigned insert_origin(const char *name, const char *vgname,
163 const char *poolname)
166 unsigned oid = get_oid(name, vgname);
169 INFO_LOG("origin #%u: %s/%s, pool: %s\n", num_origins, vgname, name,
172 origin = xrealloc(origin, num_origins * sizeof(struct origin));
173 o = origin + num_origins - 1;
174 memset(o, 0, sizeof(struct origin));
175 o->name = xstrdup(name);
176 o->vgid = get_vgid(vgname);
177 assert(o->vgid != ~0U);
178 o->poolid = get_poolid(poolname, vgname);
179 assert(o->poolid != ~0U);
180 return num_origins - 1;
184 enum event_type type;
186 struct origin *origin;
189 static int event_compare(const void *d1, const void *d2)
191 const struct event *a = d1, *b = d2;
193 if (a->epoch < b->epoch)
195 if (a->epoch > b->epoch)
200 static char *config_file;
202 #define FOR_EACH_SLOT_REVERSE(_j, _o) for ( \
203 unsigned _j = _o->num_slots - 1; _j != -1U; _j--)
205 static unsigned loglevel_arg_val = LL_WARNING;
208 static const struct lls_command *subcmd;
209 static struct lls_parse_result *lpr, *sublpr;
210 #define CMD_PTR(_cname) lls_cmd(LSG_MISMA_CMD_ ## _cname, misma_suite)
211 #define OPT_RESULT(_cname, _oname) (lls_opt_result(\
212 LSG_MISMA_ ## _cname ## _OPT_ ## _oname, \
213 (CMD_PTR(_cname) == CMD_PTR(MISMA))? lpr : sublpr))
214 #define OPT_GIVEN(_cname, _oname) (lls_opt_given(OPT_RESULT(_cname, _oname)))
215 #define OPT_UINT32_VAL(_cname, _oname) (lls_uint32_val(0, \
216 OPT_RESULT(_cname, _oname)))
217 #define OPT_STRING_VAL_N(_n, _cname, _oname) (lls_string_val(_n, \
218 OPT_RESULT(_cname, _oname)))
219 #define OPT_STRING_VAL(_cname, _oname) (OPT_STRING_VAL_N(0, _cname, _oname))
221 struct misma_user_data {bool (*handler)(void);};
222 #define EXPORT_CMD_HANDLER(_cmd) const struct misma_user_data \
223 lsg_misma_com_ ## _cmd ## _user_data = { \
224 .handler = com_ ## _cmd \
227 /* does not allocate memory */
228 void misma_log(int ll, const char* fmt,...)
235 if (ll < loglevel_arg_val)
237 if (subcmd == CMD_PTR(RUN)) {
240 strftime(str, sizeof(str), "%b %d %H:%M:%S", tm);
241 fprintf(stderr, "%s ", str);
244 vfprintf(stderr, fmt, argp);
247 static const char *exit_hook;
249 __attribute__ ((noreturn))
250 static void run_exit_hook_and_die(const char *str)
253 char *argv[] = {"/bin/sh", "-c", NULL, NULL};
258 * Prevent helpers from calling us again via die() or
259 * die_errno(), which would result in a crash due to an endless
264 arg = msg("%s '%s'", tmp, str);
271 void die(const char *fmt, ...)
278 ret = vasprintf(&str, fmt, argp);
280 if (ret < 0) { /* give up */
284 misma_log(LL_EMERG, "%s\n", str);
285 run_exit_hook_and_die(str);
288 void die_errno(const char *fmt, ...)
292 int ret, save_errno = errno;
295 ret = vasprintf(&str, fmt, argp);
301 misma_log(LL_EMERG, "%s: %s\n", str, strerror(save_errno));
302 run_exit_hook_and_die(str);
305 __attribute__ ((const))
306 static uint32_t ffz(uint32_t v)
310 assert(v != (uint32_t)-1);
311 if ((v & 0xffff) == 0xffff) {
315 if ((v & 0xff) == 0xff) {
319 if ((v & 0xf) == 0xf) {
323 if ((v & 0x3) == 0x3) {
327 if ((v & 0x1) == 0x1)
332 static bool slot_is_used(unsigned slot, const struct origin *o)
334 return o->snapshot[slot].seq != 0;
337 static void mark_slot_unused(unsigned slot, struct origin *o)
339 o->snapshot[slot].seq = 0;
342 /* Use highest numbered unused slot, or default if all slots are used. */
343 static unsigned get_slot(unsigned seq, const struct origin *o)
346 FOR_EACH_SLOT_REVERSE(sl, o)
347 if (!slot_is_used(sl, o))
350 mod = (1 << o->num_slots) - 1;
351 return ffz(seq % mod);
355 * We specify --autobackup n to avoid filling up /etc/lvm/archive with tons of
356 * useless backup configurations.
358 static bool remove_snapshot(unsigned sl, struct origin *o, bool dry_run)
360 struct snapshot *snap = o->snapshot + sl;
362 char *arg = msg("%s/misma-%s.%u", vgname(o->vgid), o->name, snap->seq);
374 printf("dry-run: would remove snapshot %s\n", arg);
378 NOTICE_LOG("removing snapshot %s\n", arg);
379 success = xexec(argv, NULL);
382 mark_slot_unused(sl, o);
386 static int slot_compare(const void *a, const void *b, void *data)
388 const struct snapshot *s1 = a, *s2 = b;
389 struct origin *o = data;
391 if (!slot_is_used(s1 - o->snapshot, o))
393 if (!slot_is_used(s2 - o->snapshot, o))
395 if (s1->seq < s2->seq)
397 if (s1->seq > s2->seq)
402 static void sort_slots(struct origin *o)
404 qsort_r(o->snapshot, o->num_slots, sizeof(struct snapshot),
409 * sleazy (adj.): 1640s, "downy, fuzzy," later "flimsy, unsubstantial" (1660s).
411 * A sleazy snapshot is one whose distance (with respect to creation time) to
412 * its sibling snapshots is minimal.
414 static bool remove_sleazy_snapshot(struct origin *o, bool dry_run)
416 unsigned sl, victim = 0;
418 bool have_victim = false;
419 struct snapshot *prev = NULL, *next = NULL;
422 for (sl = 0; sl < o->num_slots; sl++)
423 if (slot_is_used(sl, o))
425 for (; sl < o->num_slots; prev = o->snapshot + sl, sl++) {
427 struct snapshot *s = o->snapshot + sl;
429 assert(slot_is_used(sl, o));
430 next = sl == o->num_slots - 1? NULL : s + 1;
434 dist = 10 * (s->epoch - next->epoch);
436 dist = 10 * (prev->epoch - s->epoch);
438 dist = prev->epoch - next->epoch;
439 DEBUG_LOG("seq %u, slot %u, epoch %" PRIu64 ", score %" PRIu64"\n",
440 s->seq, sl, s->epoch, dist);
441 if (!have_victim || dist < score) {
448 INFO_LOG("no snapshots\n");
451 NOTICE_LOG("victim: seq %u, slot %u, score %" PRIu64 "\n",
452 o->snapshot[victim].seq, victim, score);
453 if (!remove_snapshot(victim, o, dry_run))
459 static void set_interval(enum interval_type it, const struct time_arg *ta)
461 enum lvm_scope scope = ta->lvmspec.scope;
462 unsigned vgid, poolid, oid, n;
464 if (scope == LS_GLOBAL) {
465 NOTICE_LOG("default interval #%u: %u seconds\n", it,
467 global_config.interval[it] = ta->seconds;
470 vgid = get_vgid(ta->lvmspec.vg);
472 die("invalid vg in lvmspec: %s", ta->lvmspec.vg);
475 volume_group[vgid].config.interval[it] = ta->seconds;
478 poolid = get_poolid(ta->lvmspec.pool, vgname(vgid));
480 die("invalid pool in lvmspec: %s", ta->lvmspec.pool);
481 thin_pool[poolid].config.interval[it] = ta->seconds;
484 oid = get_oid(ta->lvmspec.tlv, vgname(vgid));
486 die("invalid tlv in lvmspec: %s", ta->lvmspec.tlv);
487 origin[oid].config.interval[it] = ta->seconds;
493 * Narrow the scope of all matching origins for which it is currently
494 * set to a wider scope.
497 struct origin *o = origin + n;
498 if (o->iscope[it] >= scope)
499 continue; /* already set to more narrow scope */
506 if (poolid != o->poolid || vgid != o->vgid)
516 NOTICE_LOG("interval #%u for %s/%s: %u seconds\n", it,
517 vgname(o->vgid), o->name, ta->seconds);
518 o->iscope[it] = scope;
523 char *vg, *lv, *pool, *origin;
527 static void free_lv_info(struct lv_info *lv)
535 static void parse_lvs_line(const char *line, struct lv_info *result)
537 char *tmp = xstrdup(line), *p = tmp + 2, *comma;
539 comma = strchr(p, ',');
540 assert(comma && comma != p);
542 result->vg = xstrdup(p);
544 comma = strchr(p, ',');
547 result->lv = xstrdup(p);
549 comma = strchr(p, ',');
552 result->pool = xstrdup(p);
554 comma = strchr(p, ',');
557 result->origin = xstrdup(p);
559 assert(sscanf(p, "%" PRIu64, &result->time) == 1);
563 static void init_origins(void)
573 "-o", "vgname,lvname,pool_lv,origin,lvtime",
575 "--config", "report/time_format=%s",
578 char *buf, *tmp, *line, *select_string = NULL;
579 struct line_iter liter;
582 if (OPT_GIVEN(MISMA, ORIGIN) == 0)
583 die("--origin not given");
585 /* create argument to --select */
586 for (n = 0; n < OPT_GIVEN(MISMA, ORIGIN); n++) {
588 const char *arg = OPT_STRING_VAL_N(n, MISMA, ORIGIN);
591 slash = strchr(tmp, '/');
592 if (!slash || slash == tmp || !slash[1])
593 die("--origin arg must be of the form vg/tlv");
595 tmp2 = msg("%s%s (vg_name=%s && (lv_name=%s ||"
596 "(origin=%s && lv_name =~ misma-%s.[0-9]+)))",
597 select_string? select_string : "",
598 select_string? " || " : "" ,
599 tmp, slash + 1, slash + 1, slash + 1
603 select_string = tmp2;
605 argv[2] = select_string;
606 if (!xexec(argv, &buf))
609 line_iter_init(&liter, tmp);
610 /* insert vgs and pools */
611 while ((line = line_iter_get(&liter))) {
612 parse_lvs_line(line, &lv);
613 DEBUG_LOG("vg: %s, lv: %s, pool: %s, origin: %s, "
614 "time: %" PRIu64"\n",
615 lv.vg, lv.lv, lv.pool, lv.origin, lv.time);
616 if (lv.origin[0] == '\0') { /* origin */
618 if (lv.pool[0] == '\0')
619 die("%s/%s is no thin LV", lv.vg, lv.lv);
620 insert_pool(lv.pool, lv.vg);
626 line_iter_init(&liter, tmp);
628 while ((line = line_iter_get(&liter))) {
629 parse_lvs_line(line, &lv);
630 if (lv.origin[0] == '\0')
631 insert_origin(lv.lv, lv.vg, lv.pool);
635 /* check that all given origins exist */
636 for (n = 0; n < OPT_GIVEN(MISMA, ORIGIN); n++) {
637 const char *arg = OPT_STRING_VAL_N(n, MISMA, ORIGIN);
641 slash = strchr(tmp, '/');
643 oid = get_oid(slash + 1, tmp);
646 die("origin %s does not exist", arg);
649 line_iter_init(&liter, tmp);
650 /* allocate and init snapshot arrays */
651 while ((line = line_iter_get(&liter))) {
656 parse_lvs_line(line, &lv);
657 if (lv.origin[0] == '\0') { /* no snapshot */
661 oid = get_oid(lv.origin, lv.vg);
665 o->snapshot = xrealloc(o->snapshot, o->num_slots
666 * sizeof(struct snapshot));
667 s = o->snapshot + o->num_slots - 1;
668 fmt = msg("misma-%s.%%u", lv.origin);
669 if (sscanf(lv.lv, fmt, &s->seq) != 1)
670 die("parse error: %s", lv.lv);
673 if (s->seq > o->last_seq)
674 o->last_seq = s->seq;
675 if (s->epoch > o->last_event[ET_CREATE])
676 o->last_event[ET_CREATE] = s->epoch;
682 static void die_lopsub(int lopsub_ret, char **errctx)
684 const char *m = lls_strerror(-lopsub_ret);
686 ERROR_LOG("%s: %s\n", *errctx, m);
688 ERROR_LOG("%s\n", m);
694 static void parse_options(int argc, char **argv, const struct lls_command *cmd,
695 struct lls_parse_result **lprp)
702 char **cf_argv, *errctx = NULL;
703 const char *subcmd_name;
704 struct lls_parse_result *merged_lpr, *cf_lpr;
706 ret = lls_parse(argc, argv, cmd, lprp, &errctx);
708 die_lopsub(ret, &errctx);
710 if (OPT_GIVEN(MISMA, CONFIG_FILE))
711 config_file = xstrdup(OPT_STRING_VAL(MISMA,
714 const char *home = getenv("HOME");
716 die("fatal: HOME is unset or empty");
717 config_file = msg("%s/.mismarc", home);
720 ret = open(config_file, O_RDONLY);
722 if (errno != ENOENT || OPT_GIVEN(MISMA, CONFIG_FILE))
723 die_errno("can not open config file %s", config_file);
724 /* no config file -- nothing to do */
729 ret = fstat(fd, &statbuf);
731 die_errno("failed to stat config file %s", config_file);
732 sz = statbuf.st_size;
733 if (sz == 0) { /* config file is empty -- nothing to do */
737 map = mmap(NULL, sz, PROT_READ, MAP_PRIVATE, fd, 0);
738 if (map == MAP_FAILED)
739 die_errno("failed to mmap config file %s", config_file);
740 subcmd_name = (cmd == CMD_PTR(MISMA))? NULL : lls_command_name(cmd);
741 ret = lls_convert_config(map, sz, subcmd_name, &cf_argv,
745 ERROR_LOG("failed to convert config file %s\n", config_file);
746 die_lopsub(ret, &errctx);
749 ret = lls_parse(cf_argc, cf_argv, cmd, &cf_lpr, &errctx);
750 lls_free_argv(cf_argv);
752 die_lopsub(ret, &errctx);
753 /* command line options override config file options */
754 ret = lls_merge(*lprp, cf_lpr, cmd, &merged_lpr, &errctx);
756 die_lopsub(ret, &errctx);
757 lls_free_parse_result(cf_lpr, cmd);
758 lls_free_parse_result(*lprp, cmd);
765 static void get_utilization(void)
767 char *select_string = NULL, *buf, *line;
772 "--noheading", "--unquoted",
773 "-o", "vgname,lvname,data_percent,metadata_percent",
776 struct line_iter liter;
778 for (n = 0; n < num_pools; n++) {
779 const struct thin_pool *pool = thin_pool + n;
780 char *tmp = msg("%s%s (vg_name = %s && lv_name = %s)",
781 (n == 0)? "" : select_string, (n == 0)? "" : "||",
782 vgname(pool->vgid), pool->name);
786 argv[2] = select_string;
787 if (!xexec(argv, &buf))
790 line_iter_init(&liter, buf);
791 while ((line = line_iter_get(&liter))) {
792 struct percentage_pair *u;
793 struct thin_pool *pool;
796 size_t len = strlen(line);
797 char *vg = xmalloc(len), *lv = xmalloc(len);
798 if (sscanf(line, "%s %s %f %f", vg, lv, &data, &meta) != 4)
799 die("cannot parse lvs line: %s", line);
800 poolid = get_poolid(lv, vg);
803 assert(poolid != ~0U);
804 pool = thin_pool + poolid;
805 u = &pool->utilization;
806 u->data = data + 0.5;
807 u->meta = meta + 0.5;
808 INFO_LOG("pool %s/%s utilization: %u/%u\n",
809 vgname(pool->vgid), pool->name, u->data, u->meta);
814 static bool pool_is_full(const struct thin_pool *pool)
817 struct percentage_pair t, u = pool->utilization;
819 if (pool->threshold_scope == LS_GLOBAL)
820 t = global_config.thresholds;
821 else if (pool->threshold_scope == LS_VG)
822 t = volume_group[pool->vgid].config.thresholds;
824 t = pool->config.thresholds;
825 ret = u.data > t.data || u.meta > t.meta;
827 NOTICE_LOG("pool %s/%s utilization: %u/%u, threshold: %u/%u\n",
828 vgname(pool->vgid), pool->name,
829 u.data, u.meta, t.data, t.meta);
830 WARNING_LOG("pool %s/%s exceeds utilization thresholds\n",
831 vgname(pool->vgid), pool->name);
836 static void check_utilization(void)
838 bool found_full_pool, removed_snapshot;
841 found_full_pool = false;
842 removed_snapshot = false;
844 for (unsigned n = 0; n < num_pools; n++) {
846 const struct thin_pool *pool = thin_pool + n;
847 if (!pool_is_full(pool))
849 found_full_pool = true;
851 struct origin *o = origin + m;
854 if (remove_sleazy_snapshot(o, false))
855 removed_snapshot = true;
858 if (!found_full_pool)
860 if (removed_snapshot)
862 INFO_LOG("full pool found, but nothing to remove\n");
865 static bool create_snapshot(struct origin *o, bool dry_run)
867 unsigned seq = o->last_seq + 1;
868 char *name = msg("misma-%s.%u", o->name, seq);
869 char *vg_origin = msg("%s/%s", vgname(o->vgid), o->name);
885 printf("dry-run: would create snapshot #%u of origin %s\n",
891 NOTICE_LOG("creating snapshot %s/%s\n", vgname(o->vgid), name);
892 if (!xexec(argv, NULL))
893 die("could not create snapshot");
899 static void signal_handler(int signo)
901 die("caught signal %d, terminating", signo);
905 struct fstrim_range {uint64_t start; uint64_t len; uint64_t minlen;};
906 #define FITRIM _IOWR('X', 121, struct fstrim_range)
908 static bool trim_filesystem(struct origin *o, bool dry_run)
915 struct line_iter liter;
916 char *line, *mp = NULL;
917 struct fstrim_range range = {.len = ULLONG_MAX};
919 dev = msg("/dev/%s/%s", vgname(o->vgid), o->name);
920 if (stat(dev, &sb) < 0) {
921 WARNING_LOG("stat(%s): %m\n", dev);
925 if ((sb.st_mode & S_IFMT) != S_IFBLK) {
926 WARNING_LOG("not a block device: %s\n", dev);
931 majo = major(sb.st_rdev);
932 mino = minor(sb.st_rdev);
933 fd = open("/proc/self/mountinfo", O_RDONLY);
935 WARNING_LOG("open(/proc/self/mountinfo): %m\n");
938 if (!fd2buf(fd, &buf)) {
939 WARNING_LOG("fd2buf error\n");
944 line_iter_init(&liter, buf);
945 /* 13 15 0:5 / /proc */
946 while ((line = line_iter_get(&liter))) {
947 unsigned id, parent, mmajo, mmino;
948 size_t len = strlen(line);
949 char *mountroot = xmalloc(len), *target = xmalloc(len);
951 if (sscanf(line, "%u %u %u:%u %s %s", &id, &parent, &mmajo,
952 &mmino, mountroot, target) != 6) {
953 WARNING_LOG("parse mountinfo line: %s\n", line);
959 if (mmajo == majo && mmino == mino) {
967 WARNING_LOG("unable to find mountpoint of origin\n");
975 fd = open(mp, O_RDONLY);
977 WARNING_LOG("open(%s): %m\n", mp);
981 if (ioctl(fd, FITRIM, &range)) {
982 WARNING_LOG("ioctl(FITRIM, %s): %m\n", mp);
988 NOTICE_LOG("trimmed %s\n", mp);
993 static void set_threshold(const struct threshold_arg *ta)
995 enum lvm_scope scope = ta->lvmspec.scope;
996 unsigned poolid = 0, vgid;
998 if (scope == LS_GLOBAL) {
999 global_config.thresholds = ta->threshold;
1002 vgid = get_vgid(ta->lvmspec.vg);
1004 die("invalid vg in lvmspec: %s", ta->lvmspec.vg);
1005 if (scope == LS_VG) {
1006 volume_group[vgid].config.thresholds = ta->threshold;
1008 assert(scope == LS_POOL);
1009 poolid = get_poolid(ta->lvmspec.pool, vgname(vgid));
1011 die("invalid pool in lvmspec: %s", ta->lvmspec.pool);
1012 thin_pool[poolid].config.thresholds = ta->threshold;
1015 * Narrow the scope of all matching pools for which it is currently
1016 * set to a wider scope.
1018 for (unsigned n = 0; n < num_pools; n++) {
1019 struct thin_pool *p = thin_pool + n;
1020 if (p->threshold_scope >= scope)
1021 continue; /* already set to more narrow scope */
1022 if (vgid != p->vgid)
1024 if (scope == LS_POOL && poolid != n)
1026 NOTICE_LOG("threshold for pool %s/%s: %u/%u\n",
1027 vgname(vgid), p->name, ta->threshold.data,
1028 ta->threshold.meta);
1029 p->threshold_scope = scope;
1033 static void log_event(const void *d)
1035 const struct event *e = d;
1038 DEBUG_LOG("(%s,%u): %" PRIu64 "\n", e->origin->name,
1041 DEBUG_LOG("(utilization): %" PRIu64 "\n", e->epoch);
1044 static unsigned check_run_options(void)
1048 unsigned n, num_events = 0;
1050 for (n = 0; n < OPT_GIVEN(RUN, THRESHOLD); n++) {
1051 struct threshold_arg tha;
1052 arg = OPT_STRING_VAL_N(n, RUN, THRESHOLD);
1053 parse_threshold_arg(arg,"--threshold", &tha);
1054 set_threshold(&tha);
1055 free_lvmspec(&tha.lvmspec);
1057 if (OPT_GIVEN(RUN, CHECK_INTERVAL)) {
1058 arg = OPT_STRING_VAL(RUN, CHECK_INTERVAL);
1059 check_seconds = parse_timespec(arg, "check-interval");
1060 check_range(check_seconds, 10, 86400, "check-interval");
1062 for (n = 0; n < OPT_GIVEN(RUN, TRIM_INTERVAL); n++) {
1063 arg = OPT_STRING_VAL_N(n, RUN, TRIM_INTERVAL);
1064 parse_time_arg(arg, "--trim-interval", &ta);
1066 check_range(ta.seconds, 60, ~0U, "trim-interval");
1067 set_interval(IT_TRIM, &ta);
1068 free_lvmspec(&ta.lvmspec);
1070 for (n = 0; n < OPT_GIVEN(RUN, CREATE_INTERVAL); n++) {
1071 arg = OPT_STRING_VAL_N(n, RUN, CREATE_INTERVAL);
1072 parse_time_arg(arg, "--create-interval", &ta);
1073 check_range(ta.seconds, 60, 86400 * 365, "create-interval");
1074 set_interval(IT_CREATE, &ta);
1075 free_lvmspec(&ta.lvmspec);
1077 for (n = 0; n < OPT_GIVEN(RUN, MAX_AGE); n++) {
1078 arg = OPT_STRING_VAL_N(n, RUN, MAX_AGE);
1079 parse_time_arg(arg, "--max-age", &ta);
1080 check_range(ta.seconds, 86400, 86400 * 20 * 365, "max-age");
1081 set_interval(IT_MAX_AGE, &ta);
1082 free_lvmspec(&ta.lvmspec);
1084 FOR_EACH_ORIGIN(n) {
1085 struct origin *o = origin + n;
1086 uint32_t ma, cr, max_slots; /* max age, create interval */
1088 INFO_LOG("found %u snapshots of origin %s/%s\n",
1089 o->num_slots, vgname(o->vgid), o->name);
1090 /* set number of slots */
1091 ma = interval_length(IT_MAX_AGE, o);
1092 cr = interval_length(IT_CREATE, o);
1094 die("%s/%s: max-age/create ratio too small",
1095 vgname(o->vgid), o->name);
1096 max_slots = 1 + ceil(log2((double)ma / cr + 1));
1097 assert(max_slots > 2);
1098 assert(max_slots < 30);
1099 if (o->num_slots > max_slots)
1100 die("%s/%s: too many snapshots", vgname(o->vgid),
1102 if (o->num_slots < max_slots) {
1103 unsigned diff = max_slots - o->num_slots;
1104 o->snapshot = xrealloc(o->snapshot, max_slots
1105 * sizeof(struct snapshot));
1106 memset(o->snapshot + o->num_slots, 0,
1107 diff * sizeof(struct snapshot));
1108 o->num_slots = max_slots;
1110 INFO_LOG("%s/%s: using %u slots\n", vgname(o->vgid), o->name,
1112 if (interval_length(IT_TRIM, o) > 0)
1115 return num_events + 1 + num_origins;
1118 static void dispatch_create_event(struct origin *o)
1121 const struct thin_pool *pool;
1124 pool = thin_pool + o->poolid;
1125 if (pool_is_full(pool)) {
1126 WARNING_LOG("%s/%s: creation suspended\n", vgname(o->vgid),
1130 seq = o->last_seq + 1, sl = get_slot(seq, o);
1131 if (slot_is_used(sl, o) && !remove_snapshot(sl, o, false))
1132 die("%s/%s: unable to free slot\n", vgname(o->vgid), o->name);
1134 create_snapshot(o, false);
1135 o->snapshot[sl].seq = seq;
1136 o->snapshot[sl].epoch = now;
1138 o->last_event[ET_CREATE] = now;
1141 /* We leak the fd but that's OK as long as we're only called once. */
1142 static int silence_lvm(void)
1145 int fd = open("/dev/null", O_RDWR);
1148 die_errno("open(/dev/null)");
1149 val = msg("%d", fd);
1150 setenv("LVM_ERR_FD", val, true /* overwrite */);
1155 __attribute__ ((noreturn))
1156 static bool com_run(void)
1159 unsigned n, num_events;
1161 struct event **event; /* At most 2 * num_origins + 1 */
1162 struct heap *event_heap;
1163 uint64_t now = time(NULL);
1165 num_events = check_run_options();
1166 event = xmalloc(num_events * sizeof(struct event *));
1168 (*ep) = xmalloc(sizeof(struct event));
1169 (*ep)->type = ET_CHECK;
1170 (*ep)->origin = NULL;
1174 FOR_EACH_ORIGIN(n) {
1175 struct origin *o = origin + n;
1176 (*ep) = xmalloc(sizeof(struct event));
1177 (*ep)->type = ET_CREATE;
1179 (*ep)->epoch = o->last_event[ET_CREATE]
1180 + interval_length(IT_CREATE, o);
1183 if (interval_length(IT_TRIM, o) == 0)
1185 (*ep) = xmalloc(sizeof(struct event));
1186 (*ep)->type = ET_TRIM;
1188 (*ep)->epoch = now + interval_length(IT_TRIM, o);
1192 event_heap = heap_init(&event, num_events, event_compare);
1193 if (get_misma_pid(config_file) > 0)
1194 die("already running");
1195 if (OPT_GIVEN(RUN, DAEMON))
1196 fd = daemonize(OPT_STRING_VAL(RUN, LOGFILE));
1197 if (!misma_lock(config_file))
1198 die("already running");
1199 if (signal(SIGINT, &signal_handler) == SIG_ERR)
1200 die_errno("signal handler for SIGINT");
1201 if (signal(SIGTERM, &signal_handler) == SIG_ERR)
1202 die_errno("signal handler for SIGTERM");
1203 if (signal(SIGHUP, &signal_handler) == SIG_ERR)
1204 die_errno("signal handler for SIGHUP");
1206 if (write(fd, "\0", 1) < 0)
1210 exit_hook = OPT_STRING_VAL(RUN, EXIT_HOOK);
1211 if (OPT_GIVEN(RUN, SUPPRESS_LVM_WARNINGS))
1214 struct event *e = heap_min(event_heap);
1218 if (e->epoch > now) {
1219 INFO_LOG("sleeping %" PRIu64 " seconds\n",
1221 sleep(e->epoch - now);
1224 e = heap_extract_min(event_heap);
1228 INFO_LOG("next event: check\n");
1229 check_utilization();
1231 e->epoch = now + check_seconds;
1234 INFO_LOG("next event: trim %s/%s\n",
1235 vgname(o->vgid), o->name);
1236 trim_filesystem(o, false /* dry-run */);
1237 e->origin->last_event[ET_TRIM] = now;
1238 e->epoch = now + interval_length(IT_TRIM, o);
1241 INFO_LOG("next event: create %s/%s\n", vgname(o->vgid),
1243 dispatch_create_event(o);
1244 e->epoch = now + interval_length(IT_CREATE, o);
1248 heap_insert(e, event_heap);
1249 heap_dump(event_heap, log_event);
1253 EXPORT_CMD_HANDLER(run);
1255 static void seconds_to_human(int64_t diff, char *buf)
1257 if (diff > 2 * 86400 * 365)
1258 sprintf(buf, "%3" PRId64 " years ", diff / (86400 * 365));
1259 else if (diff > 2 * 86400 * 30)
1260 sprintf(buf, "%3" PRId64 " months ", diff / (86400 * 30));
1261 else if (diff > 2 * 86400 * 7)
1262 sprintf(buf, "%3" PRId64 " weeks ", diff / (86400 * 7));
1263 else if (diff > 2 * 86400)
1264 sprintf(buf, "%3" PRId64 " days ", diff / 86400);
1265 else if (diff > 2 * 3600)
1266 sprintf(buf, "%3" PRId64 " hours ", diff / 3600);
1267 else if (diff > 2 * 60)
1268 sprintf(buf, "%3" PRId64 " minutes", diff / 60);
1270 sprintf(buf, "%3" PRId64 " second%s", diff, diff == 1? "" : "s");
1273 static bool origin_matches_lvmspec(const struct origin *o,
1274 const struct lvmspec *spec)
1276 if (spec->scope == LS_GLOBAL)
1278 if (strcmp(spec->vg, vgname(o->vgid)))
1280 if (spec->scope == LS_VG)
1282 if (spec->scope == LS_ORIGIN)
1283 return !strcmp(spec->tlv, o->name);
1284 return !strcmp(spec->pool, thin_pool[o->poolid].name);
1287 static bool for_each_matching_origin(bool (*func)(struct origin *, bool),
1290 unsigned k, n, num_args = lls_num_inputs(sublpr);
1291 struct lvmspec *spec = NULL; /* STFU gcc-12.3.0 */
1295 spec = xmalloc(num_args * sizeof(*spec));
1296 for (k = 0; k < num_args; k++)
1297 parse_lvmspec(lls_input(k, sublpr), "create/rm", spec + k);
1298 FOR_EACH_ORIGIN(n) {
1299 struct origin *o = origin + n;
1300 for (k = 0; k < num_args; k++)
1301 if (origin_matches_lvmspec(o, spec + k))
1303 if (num_args == 0 || k < num_args) {
1309 if (!match && num_args > 0)
1310 printf("no matches\n");
1314 static bool list_snapshots(struct origin *o, bool l_given)
1317 printf("%s/%s:\n", vgname(o->vgid), o->name);
1318 FOR_EACH_SLOT_REVERSE(sl, o) {
1321 struct snapshot *s = o->snapshot + sl;
1324 assert(slot_is_used(sl, o));
1326 printf("/dev/%s/misma-%s.%u\t", vgname(o->vgid),
1330 strftime(buf, sizeof(buf), "%F %R", tm);
1333 printf("%8u ", s->seq);
1335 seconds_to_human(t - s->epoch, buf);
1336 printf(" %s\n", buf);
1341 static bool com_ls(void)
1343 return for_each_matching_origin(list_snapshots,
1344 OPT_GIVEN(LS, LONG));
1346 EXPORT_CMD_HANDLER(ls);
1348 static bool com_create(void)
1350 if (!misma_lock(config_file))
1351 die("already running");
1352 return for_each_matching_origin(create_snapshot,
1353 OPT_GIVEN(CREATE, DRY_RUN));
1355 EXPORT_CMD_HANDLER(create);
1357 static bool com_rm(void)
1359 if (!misma_lock(config_file))
1360 die("already running");
1361 return for_each_matching_origin(remove_sleazy_snapshot,
1362 OPT_GIVEN(RM, DRY_RUN));
1364 EXPORT_CMD_HANDLER(rm);
1366 static bool com_kill(void)
1369 unsigned sig = OPT_UINT32_VAL(KILL, SIGNAL);
1372 pid = get_misma_pid(config_file);
1374 die("no misma run process to send signal to");
1375 NOTICE_LOG("sending signal %u to pid %d\n", sig, pid);
1376 if (kill(pid, sig) < 0)
1378 if (!OPT_GIVEN(KILL, WAIT))
1381 struct timespec ts = {
1382 .tv_sec = ms / 1000,
1383 .tv_nsec = (ms % 1000) * 1000 * 1000
1385 if (nanosleep(&ts, NULL) < 0)
1387 if (kill(pid, 0) < 0)
1388 return errno == ESRCH;
1393 EXPORT_CMD_HANDLER(kill);
1395 #define LSG_MISMA_CMD(_name) #_name
1396 static const char * const subcommand_names[] = {LSG_MISMA_SUBCOMMANDS NULL};
1397 #undef LSG_MISMA_CMD
1399 static void show_subcommand_summary(bool verbose)
1403 printf("Available subcommands:\n");
1405 const struct lls_command *cmd;
1406 for (i = 1; (cmd = lls_cmd(i, misma_suite)); i++) {
1407 const char *purpose = lls_purpose(cmd);
1408 const char *name = lls_command_name(cmd);
1409 printf("%-12s%s\n", name, purpose);
1414 for (i = 0; i < LSG_NUM_MISMA_SUBCOMMANDS; i++) {
1421 n += printf("%s", subcommand_names[i]);
1427 static bool com_trim(void)
1429 if (!misma_lock(config_file))
1430 die("already running");
1431 return for_each_matching_origin(trim_filesystem,
1432 OPT_GIVEN(TRIM, DRY_RUN));
1434 EXPORT_CMD_HANDLER(trim);
1436 static bool com_help(void)
1439 char *errctx, *help;
1441 const struct lls_command *cmd;
1443 ret = lls_check_arg_count(sublpr, 0, 1, &errctx);
1445 die_lopsub(ret, &errctx);
1446 if (lls_num_inputs(sublpr) == 0) {
1447 show_subcommand_summary(OPT_GIVEN(HELP, LONG));
1450 arg = lls_input(0, sublpr);
1451 ret = lls_lookup_subcmd(arg, misma_suite, &errctx);
1453 die_lopsub(ret, &errctx);
1454 cmd = lls_cmd(ret, misma_suite);
1455 if (OPT_GIVEN(HELP, LONG))
1456 help = lls_long_help(cmd);
1458 help = lls_short_help(cmd);
1459 printf("%s\n", help);
1463 EXPORT_CMD_HANDLER(help);
1465 static bool com_configtest(void)
1467 printf("Syntax Ok\n");
1470 EXPORT_CMD_HANDLER(configtest);
1472 static bool com_utilization(void)
1475 for (unsigned n = 0; n < num_pools; n++) {
1476 struct thin_pool *p = thin_pool + n;
1477 printf("%s/%s: %u%%/%u%%\n",
1478 vgname(p->vgid), p->name, p->utilization.data,
1479 p->utilization.meta);
1483 EXPORT_CMD_HANDLER(utilization);
1485 const char *GET_VERSION(void);
1486 static void handle_version_and_help(void)
1490 if (OPT_GIVEN(MISMA, VERSION)) {
1491 printf(PACKAGE " %s\n"
1492 "Copyright (C) " COPYRIGHT_YEAR " " AUTHOR ".\n"
1493 "License: " LICENSE ": <" LICENSE_URL ">.\n"
1494 "This is free software: you are free to change and redistribute it.\n"
1495 "There is NO WARRANTY, to the extent permitted by law.\n"
1497 "Web page: " URL "\n"
1498 "Clone URL: " CLONE_URL "\n"
1499 "Gitweb: " GITWEB_URL "\n"
1500 "Author's Home Page: " HOME_URL "\n"
1501 "Send feedback to: " AUTHOR " <" EMAIL ">\n"
1507 if (OPT_GIVEN(MISMA, DETAILED_HELP))
1508 help = lls_long_help(CMD_PTR(MISMA));
1509 else if (OPT_GIVEN(MISMA, HELP))
1510 help = lls_short_help(CMD_PTR(MISMA));
1513 printf("%s\n", help);
1518 int main(int argc, char **argv)
1520 unsigned num_inputs;
1523 const struct misma_user_data *ud;
1526 parse_options(argc, argv, CMD_PTR(MISMA), &lpr);
1527 loglevel_arg_val = OPT_UINT32_VAL(MISMA, LOGLEVEL);
1528 handle_version_and_help();
1529 num_inputs = lls_num_inputs(lpr);
1530 if (num_inputs == 0) {
1531 show_subcommand_summary(true /* verbose */);
1534 ret = lls_lookup_subcmd(argv[argc - num_inputs], misma_suite, &errctx);
1536 die_lopsub(ret, &errctx);
1537 subcmd = lls_cmd(ret, misma_suite);
1538 parse_options(num_inputs, argv + argc - num_inputs, subcmd, &sublpr);
1539 if (subcmd != CMD_PTR(HELP))
1541 ud = lls_user_data(subcmd);
1542 exit(ud->handler()? EXIT_SUCCESS : EXIT_FAILURE);