diff --git a/.gitignore b/.gitignore index a6f379146..5452cb479 100644 --- a/.gitignore +++ b/.gitignore @@ -130,6 +130,7 @@ ylwrap /mkswap /more /mount +/mountbomber /mountpoint /namei /newgrp diff --git a/configure.ac b/configure.ac index 1e31ca3e2..beb3b5867 100644 --- a/configure.ac +++ b/configure.ac @@ -1191,6 +1191,16 @@ UL_REQUIRES_BUILD([mount], [libmount]) AM_CONDITIONAL([BUILD_MOUNT], [test "x$build_mount" = xyes]) +AC_ARG_ENABLE([mountbomber], + AS_HELP_STRING([--enable-mount], [do build mountbomber(8)]), + [], [UL_DEFAULT_ENABLE([mountbomber], [check])] +) +UL_BUILD_INIT([mountbomber]) +UL_REQUIRES_LINUX([mountbomber]) +UL_REQUIRES_BUILD([mountbomber], [libmount]) +AM_CONDITIONAL([BUILD_MOUNTBOMBER], [test "x$build_mountbomber" = xyes]) + + AC_ARG_ENABLE([losetup], AS_HELP_STRING([--disable-losetup], [do not build losetup]), [], [UL_DEFAULT_ENABLE([losetup], [check])] diff --git a/include/strutils.h b/include/strutils.h index 4b3182f85..cd94e802e 100644 --- a/include/strutils.h +++ b/include/strutils.h @@ -258,6 +258,12 @@ static inline const char *skip_blank(const char *p) return p; } +static inline const char *skip_alnum(const char *p) +{ + while (isalnum(*p)) + ++p; + return p; +} /* Removes whitespace from the right-hand side of a string (trailing * whitespace). diff --git a/libmount/docs/libmount-sections.txt b/libmount/docs/libmount-sections.txt index da96b75b3..be1d5c144 100644 --- a/libmount/docs/libmount-sections.txt +++ b/libmount/docs/libmount-sections.txt @@ -312,18 +312,19 @@ MNT_NOHLPS
optstr +mnt_match_options mnt_optstr_append_option mnt_optstr_apply_flags mnt_optstr_deduplicate_option mnt_optstr_get_flags mnt_optstr_get_option mnt_optstr_get_options +mnt_optstr_get_uint mnt_optstr_next_option mnt_optstr_prepend_option mnt_optstr_remove_option mnt_optstr_set_option mnt_split_optstr -mnt_match_options
diff --git a/libmount/src/libmount.h.in b/libmount/src/libmount.h.in index 321c0540b..ff878e133 100644 --- a/libmount/src/libmount.h.in +++ b/libmount/src/libmount.h.in @@ -30,6 +30,7 @@ extern "C" { #include #include +#include #include /* Make sure libc MS_* definitions are used by default. Note that MS_* flags @@ -379,6 +380,8 @@ extern int mnt_optstr_set_option(char **optstr, const char *name, extern int mnt_optstr_remove_option(char **optstr, const char *name); extern int mnt_optstr_deduplicate_option(char **optstr, const char *name); +extern int mnt_optstr_get_uint(const char *optstr, const char *name, uintmax_t *res); + extern int mnt_split_optstr(const char *optstr, char **user, char **vfs, char **fs, int ignore_user, int ignore_vfs); diff --git a/libmount/src/libmount.sym b/libmount/src/libmount.sym index 792d11753..b6a46c789 100644 --- a/libmount/src/libmount.sym +++ b/libmount/src/libmount.sym @@ -356,3 +356,7 @@ MOUNT_2_35 { mnt_context_get_target_prefix; mnt_context_set_target_prefix; } MOUNT_2.34; + +MOUNT_2_37 { + mnt_optstr_get_uint; +} MOUNT_2_35; diff --git a/libmount/src/optstr.c b/libmount/src/optstr.c index 781bb2980..68e18cd71 100644 --- a/libmount/src/optstr.c +++ b/libmount/src/optstr.c @@ -310,6 +310,42 @@ int mnt_optstr_get_option(const char *optstr, const char *name, return rc; } +/** + * mnt_optstr_get_uint: + * @optstr: string with a comma separated list of options + * @name: requested option name + * @value: returns number + * + * Returns: 0 on success, 1 when not found the @name or negative number in case + * of error. + */ +int mnt_optstr_get_uint(const char *optstr, const char *name, uintmax_t *res) +{ + char *val; + size_t valsz; + int rc; + + if (!optstr || !name) + return -EINVAL; + + *res = 0; + + rc = mnt_optstr_get_option(optstr, name, &val, &valsz); + + if (rc == 0 && val && valsz) { + char *end = NULL; + + errno = 0; + *res = strtoumax(val, &end, 0); + + if (errno) + return -errno; + if (val == end || (end && *end != ',' && *end != '\0')) + return -EINVAL; + } + return rc; +} + /** * mnt_optstr_deduplicate_option: * @optstr: string with a comma separated list of options @@ -1350,6 +1386,28 @@ static int test_get(struct libmnt_test *ts, int argc, char *argv[]) return rc; } +static int test_get_uint(struct libmnt_test *ts, int argc, char *argv[]) +{ + char *optstr; + const char *name; + uintmax_t num; + int rc; + + if (argc < 2) + return -EINVAL; + optstr = argv[1]; + name = argv[2]; + + rc = mnt_optstr_get_uint(optstr, name, &num); + if (rc == 0) + printf("found; name: %s, argument=%ju\n", name, num); + else if (rc == 1) + printf("%s: not found\n", name); + else + printf("parse error: %s\n", optstr); + return rc; +} + static int test_remove(struct libmnt_test *ts, int argc, char *argv[]) { const char *name; @@ -1428,6 +1486,7 @@ int main(int argc, char *argv[]) { "--prepend",test_prepend," [] prepend value to optstr" }, { "--set", test_set, " [] (un)set value" }, { "--get", test_get, " search name in optstr" }, + { "--get-uint", test_get_uint, " search number in optstr" }, { "--remove", test_remove, " remove name in optstr" }, { "--dedup", test_dedup, " deduplicate name in optstr" }, { "--split", test_split, " split into FS, VFS and userspace" }, diff --git a/misc-utils/Makemodule.am b/misc-utils/Makemodule.am index 4d933f1df..382c558f8 100644 --- a/misc-utils/Makemodule.am +++ b/misc-utils/Makemodule.am @@ -229,3 +229,11 @@ hardlink_CFLAGS += $(PCRE_CFLAGS) endif dist_man_MANS += misc-utils/hardlink.1 endif + + +if BUILD_MOUNTBOMBER +usrbin_exec_PROGRAMS += mountbomber +mountbomber_SOURCES = misc-utils/mountbomber.c lib/monotonic.c +mountbomber_LDADD = $(LDADD) libcommon.la libmount.la $(REALTIME_LIBS) +mountbomber_CFLAGS = $(AM_CFLAGS) -I$(ul_libmount_incdir) +endif # BUILD_MOUNT diff --git a/misc-utils/mountbomber.c b/misc-utils/mountbomber.c new file mode 100644 index 000000000..9cbf7da15 --- /dev/null +++ b/misc-utils/mountbomber.c @@ -0,0 +1,942 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nls.h" +#include "c.h" +#include "bitops.h" +#include "env.h" +#include "strutils.h" +#include "closestream.h" +#include "canonicalize.h" +#include "fileutils.h" +#include "monotonic.h" + +#define XALLOC_EXIT_CODE MNT_EX_SYSERR +#include "xalloc.h" + +#define OPTUTILS_EXIT_CODE MNT_EX_USAGE +#include "optutils.h" + +#define MOUNTPOINT_FMT "%05zu" +#define MOUNTPOINT_BUFSZ sizeof(stringify_value(SIZE_MAX)) + +enum { + CMD_DELAY, + CMD_LABEL, + CMD_MOUNT, + CMD_REMOUNT, + CMD_REPEAT, + CMD_UMOUNT, +}; + +static const char *cmdnames[] = { + [CMD_DELAY] = "delay", + [CMD_LABEL] = "label", + [CMD_MOUNT] = "mount", + [CMD_REMOUNT] = "remount", + [CMD_REPEAT] = "repeat", + [CMD_UMOUNT] = "umount", +}; + +enum { + CMD_TARGET_ALL, /* default */ + + CMD_TARGET_LAST, + CMD_TARGET_NEXT, + CMD_TARGET_PREV, + CMD_TARGET_RAND, + + CMD_TARGET_NONE, +}; + +static const char *targetnames[] = { + [CMD_TARGET_ALL] = "all", + + [CMD_TARGET_LAST] = "last", + [CMD_TARGET_NEXT] = "next", + [CMD_TARGET_PREV] = "prev", + [CMD_TARGET_RAND] = "rand", + + [CMD_TARGET_NONE] = "none", +}; + +struct bomber_cmd { + size_t id; /* CMD_ */ + size_t idx; + size_t target; /* CMD_TARGET_ */ + + char *args; /* command options specified by user */ + + uintmax_t repeat_max_loops; + time_t repeat_max_seconds; + size_t repeat_nloops; + char *repeat_label; + suseconds_t delay_usec; +}; + +struct bomber_worker { + pid_t pid; + int status; /* status as returned by wait() */ + + size_t pool_off; /* first mountpoint */ + size_t pool_len; /* number of mounpoints assigned to the worker */ + char *pool_status; + + struct timeval starttime; + int last_mountpoint; +}; + +struct bomber_ctl { + size_t nmounts; /* --pool */ + + const char *dir; /* --dir */ + + struct bomber_cmd *commands; + size_t ncommands; + + struct bomber_worker *workers; + size_t nworkers; + size_t nactive; + + unsigned int clean_dir : 1, + carriage_ret: 1, + no_cleanup : 1; +}; + +static volatile sig_atomic_t sig_die; +static int verbose; + +static void sig_handler_die(int dummy __attribute__((__unused__))) +{ + sig_die = 1; +} + +static inline void mesg_cr_cleanup(struct bomber_ctl *ctl) +{ + if (ctl->carriage_ret) + fputc('\n', stdout); + ctl->carriage_ret = 0; +} + +static void __attribute__ ((__format__ (__printf__, 2, 3))) +mesg_bar(struct bomber_ctl *ctl, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vfprintf(stdout, fmt, ap); + va_end(ap); + + fflush(stdout); + fputc('\r', stdout); + ctl->carriage_ret = 1; +} + +static void +mesg_bar_done(struct bomber_ctl *ctl) +{ + mesg_cr_cleanup(ctl); +} + +static void __attribute__ ((__format__ (__printf__, 2, 3))) +mesg_warn(struct bomber_ctl *ctl, const char *fmt, ...) +{ + va_list ap; + + mesg_cr_cleanup(ctl); + va_start(ap, fmt); + vwarn(fmt, ap); + va_end(ap); +} + +static void __attribute__ ((__format__ (__printf__, 2, 3))) +mesg_warnx(struct bomber_ctl *ctl, const char *fmt, ...) +{ + va_list ap; + + mesg_cr_cleanup(ctl); + va_start(ap, fmt); + vwarnx(fmt, ap); + va_end(ap); +} + +static void __attribute__ ((__format__ (__printf__, 1, 2))) +mesg_verbose(const char *fmt, ...) +{ + va_list ap; + + if (!verbose) + return; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fputc('\n', stderr); +} + +static inline char *get_mountpoint_name(size_t i, char *buf, size_t bufsz) +{ + int len = snprintf(buf, bufsz, MOUNTPOINT_FMT, i); + + if (len < 0 || (size_t) len > bufsz) + return NULL; + return buf; +} + +static int bomber_init_mountdir(struct bomber_ctl *ctl) +{ + int mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; + size_t i; + int rc = 0; + + assert(ctl->dir); + + if (access(ctl->dir, F_OK) != 0) { + if (mkdir_p(ctl->dir, mode)) + err(EXIT_FAILURE, _("cannot create directory %s"), ctl->dir); + ctl->clean_dir = 1; + } + + if (chdir(ctl->dir)) { + warn(_("cannot access directory %s"), ctl->dir); + return rc; + } + + for (i = 0; i < ctl->nmounts; i++) { + int rc; + char name[MOUNTPOINT_BUFSZ]; + + get_mountpoint_name(i, name, sizeof(name)); + + mesg_bar(ctl, _("initialize mountpoint: %05zu"), i + 1); + + rc = mkdir(name, mode); + if (rc && errno != EEXIST) { + mesg_warn(ctl, _("cannot create directory %s"), name); + break; + } + } + + mesg_bar_done(ctl); + return rc; +} + +static void bomber_cleanup_dir(struct bomber_ctl *ctl) +{ + size_t i; + + if (!ctl->clean_dir) + return; + + if (chdir(ctl->dir)) { + mesg_warn(ctl, _("cannot access directory %s"), ctl->dir); + return; + } + + for (i = 0; i < ctl->nmounts; i++) { + char name[MOUNTPOINT_BUFSZ]; + + get_mountpoint_name(i, name, sizeof(name)); + + mesg_bar(ctl, _("cleanup mountpoint: %05zu"), i + 1); + + if (rmdir(name)) { + if (errno == EBUSY) { + umount(name); + errno = 0; + rmdir(name); + } + } + } + + mesg_bar_done(ctl); + + if (rmdir(ctl->dir) && errno != ENOENT) + mesg_warn(ctl, _("connot remove directory %s"), ctl->dir); +} + +static inline int is_mounted(struct bomber_worker *wrk, size_t mnt) +{ + return isset(wrk->pool_status, mnt - wrk->pool_off); +} + +static inline int set_mounted(struct bomber_worker *wrk, size_t mnt) +{ + return setbit(wrk->pool_status, mnt - wrk->pool_off); +} + +static inline int set_umounted(struct bomber_worker *wrk, size_t mnt) +{ + return clrbit(wrk->pool_status, mnt - wrk->pool_off); +} + +static int do_mount(struct bomber_worker *wrk, struct bomber_cmd *cmd, size_t mnt) +{ + char name[MOUNTPOINT_BUFSZ]; + int rc = 0; + + assert(wrk); + assert(cmd); + + if (is_mounted(wrk, mnt)) { + mesg_verbose(" ignore target %ju (mounted)", mnt); + goto done; + } + get_mountpoint_name(mnt, name, sizeof(name)); + rc = mount("tmpfs", name, "tmpfs", 0, NULL); + if (rc) + warn("mount failed"); + else + set_mounted(wrk, mnt); +done: + if (rc == 0) + wrk->last_mountpoint = mnt; + return rc; +} + +static int do_umount(struct bomber_worker *wrk, struct bomber_cmd *cmd, size_t mnt) +{ + char name[MOUNTPOINT_BUFSZ]; + int rc = 0; + + assert(wrk); + assert(cmd); + + if (!is_mounted(wrk, mnt)) { + mesg_verbose(" ignore target %ju (not mounted)", mnt); + goto done; + } + get_mountpoint_name(mnt, name, sizeof(name)); + rc = umount(name); + if (rc) + warn("umount failed"); + else + set_umounted(wrk, mnt); +done: + if (rc == 0) + wrk->last_mountpoint = mnt; + return rc; +} + +static int get_mount_idx(struct bomber_worker *wrk, struct bomber_cmd *cmd) +{ + int mnt = -1; + int lo = wrk->pool_off; + int up = wrk->pool_off + wrk->pool_len - 1; + + switch (cmd->target) { + case CMD_TARGET_RAND: + mnt = (rand() % (up - lo + 1)) + lo; + break; + case CMD_TARGET_LAST: + mnt = wrk->last_mountpoint; + if (mnt < 0) + mnt = 0; + break; + case CMD_TARGET_NEXT: + mnt = wrk->last_mountpoint + 1; + if (mnt < 0) + mnt = 0; + if (mnt > up) + mnt = lo; + break; + case CMD_TARGET_PREV: + mnt = wrk->last_mountpoint - 1; + if (mnt < 0) + mnt = 0; + if (mnt < lo) + mnt = up; + break; + + case CMD_TARGET_ALL: + mesg_verbose(" target: all"); + break; + } + + if (mnt >= 0) + mesg_verbose(" target: %d", mnt); + return mnt; +} + +static int cmd_mount(struct bomber_ctl *ctl, struct bomber_worker *wrk, struct bomber_cmd *cmd) +{ + int rc = 0; + int mnt; + + assert(ctl); + assert(wrk); + assert(cmd); + + mnt = get_mount_idx(wrk, cmd); + if (mnt >= 0) + rc = do_mount(wrk, cmd, mnt); + + else if (cmd->target == CMD_TARGET_ALL) { + int lo = wrk->pool_off; + int up = wrk->pool_off + wrk->pool_len - 1; + + for (mnt = lo; mnt <= up; mnt++) { + rc += do_mount(wrk, cmd, mnt); + } + } + + return rc; +} + +static int cmd_umount(struct bomber_ctl *ctl, struct bomber_worker *wrk, struct bomber_cmd *cmd) +{ + int rc = 0; + int mnt; + + assert(ctl); + assert(wrk); + assert(cmd); + + mnt = get_mount_idx(wrk, cmd); + if (mnt >= 0) + rc = do_umount(wrk, cmd, mnt); + + else if (cmd->target == CMD_TARGET_ALL) { + int lo = wrk->pool_off; + int up = wrk->pool_off + wrk->pool_len - 1; + + for (mnt = lo; mnt <= up; mnt++) { + rc += do_umount(wrk, cmd, mnt); + } + } + + return rc; +} + +static int cmd_repeat(struct bomber_ctl *ctl, struct bomber_worker *wrk, + struct bomber_cmd *cmd, size_t *idx) +{ + size_t idx0 = *idx; + + if (cmd->repeat_max_seconds) { + struct timeval rest, now; + + gettime_monotonic(&now); + timersub(&now, &wrk->starttime, &rest); + + if (rest.tv_sec < cmd->repeat_max_seconds) + goto repeat; + } else if (cmd->repeat_max_loops) { + + if (cmd->repeat_nloops < cmd->repeat_max_loops) { + cmd->repeat_nloops++; + goto repeat; + } + cmd->repeat_nloops = 0; + } + + return 0; +repeat: + if (cmd->repeat_label) { + size_t i; + + for (i = *idx; i > 0; i--) { + struct bomber_cmd *xc = &ctl->commands[i - 1]; + if (xc->id == CMD_LABEL && + xc->args && strcmp(xc->args, cmd->repeat_label) == 0) { + *idx = i - 1; + break; + } + } + } else + *idx = 0; + + mesg_verbose(" repeating %zu --> %zu", *idx, idx0); + return 0; +} + +static int cmd_delay(struct bomber_cmd *cmd) +{ + xusleep(cmd->delay_usec); + return 0; +} + +static pid_t start_worker(struct bomber_ctl *ctl, struct bomber_worker *wrk) +{ + pid_t pid; + size_t i = 0; + int rc = 0; + + switch ((pid = fork())) { + case -1: + warn(_("fork failed")); + return -errno; + case 0: /* child */ + break; + default: /* parent */ + return pid; + } + + gettime_monotonic(&wrk->starttime); + + /* init */ + wrk->pool_status = xcalloc(wrk->pool_len / NBBY + 1, sizeof(char)); + + /* child main loop */ + while (sig_die == 0) { + struct bomber_cmd *cmd; + size_t idx = i; + + if (i >= ctl->ncommands) + break; + + cmd = &ctl->commands[idx]; + + if (cmd->target != CMD_TARGET_NONE) + mesg_verbose("COMMAND[%zu] %s:%s", idx, + cmdnames[cmd->id], + targetnames[cmd->target]); + else + mesg_verbose("COMMAND[%zu] %s", idx, cmdnames[cmd->id]); + + switch (cmd->id) { + case CMD_MOUNT: + rc = cmd_mount(ctl, wrk, cmd); + break; + case CMD_UMOUNT: + rc = cmd_umount(ctl, wrk, cmd); + break; + case CMD_REMOUNT: + break; + case CMD_DELAY: + rc = cmd_delay(cmd); + break; + case CMD_REPEAT: + rc = cmd_repeat(ctl, wrk, cmd, &idx); + break; + case CMD_LABEL: + break; + default: + rc = -EINVAL; + break; + } + if (rc) + break; + + if (idx == i) + i++; + else + i = idx; /* modified by function (e.g. CMD_REPEAT) */ + } + + if (rc) + err(EXIT_FAILURE, _("worker %d: failed"), getpid()); + + exit(EXIT_SUCCESS); +} + +static int bomber_init_pool(struct bomber_ctl *ctl) +{ + size_t i, off = 0, len; + + assert(ctl); + + len = ctl->nmounts / ctl->nworkers; + + ctl->workers = xcalloc(ctl->nworkers, sizeof(struct bomber_worker)); + + for (i = 0; i < ctl->nworkers; i++) { + struct bomber_worker *w = &ctl->workers[i]; + + w->pool_off = off; + w->pool_len = len; + w->pid = start_worker(ctl, w); + if (w->pid <= 0) + return w->pid; + off += len; + ctl->nactive++; + + mesg_bar(ctl, _("starting worker: %04zu"), i + 1); + } + + mesg_bar_done(ctl); + return 0; +} + +static void unlink_child(struct bomber_ctl *ctl, pid_t pid, int status) +{ + size_t i; + + for (i = 0; i < ctl->nworkers; i++) { + struct bomber_worker *w = &ctl->workers[i]; + + if (w->pid == pid) { + w->pid = 0; + w->status = status; + ctl->nactive--; + break; + } + } +} + +static int bomber_wait_pool(struct bomber_ctl *ctl, int flags) +{ + while (ctl->nactive > 0) { + int status = 0; + pid_t pid; + + mesg_bar(ctl, _("active workers ... %04zu (waiting)"), ctl->nactive); + + pid = waitpid(-1, &status, flags); + if (pid == 0 && (flags & WNOHANG)) + return 0; + if (pid < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + if (errno == ECHILD) + break; + mesg_warn(ctl, _("waitpid failed")); + continue; + } + if (WIFEXITED(status) || WIFSIGNALED(status)) + unlink_child(ctl, pid, status); + + mesg_bar(ctl, _("active workers ... %04zu (waiting)"), ctl->nactive); + } + + mesg_bar_done(ctl); + + return 1; /* no more childs */ +} + +static int bomber_cleanup_pool(struct bomber_ctl *ctl) +{ + size_t i; + + for (i = 0; i < ctl->nworkers; i++) { + struct bomber_worker *w = &ctl->workers[i]; + if (w->pid > 0) + kill(w->pid, SIGTERM); + } + + bomber_wait_pool(ctl, 0); + + return 0; +} + +static int parse_command_args(struct bomber_ctl *ctl, struct bomber_cmd *cmd) +{ + uintmax_t x; + char *opt; + size_t optsz; + + assert(ctl); + assert(cmd); + assert(cmd->args); + + switch (cmd->id) { + case CMD_REPEAT: + /* unnamed argument */ + if (isdigit_string(cmd->args)) { + cmd->repeat_max_loops = strtou64_or_err(cmd->args, + _("repeat(): failed to parse arguments")); + break; + } + /* named arguments */ + if (mnt_optstr_get_uint(cmd->args, "loops", &cmd->repeat_max_loops) == 0) { + ; + } + if (mnt_optstr_get_uint(cmd->args, "seconds", &x) == 0) + cmd->repeat_max_seconds = (time_t) x; + if (mnt_optstr_get_option(cmd->args, "label", &opt, &optsz) == 0 && optsz) + cmd->repeat_label = xstrndup(opt, optsz); + break; + case CMD_DELAY: + if (isdigit_string(cmd->args)) { + cmd->delay_usec = strtou64_or_err(cmd->args, + _("delay(): failed to parse arguments")); + break; + } + errx(EXIT_FAILURE, _("delay(): failed to parse arguments")); + break; + default: + break; + } + + return 0; +} + +static inline size_t name2idx(const char *name, const char **ary, size_t arysz, const char *errmsg) +{ + size_t i; + + for (i = 0; i < arysz; i++) { + if (strcmp(name, ary[i]) == 0) + return i; + } + + errx(EXIT_FAILURE, errmsg, name); + return 0; +} + +static int bomber_add_command(struct bomber_ctl *ctl, const char *str) +{ + char *cmdstr = xstrdup(str); + char *xstr = cmdstr; + + while (xstr && *xstr) { + struct bomber_cmd *cmd; + char *name, *end, *args = NULL, *target = NULL; + + ctl->commands = xrealloc(ctl->commands, + (ctl->ncommands + 1) * sizeof(struct bomber_cmd)); + + cmd = &ctl->commands[ctl->ncommands]; + memset(cmd, 0, sizeof(*cmd)); + + cmd->idx = ctl->ncommands; + ctl->ncommands++; + + name = xstr; + if (*xstr == '@') + xstr++; + else if (startswith(name, "->")) + xstr += 2; + + end = (char *) skip_alnum(xstr); + + /* name terminator */ + switch (*end) { + case '(': + args = end + 1; + break; + case ':': + target = end + 1; + break; + case ',': + case '\0': + break; + default: + errx(EXIT_FAILURE, _("failed to parse command name '%s'"), name); + break; + } + + xstr = *end ? end + 1 : end; + *end = '\0'; + + if (*name == '@') { + cmd->id = CMD_LABEL; + cmd->args = xstrdup(name + 1); + } else if (startswith(name, "->")) { + cmd->id = CMD_REPEAT; + cmd->repeat_label = xstrdup(name + 2); + } else + cmd->id = name2idx(name, cmdnames, ARRAY_SIZE(cmdnames), + _("unknown command name '%s'")); + if (args) { + end = strchr(args, ')'); + if (!end) + errx(EXIT_FAILURE, _("missing terminating ')' in '%s'"), args); + *end = '\0'; + if (!cmd->args) + cmd->args = xstrdup(args); + xstr = end + 1; + + if (*xstr == ':') + target = xstr + 1; + else if (*xstr == ',') + xstr++; + + parse_command_args(ctl, cmd); + } + + if (target) { + end = (char *) skip_alnum(target); + xstr = *end ? end + 1 : end; + *end = '\0'; + cmd->target = name2idx(target, targetnames, + ARRAY_SIZE(targetnames), + _("unknown command target '%s'")); + } else switch (cmd->id) { + case CMD_REPEAT: + case CMD_LABEL: + case CMD_DELAY: + cmd->target = CMD_TARGET_NONE; + break; + } + } + + if (verbose) { + size_t i; + + mesg_verbose("parsed commands:"); + for (i = 0; i < ctl->ncommands; i++) { + struct bomber_cmd *cmd = &ctl->commands[i]; + + mesg_verbose("[%zu] %10s : target=%-7s args=\"%s\"", + i, + cmdnames[cmd->id], + targetnames[cmd->target], + cmd->args ? : ""); + } + } + + + free(cmdstr); + return 0; +} + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + + fputs(USAGE_HEADER, out); + fprintf(out, _(" %s [options]\n"), program_invocation_short_name); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Generate large number of mount operations.\n"), out); + + fputs(USAGE_OPTIONS, out); + fputs(_(" -p, --pool number of the mountpoints (default: 100)\n"), out); + fputs(_(" -x, --parallel number of the parallel processes (default: 10 or 1)\n"), out); + fputs(_(" -d, --dir directory for mountpoints (default: /mnt/bomber)\n"), out); + fputs(_(" -O, --oper requested mount operations\n"), out); + fputs(_(" -N, --no-cleanup don't remove mountpoints\n"), out); + fputs(_(" -V, --verbose verbose output\n"), out); + printf(USAGE_HELP_OPTIONS(24)); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Operation syntax (--oper):\n"), out); + fputs(_(" command[(arg, ...)[:target]], ...\n"), out); + fputs(_(" @name specifies label and ->name repeats all from label \n"), out); + fputs(_(" * ->name loop may be restricted by , loops= or seconds=\n"), out); + fputs(_(" * for example repeat 100 times command foo: @A,foo,->A(100)\n"), out); + fputs(_(" * or repeat command foo for 3600 seconds: @A,foo,->A(seconds=3600)\n"), out); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Operation commands:\n"), out); + fputs(_(" mount call mount(2) syscall\n"), out); + fputs(_(" umount call umount(2) syscall\n"), out); + fputs(_(" delay() wait for microseconds\n"), out); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Operation targets:\n"), out); + fputs(_(" all all mountpoints in the pool\n"), out); + fputs(_(" rand random mountpoint from pool\n"), out); + fputs(_(" last previously used mountpoint\n"), out); + fputs(_(" next +1\n"), out); + fputs(_(" prev -1\n"), out); + fputs(USAGE_SEPARATOR, out); + fputs(_("Examples:\n"), out); + fputs(_(" mountbomber --pool 200 --oper \"mount:all,@A,umount:rand,mount:last,->A(1000),umount:all\"\n"), out); + fputs(_(" * mount 200 mountpoints\n"), out); + fputs(_(" * 1000 times call umount and mount on random mountpoint\n"), out); + fputs(_(" * after that umount all\n"), out); + fputs(_(" mountbomber --verbose --parallel 1 --oper \"mount:all,@A,umount:rand,mount:last,delay(500000),->A(10),umount:all\"\n"), out); + fputs(_(" * user and system friendly way to develop your testing scenario\n"), out); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Notes:\n"), out); + fputs(_(" The pool is split by number of paraller processes and the process uses\n" + " only a subset of the pool. For example \"--paralell 10 --pool 1000\"\n" + " means 100 mountpoints for the each process.\n"), out); + + + + fputs(USAGE_SEPARATOR, out); + exit(EXIT_SUCCESS); +} + + +int main(int argc, char *argv[]) +{ + struct bomber_ctl _ctl = { + .nmounts = 100 + }, *ctl = &_ctl; + struct sigaction sa; + int rc = 0, c; + static const struct option longopts[] = { + { "pool", required_argument, NULL, 'p' }, + { "parallel", required_argument, NULL, 'x' }, + { "dir", required_argument, NULL, 'd' }, + { "oper", required_argument, NULL, 'O' }, + { "no-cleanup", no_argument, NULL, 'N' }, + { "verbose", no_argument, NULL, 'V' }, + { "version", no_argument, NULL, 'v' }, + { "help", no_argument, NULL, 'h' }, + { NULL, 0, NULL, 0 } + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + close_stdout_atexit(); + + while ((c = getopt_long(argc, argv, "hp:x:f:d:O:NVv", longopts, NULL)) != -1) { + + switch(c) { + case 'p': + ctl->nmounts = strtou32_or_err(optarg, _("failed to parse pool argument")); + break; + case 'x': + ctl->nworkers = strtou32_or_err(optarg, _("failed to parse parallel argument")); + break; + case 'd': + ctl->dir = xstrdup(optarg); + break; + case 'O': + bomber_add_command(ctl, optarg); + break; + case 'N': + ctl->no_cleanup = 1; + break; + case 'V': + verbose = 1; + break; + + case 'h': + usage(); + case 'v': + print_version(EXIT_SUCCESS); + default: + errtryhelp(EXIT_FAILURE); + } + } + + mnt_init_debug(0); + + if (!ctl->nmounts) + errx(EXIT_FAILURE, _("pool size cannot be zero")); + if (!ctl->dir) + ctl->dir = xstrdup("/mnt/bomber"); + if (!ctl->nworkers) + ctl->nworkers = ctl->nmounts > 10 ? 10 : 1; + + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = sig_handler_die; + sigaction(SIGINT, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + + rc = bomber_init_mountdir(ctl); + if (rc == 0) + rc = bomber_init_pool(ctl); + if (rc == 0) + bomber_wait_pool(ctl, 0); + if (sig_die) + mesg_warnx(ctl, _("interrupted by signal")); + + bomber_cleanup_pool(ctl); + + if (!ctl->no_cleanup) + bomber_cleanup_dir(ctl); + + return EXIT_SUCCESS; +}