Compare commits

...
Sign in to create a new pull request.

19 commits

Author SHA1 Message Date
Karel Zak
1e45e4e423 mountbomber: default to 10 processes
... or to 1 is the pool <10.

Signed-off-by: Karel Zak <kzak@redhat.com>
2020-09-23 12:18:15 +02:00
Karel Zak
6738bce1fd build-sys: enable mountbomber in topic/ branch
Signed-off-by: Karel Zak <kzak@redhat.com>
2020-09-23 12:06:28 +02:00
Karel Zak
b87373449a mountbomber: add usage()
Signed-off-by: Karel Zak <kzak@redhat.com>
2020-09-23 11:50:58 +02:00
Karel Zak
5d51a50f9c mountbomber: improve loops
- add label to address where to start loop
- add shortcuts for label and repeat functions

for example --oper:

  mount:all,@A,umount:rand,mount:last,->A(loops=10),umount:all

repeat 10 times all between @A and ->A

Signed-off-by: Karel Zak <kzak@redhat.com>
2020-09-22 11:50:48 +02:00
Karel Zak
5be5bf91e1 mountbomber: add delay() function
Signed-off-by: Karel Zak <kzak@redhat.com>
2020-09-22 10:54:45 +02:00
Karel Zak
e5b8fd35f7 mountbomber: add --verbose
Signed-off-by: Karel Zak <kzak@redhat.com>
2020-09-22 10:47:03 +02:00
Karel Zak
88e582de02 mountbomber: add repeat() function
Signed-off-by: Karel Zak <kzak@redhat.com>
2020-09-22 10:04:36 +02:00
Karel Zak
2528229bb2 mountbomber: add umount func
Signed-off-by: Karel Zak <kzak@redhat.com>
2020-09-21 17:04:37 +02:00
Karel Zak
4e361e0a74 mountbomber: remove unused functions
Signed-off-by: Karel Zak <kzak@redhat.com>
2020-09-18 12:34:09 +02:00
Karel Zak
14e85f1497 mountbomber: add basic mount command
Signed-off-by: Karel Zak <kzak@redhat.com>
2020-09-18 12:32:43 +02:00
Karel Zak
6e4f340aaa mountbomber: parse commadn arguments
Signed-off-by: Karel Zak <kzak@redhat.com>
2020-09-18 10:46:37 +02:00
Karel Zak
4eef10bd8c libmount: add mnt_optstr_get_uint()
Signed-off-by: Karel Zak <kzak@redhat.com>
2020-09-18 10:40:31 +02:00
Karel Zak
3baeda185c mountbomber: add workers
Signed-off-by: Karel Zak <kzak@redhat.com>
2020-09-17 17:16:55 +02:00
Karel Zak
6353e35d64 mountbomber: improve output
Signed-off-by: Karel Zak <kzak@redhat.com>
2020-09-17 16:41:37 +02:00
Karel Zak
0c949b58a9 mountbomber: add --oper
Signed-off-by: Karel Zak <kzak@redhat.com>
2020-09-17 15:23:13 +02:00
Karel Zak
02332a94ea include/strutils: add function skip_alnum()
Signed-off-by: Karel Zak <kzak@redhat.com>
2020-09-17 15:22:49 +02:00
Karel Zak
affbc3bcc5 build-sys: add mountbomber to gitignore
Signed-off-by: Karel Zak <kzak@redhat.com>
2020-09-15 12:11:00 +02:00
Karel Zak
88d9b42f34 mountbomber: move to misc-utils/ and init mount dir
* move to misc-utils/ (it's really not a system tool)
* add --enable-mountbomber to build-system
* add function to generate readable output and progress bar

Signed-off-by: Karel Zak <kzak@redhat.com>
2020-09-15 12:07:56 +02:00
Karel Zak
620ac26dca mountbomber: add new command (mail sceleton only)
Signed-off-by: Karel Zak <kzak@redhat.com>
2020-09-07 11:32:45 +02:00
9 changed files with 1035 additions and 1 deletions

1
.gitignore vendored
View file

@ -130,6 +130,7 @@ ylwrap
/mkswap
/more
/mount
/mountbomber
/mountpoint
/namei
/newgrp

View file

@ -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])]

View file

@ -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).

View file

@ -312,18 +312,19 @@ MNT_NOHLPS
<SECTION>
<FILE>optstr</FILE>
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
</SECTION>
<SECTION>

View file

@ -30,6 +30,7 @@ extern "C" {
#include <stdio.h>
#include <mntent.h>
#include <inttypes.h>
#include <sys/types.h>
/* 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);

View file

@ -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;

View file

@ -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,"<optstr> <name> [<value>] prepend value to optstr" },
{ "--set", test_set, "<optstr> <name> [<value>] (un)set value" },
{ "--get", test_get, "<optstr> <name> search name in optstr" },
{ "--get-uint", test_get_uint, "<optstr> <name> search number in optstr" },
{ "--remove", test_remove, "<optstr> <name> remove name in optstr" },
{ "--dedup", test_dedup, "<optstr> <name> deduplicate name in optstr" },
{ "--split", test_split, "<optstr> split into FS, VFS and userspace" },

View file

@ -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

942
misc-utils/mountbomber.c Normal file
View file

@ -0,0 +1,942 @@
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <getopt.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <stdarg.h>
#include <libmount.h>
#include <ctype.h>
#include <signal.h>
#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 <size> */
const char *dir; /* --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 <num> number of the mountpoints (default: 100)\n"), out);
fputs(_(" -x, --parallel <num> number of the parallel processes (default: 10 or 1)\n"), out);
fputs(_(" -d, --dir <path> directory for mountpoints (default: /mnt/bomber)\n"), out);
fputs(_(" -O, --oper <list> 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 <num>, loops=<num> or seconds=<num>\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(<num>) wait for <num> 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 <last>+1\n"), out);
fputs(_(" prev <last>-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;
}