/* * Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved. * Copyright (C) 2004-2018 Red Hat, Inc. All rights reserved. * Copyright (C) 2005-2007 NEC Corporation * * This file is part of the device-mapper userspace tools. * * It includes tree drawing code based on pstree: http://psmisc.sourceforge.net/ * * This copyrighted material is made available to anyone wishing to use, * modify, copy, or redistribute it subject to the terms and conditions * of the GNU General Public License v.2. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ // For canonicalize_file_name() #include "libdm/misc/dm-logging.h" #include "libdm/dm-tools/util.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef UDEV_SYNC_SUPPORT # include # include # include # include #endif /* FIXME Unused so far */ #undef HAVE_SYS_STATVFS_H #ifdef HAVE_SYS_STATVFS_H # include #endif #ifdef HAVE_SYS_IOCTL_H # include #endif #ifdef HAVE_SYS_TIMERFD_H # include #endif #ifdef HAVE_TERMIOS_H # include #endif #ifdef HAVE_GETOPTLONG # include # define GETOPTLONG_FN(a, b, c, d, e) getopt_long((a), (b), (c), (d), (e)) # define OPTIND_INIT 0 #else struct option { }; # define GETOPTLONG_FN(a, b, c, d, e) getopt((a), (b), (c)) # define OPTIND_INIT 1 #endif #ifndef TEMP_FAILURE_RETRY # define TEMP_FAILURE_RETRY(expression) \ (__extension__ \ ({ long int __result; \ do __result = (long int) (expression); \ while (__result == -1L && errno == EINTR); \ __result; })) #endif #ifdef __linux__ # include "libdm/misc/kdev_t.h" #else # define MAJOR(x) major((x)) # define MINOR(x) minor((x)) # define MKDEV(x,y) makedev((x),(y)) #endif #define LINE_SIZE 4096 #define ARGS_MAX 256 #define LOOP_TABLE_SIZE (PATH_MAX + 255) #define DEFAULT_DM_DEV_DIR "/dev/" #define DM_DEV_DIR_ENV_VAR_NAME "DM_DEV_DIR" #define DM_UDEV_COOKIE_ENV_VAR_NAME "DM_UDEV_COOKIE" /* FIXME Should be imported */ #ifndef DM_MAX_TYPE_NAME # define DM_MAX_TYPE_NAME 16 #endif /* FIXME Should be elsewhere */ #define SECTOR_SHIFT 9L /* program_id used for dmstats-managed statistics regions */ #define DM_STATS_PROGRAM_ID "dmstats" /* * Basic commands this code implments. */ typedef enum { DMSETUP_CMD = 0, LOSETUP_CMD = 1, DMLOSETUP_CMD = 2, DMSTATS_CMD = 3, DMSETUP_STATS_CMD = 4, DEVMAP_NAME_CMD = 5 } cmd_name_t; typedef enum { DMSETUP_TYPE = 0, LOSETUP_TYPE = 1, STATS_TYPE = 2, DEVMAP_NAME_TYPE = 3 } cmd_type_t; #define DMSETUP_CMD_NAME "dmsetup" #define LOSETUP_CMD_NAME "losetup" #define DMLOSETUP_CMD_NAME "dmlosetup" #define DMSTATS_CMD_NAME "dmstats" #define DMSETUP_STATS_CMD_NAME "dmsetup stats" #define DEVMAP_NAME_CMD_NAME "devmap_name" static const struct { cmd_name_t command; const char name[14]; cmd_type_t type; } _base_commands[] = { { DMSETUP_CMD, DMSETUP_CMD_NAME, DMSETUP_TYPE }, { LOSETUP_CMD, LOSETUP_CMD_NAME, LOSETUP_TYPE }, { DMLOSETUP_CMD, DMLOSETUP_CMD_NAME, LOSETUP_TYPE }, { DMSTATS_CMD, DMSTATS_CMD_NAME, STATS_TYPE }, { DMSETUP_STATS_CMD, DMSETUP_STATS_CMD_NAME, STATS_TYPE }, { DEVMAP_NAME_CMD, DEVMAP_NAME_CMD_NAME, DEVMAP_NAME_TYPE }, }; static const int _num_base_commands = DM_ARRAY_SIZE(_base_commands); /* * We have only very simple switches ATM. */ enum { GID_ARG = 'G', MODE_ARG = 'M', SORT_ARG = 'O', SELECT_ARG = 'S', UID_ARG = 'U', COLS_ARG = 'c', FORCE_ARG = 'f', HELP_ARG = 'h', MAJOR_ARG = 'j', MINOR_ARG = 'm', NOTABLE_ARG = 'n', OPTIONS_ARG = 'o', READ_ONLY = 'r', UUID_ARG = 'u', VERBOSE_ARG = 'v', YES_ARG = 'y', ADD_NODE_ON_CREATE_ARG = 128, ADD_NODE_ON_RESUME_ARG, ALIAS_ARG, ALL_DEVICES_ARG, ALL_PROGRAMS_ARG, ALL_REGIONS_ARG, AREAS_ARG, AREA_ARG, AREA_SIZE_ARG, BOUNDS_ARG, CHECKS_ARG, CLEAR_ARG, CONCISE_ARG, COUNT_ARG, DEFERRED_ARG, EXEC_ARG, FILEMAP_ARG, FOLLOW_ARG, FOREGROUND_ARG, GROUP_ARG, GROUP_ID_ARG, HEADINGS_ARG, HISTOGRAM_ARG, INACTIVE_ARG, INTERVAL_ARG, LENGTH_ARG, MANGLENAME_ARG, NAMEPREFIXES_ARG, NOFLUSH_ARG, NOGROUP_ARG, NOHEADINGS_ARG, NOLOCKFS_ARG, NOMONITOR_ARG, NOOPENCOUNT_ARG, NOSUFFIX_ARG, NOTIMESUFFIX_ARG, NOUDEVRULES_ARG, NOUDEVSYNC_ARG, PRECISE_ARG, PROGRAM_ID_ARG, RAW_ARG, READAHEAD_ARG, REGIONS_ARG, REGION_ARG, REGION_ID_ARG, RELATIVE_ARG, RETRY_ARG, ROWS_ARG, SEGMENTS_ARG, SEPARATOR_ARG, SETUUID_ARG, SHOWKEYS_ARG, START_ARG, TABLE_ARG, TARGET_ARG, TREE_ARG, UDEVCOOKIE_ARG, UNBUFFERED_ARG, UNITS_ARG, UNQUOTED_ARG, USER_DATA_ARG, VERIFYUDEV_ARG, VERSION_ARG, NUM_SWITCHES }; typedef enum { DR_TASK = 1, DR_INFO = 2, DR_DEPS = 4, DR_TREE = 8, /* Complete dependency tree required */ DR_NAME = 16, DR_STATS = 32, /* Requires populated stats handle. */ DR_STATS_META = 64, /* Requires listed stats handle. */ } report_type_t; typedef enum { DN_DEVNO, /* Major and minor number pair */ DN_BLK, /* Block device name (e.g. dm-0) */ DN_MAP /* Map name (for dm devices only, equal to DN_BLK otherwise) */ } dev_name_t; static cmd_name_t _base_command = DMSETUP_CMD; /* Default command is 'dmsetup' */ static cmd_type_t _base_command_type = DMSETUP_TYPE; static uint16_t _switches[NUM_SWITCHES]; static int _int_args[NUM_SWITCHES]; static const char *_string_args[NUM_SWITCHES]; static int _num_devices; static char *_uuid; static char *_table; static char *_target; static char *_command_to_exec; /* --exec */ static const char *_command; /* dmsetup */ static uint32_t _read_ahead_flags; static uint32_t _udev_cookie; static int _udev_only; static struct dm_tree *_dtree; static struct dm_report *_report; static report_type_t _report_type; static dev_name_t _dev_name_type; static uint64_t _count = 1; /* count of repeating reports */ static struct dm_timestamp *_initial_timestamp = NULL; static uint64_t _disp_factor = 512; /* display sizes in sectors */ static char _disp_units = 's'; static const char *_program_id = DM_STATS_PROGRAM_ID; /* program_id used for reports. */ static uint64_t _statstype = 0; /* stats objects to report */ static int _concise_output_produced = 0; /* Was any concise output already printed? */ static int _added_target = 0; /* Count added target (no target -> no event) */ struct command; static const struct command *_selection_cmd = NULL; /* Command to run against each device select with -S */ /* string names for stats object types */ static const char * const _stats_types[] = { "all", "area", "region", "group", NULL }; /* report timekeeping */ static struct dm_timestamp *_cycle_timestamp = NULL; #ifndef HAVE_SYS_TIMERFD_H static struct dm_timestamp *_start_timestamp = NULL; #else /* HAVE_SYS_TIMERFD_H */ static int _timer_fd = -1; /* timerfd file descriptor. */ #endif /* !HAVE_SYS_TIMERFD_H */ static uint64_t _interval = 0; /* configured interval in nsecs */ static uint64_t _new_interval = 0; /* flag top-of-interval */ static uint64_t _last_interval = 0; /* approx. measured interval in nsecs */ /* Invalid fd value used to signal end-of-reporting. */ #define TIMER_STOPPED (-2) #define NSEC_PER_USEC UINT64_C(1000) #define NSEC_PER_MSEC UINT64_C(1000000) #define NSEC_PER_SEC UINT64_C(1000000000) /* * Commands */ #define CMD_ARGS const struct command *cmd, const char *subcommand, int argc, char **argv, struct dm_names *names, int multiple_devices typedef int (*command_fn) (CMD_ARGS); struct command { const char name[24]; const char *help; int min_args; int max_args; int repeatable_cmd; /* Repeat to process device list? */ /* 2 means --select is also supported */ int has_subcommands; /* Command implements sub-commands. */ command_fn fn; }; static int _parse_line(struct dm_task *dmt, char *buffer, const char *file, int line) { char ttype[LINE_SIZE], *ptr, *comment; unsigned long long start, size; int n; /* trim trailing space */ for (ptr = buffer + strlen(buffer) - 1; ptr >= buffer; ptr--) if (!isspace((int) *ptr)) break; ptr++; *ptr = '\0'; /* trim leading space */ for (ptr = buffer; *ptr && isspace((int) *ptr); ptr++) ; if (!*ptr || *ptr == '#') return 1; if (sscanf(ptr, "%llu %llu %" DM_TO_STRING(LINE_SIZE) "s %n", &start, &size, ttype, &n) < 3) { log_error("Invalid format on line %d of table %s.", line, file); return 0; } ptr += n; if ((comment = strchr(ptr, (int) '#'))) *comment = '\0'; if (!dm_task_add_target(dmt, start, size, ttype, ptr)) return_0; _added_target++; return 1; } /* Parse multiple lines of table */ static int _parse_table_lines(struct dm_task *dmt) { char *pos = _table, *next_pos; int line = 0; do { /* Identify and terminate each line */ if ((next_pos = strchr(pos, '\n'))) *next_pos++ = '\0'; if (!_parse_line(dmt, pos, "", ++line)) return_0; } while ((pos = next_pos)); return 1; } static int _parse_file(struct dm_task *dmt, const char *file) { char *buffer = NULL; size_t buffer_size = 0; FILE *fp; int r = 0, line = 0; /* Table on cmdline or from stdin with --concise */ if (_table) return _parse_table_lines(dmt); /* OK for empty stdin */ if (file) { if (!(fp = fopen(file, "r"))) { log_error("Couldn't open '%s' for reading.", file); return 0; } } else fp = stdin; #ifndef HAVE_GETLINE buffer_size = LINE_SIZE; if (!(buffer = malloc(buffer_size))) { log_error("Failed to malloc line buffer."); return 0; } while (fgets(buffer, (int) buffer_size, fp)) #else while (getline(&buffer, &buffer_size, fp) > 0) #endif if (!_parse_line(dmt, buffer, file ? : "on stdin", ++line)) goto_out; r = 1; out: memset(buffer, 0, buffer_size); #ifndef HAVE_GETLINE free(buffer); #else free(buffer); #endif if (file && fclose(fp)) log_sys_debug("fclose", file); return r; } struct dm_split_name { char *subsystem; char *vg_name; char *lv_name; char *lv_layer; }; struct dmsetup_report_obj { struct dm_task *task; struct dm_info *info; struct dm_task *deps_task; struct dm_tree_node *tree_node; struct dm_split_name *split_name; struct dm_stats *stats; }; static int _task_run(struct dm_task *dmt) { int r; uint64_t delta; struct dm_timestamp *ts; if (_initial_timestamp) dm_task_set_record_timestamp(dmt); r = dm_task_run(dmt); if (_initial_timestamp && (ts = dm_task_get_ioctl_timestamp(dmt))) { delta = dm_timestamp_delta(ts, _initial_timestamp); log_debug("Timestamp: %7" PRIu64 ".%09" PRIu64 " seconds", delta / NSEC_PER_SEC, delta % NSEC_PER_SEC); } return r; } static struct dm_task *_get_deps_task(int major, int minor) { struct dm_task *dmt; struct dm_info info; if (!(dmt = dm_task_create(DM_DEVICE_DEPS))) return_NULL; if (!dm_task_set_major(dmt, major) || !dm_task_set_minor(dmt, minor)) goto_bad; if (_switches[NOOPENCOUNT_ARG] && !dm_task_no_open_count(dmt)) goto_bad; if (_switches[INACTIVE_ARG] && !dm_task_query_inactive_table(dmt)) goto_bad; if (_switches[CHECKS_ARG] && !dm_task_enable_checks(dmt)) goto_bad; if (!_task_run(dmt)) goto_bad; if (!dm_task_get_info(dmt, &info)) goto_bad; if (!info.exists) goto_bad; return dmt; bad: dm_task_destroy(dmt); return NULL; } static char *_extract_uuid_prefix(const char *uuid, const int separator) { char *ptr = NULL; char *uuid_prefix = NULL; size_t len; if (uuid) ptr = strchr(uuid, separator); len = ptr ? ptr - uuid : 0; if (!(uuid_prefix = malloc(len + 1))) { log_error("Failed to allocate memory to extract uuid prefix."); return NULL; } if (uuid) memcpy(uuid_prefix, uuid, len); uuid_prefix[len] = '\0'; return uuid_prefix; } static struct dm_split_name *_get_split_name(const char *uuid, const char *name, int separator) { struct dm_split_name *split_name; if (!(split_name = malloc(sizeof(*split_name)))) { log_error("Failed to allocate memory to split device name " "into components."); return NULL; } if (!(split_name->subsystem = _extract_uuid_prefix(uuid, separator))) { free(split_name); return_NULL; } split_name->vg_name = split_name->lv_name = split_name->lv_layer = (char *) ""; if (!strcmp(split_name->subsystem, "LVM") && (!(split_name->vg_name = strdup(name)) || !dm_split_lvm_name(NULL, NULL, &split_name->vg_name, &split_name->lv_name, &split_name->lv_layer))) log_error("Failed to allocate memory to split LVM name " "into components."); return split_name; } static void _destroy_split_name(struct dm_split_name *split_name) { /* * lv_name and lv_layer are allocated within the same block * of memory as vg_name so don't need to be freed separately. */ if (!strcmp(split_name->subsystem, "LVM")) free(split_name->vg_name); free(split_name->subsystem); free(split_name); } /* * Stats clock: * * Use either Linux timerfds or usleep to implement the reporting * interval wait. * * _start_timer() - Start the timer running. * _do_timer_wait() - Wait until the beginning of the next interval. * * _update_interval_times() - Update timestamps and interval estimate. */ /* * Return the current interval number counting upwards from one. */ static uint64_t _interval_num(void) { uint64_t count_arg = _int_args[COUNT_ARG]; return ((uint64_t) _int_args[COUNT_ARG] - _count) + !!count_arg; } #ifdef HAVE_SYS_TIMERFD_H static int _start_timerfd_timer(void) { struct itimerspec interval_timer; time_t secs; long nsecs; log_debug("Using timerfd for interval timekeeping."); /* timer running? */ if (_timer_fd != -1) return 1; memset(&interval_timer, 0, sizeof(interval_timer)); /* Use CLOCK_MONOTONIC to avoid warp on RTC adjustments. */ if ((_timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC)) < 0) { log_error("Could not create timer: %s", strerror(errno)); return 0; } secs = (time_t) _interval / NSEC_PER_SEC; nsecs = (long) _interval % NSEC_PER_SEC; /* Must set interval and value to create an armed periodic timer. */ interval_timer.it_interval.tv_sec = secs; interval_timer.it_interval.tv_nsec = nsecs; interval_timer.it_value.tv_sec = secs; interval_timer.it_value.tv_nsec = nsecs; log_debug("Setting interval timer to: " FMTu64 "s %ldns", (uint64_t)secs, nsecs); if (timerfd_settime(_timer_fd, 0, &interval_timer, NULL)) { log_error("Could not set interval timer: %s", strerror(errno)); return 0; } return 1; } static int _do_timerfd_wait(void) { uint64_t expired; ssize_t bytes; if (_timer_fd < 0) return_0; /* read on timerfd returns a uint64_t in host byte order. */ bytes = read(_timer_fd, &expired, sizeof(expired)); if (bytes < 0) { /* EBADF from invalid timerfd or EINVAL from too small buffer. */ log_error("Interval timer wait failed: %s.", strerror(errno)); return 0; } /* read(2) on a timerfd descriptor is guaranteed to return 8 bytes. */ if (bytes != 8) log_error("Unexpected byte count on timerfd read: " FMTssize_t ".", bytes); /* FIXME: attempt to rebase clock? */ if (expired > 1) log_warn("WARNING: Try increasing --interval ("FMTu64 " missed timer events).", expired - 1); /* Signal that a new interval has begun. */ _new_interval = 1; /* Final interval? */ if (_count == 2) { if (close(_timer_fd)) stack; /* Tell _update_interval_times() to shut down. */ _timer_fd = TIMER_STOPPED; } return 1; } static int _start_timer(void) { return _start_timerfd_timer(); } static int _do_timer_wait(void) { return _do_timerfd_wait(); } static int _timer_running(void) { return ((_timer_fd != TIMER_STOPPED) || _cycle_timestamp); } #else /* !HAVE_SYS_TIMERFD_H */ static int _start_usleep_timer(void) { log_debug("Using usleep for interval timekeeping."); _start_timestamp = dm_timestamp_alloc(); dm_timestamp_get(_start_timestamp); return 1; } static int _do_usleep_wait(void) { static struct dm_timestamp *_now = NULL; uint64_t this_interval; int64_t delta_t; /* * Report clock: compensate for time spent in userspace and stats * message ioctls by keeping track of the last wake time and * adjusting the sleep interval accordingly. */ if (!_now) { if (!(_now = dm_timestamp_alloc())) return_0; dm_timestamp_get(_now); this_interval = _interval; log_error("Using "FMTu64" as first interval.", this_interval); } else { dm_timestamp_get(_now); delta_t = dm_timestamp_delta(_now, _start_timestamp); log_debug("Interval timer drift: "FMTd64".", (delta_t % _interval)); /* FIXME: usleep timer drift over large counts. */ /* adjust for time spent populating and reporting */ this_interval = _interval - (delta_t % _interval); log_debug("Using "FMTu64" as interval.", this_interval); } /* Signal that a new interval has begun. */ _new_interval = 1; if (usleep(this_interval / NSEC_PER_USEC)) { if (errno == EINTR) log_error("Report interval interrupted by signal."); else if (errno == EINVAL) log_error("Report interval too short."); else stack; /* other reason */ return 0; } if (_count == 2) { dm_timestamp_destroy(_start_timestamp); dm_timestamp_destroy(_now); _start_timestamp = _now = NULL; } return 1; } static int _start_timer(void) { return _start_usleep_timer(); } static int _do_timer_wait(void) { return _do_usleep_wait(); } static int _timer_running(void) { return (_start_timestamp != NULL); } #endif /* HAVE_SYS_TIMERFD_H */ static int _update_interval_times(void) { static struct dm_timestamp *this_timestamp = NULL; uint64_t delta_t, interval_num = _interval_num(); int r = 1; /* * Clock shutdown for exit - nothing to do. */ if (!_timer_running()) goto out; /* clock is running */ r = 0; /* * Current timestamp. If _new_interval is set this is used as * the new cycle start timestamp. */ if (!this_timestamp) { if (!(this_timestamp = dm_timestamp_alloc())) return_0; } /* * Take cycle timstamp as close as possible to ioctl return. * * FIXME: use per-region timestamp deltas for interval estimate. */ if (!dm_timestamp_get(this_timestamp)) goto_out; /* * Stats clock: maintain a single timestamp taken just after the * call to dm_stats_populate() and take a delta between the current * and last value to determine the sampling interval. * * A new interval is started when the _new_interval flag is set * on return from _do_report_wait(). * * The first interval is treated as a special case: since the * time since the last clear of the counters is unknown (no * previous timestamp exists) the duration is assumed to be the * configured value. */ if (_cycle_timestamp) /* Current delta_t: time from start of cycle to now. */ delta_t = dm_timestamp_delta(this_timestamp, _cycle_timestamp); else { _cycle_timestamp = dm_timestamp_alloc(); if (!_cycle_timestamp) { log_error("Could not allocate timestamp object."); goto out; } /* Pretend we have the configured interval. */ delta_t = _interval; /* start the first cycle */ log_debug("Beginning first interval."); _new_interval = 1; } log_debug("Interval #%-4"PRIu64" time delta: %12"PRIu64"ns.", interval_num, delta_t); if (_new_interval) { /* Update timestamp and interval and clear _new_interval */ dm_timestamp_copy(_cycle_timestamp, this_timestamp); _last_interval = delta_t; _new_interval = 0; /* * Log interval duration and current error. */ log_debug("Interval #%-5"PRIu64" current err: %12"PRIi64"ns.", interval_num, ((int64_t)_last_interval - (int64_t)_interval)); log_debug("End interval #%-9"PRIu64" duration: %12"PRIu64"ns.", interval_num, _last_interval); } r = 1; out: /* timer stopped or never started */ if (!r || !_timer_running()) { /* The _cycle_timestamp has not yet been allocated if we * fail to obtain this_timestamp on the first interval. */ if (_cycle_timestamp) dm_timestamp_destroy(_cycle_timestamp); dm_timestamp_destroy(this_timestamp); /* Clear timestamp pointers to signal shutdown. */ _cycle_timestamp = this_timestamp = NULL; } return r; } static int _display_info_cols(struct dm_task *dmt, struct dm_info *info) { struct dmsetup_report_obj obj; uint64_t walk_flags = _statstype; int r = 0; int selected; char *device_name; obj.task = dmt; obj.info = info; obj.deps_task = NULL; obj.split_name = NULL; obj.stats = NULL; if (_report_type & DR_TREE) if (!(obj.tree_node = dm_tree_find_node(_dtree, info->major, info->minor))) { log_error("Cannot find node %d:%d.", info->major, info->minor); goto out; } if (_report_type & DR_DEPS) if (!(obj.deps_task = _get_deps_task(info->major, info->minor))) { log_error("Cannot get deps for %d:%d.", info->major, info->minor); goto out; } if (_report_type & DR_NAME) if (!(obj.split_name = _get_split_name(dm_task_get_uuid(dmt), dm_task_get_name(dmt), '-'))) goto_out; if (!(_report_type & (DR_STATS | DR_STATS_META))) { /* * If _selection_cmd is set we are applying -S to some other command, so suppress * output and run that other command if the device matches the criteria. */ if (!dm_report_object_is_selected(_report, &obj, _selection_cmd ? 0 : 1, &selected)) goto_out; if (_selection_cmd && selected) { device_name = (char*) dm_task_get_name(dmt); /* coverity[overrun-buffer-val] _setgeometry never called from this place */ if (!_selection_cmd->fn(_selection_cmd, NULL, 1, &device_name, NULL, 1)) goto_out; } r = 1; goto out; } /* * Obtain statistics for the current reporting object and set * the interval estimate used for stats rate conversion. */ if (_report_type & DR_STATS) { if (!(obj.stats = dm_stats_create(DM_STATS_PROGRAM_ID))) goto_out; dm_stats_bind_devno(obj.stats, info->major, info->minor); if (!dm_stats_populate(obj.stats, _program_id, DM_STATS_REGIONS_ALL)) { r = 1; goto out; } /* Update timestamps and handle end-of-interval accounting. */ _update_interval_times(); log_debug("Adjusted sample interval duration: %12"PRIu64"ns.", _last_interval); /* use measured approximation for calculations */ dm_stats_set_sampling_interval_ns(obj.stats, _last_interval); } else if (!obj.stats && (_report_type & DR_STATS_META) /* Only a dm_stats_list is needed for DR_STATS_META reports. */ && !(_report_type & DR_STATS)) { if (!(obj.stats = dm_stats_create(DM_STATS_PROGRAM_ID))) goto_out; dm_stats_bind_devno(obj.stats, info->major, info->minor); if (!dm_stats_list(obj.stats, _program_id)) goto_out; /* No regions to report is not an error */ if (!dm_stats_get_nr_regions(obj.stats)) { r = 1; goto out; } } /* Group report with no groups is not an error */ if ((walk_flags == DM_STATS_WALK_GROUP) && !dm_stats_get_nr_groups(obj.stats)) { r = 1; goto out; } dm_stats_walk_init(obj.stats, walk_flags); dm_stats_walk_do(obj.stats) { if (!dm_report_object(_report, &obj)) goto_out; dm_stats_walk_next(obj.stats); } dm_stats_walk_while(obj.stats); r = 1; out: if (obj.deps_task) dm_task_destroy(obj.deps_task); if (obj.split_name) _destroy_split_name(obj.split_name); if (obj.stats) dm_stats_destroy(obj.stats); return r; } static void _display_info_long(struct dm_task *dmt, struct dm_info *info) { const char *uuid; uint32_t read_ahead; printf("Name: %s\n", dm_task_get_name(dmt)); printf("State: %s%s%s\n", info->suspended ? "SUSPENDED" : "ACTIVE", info->read_only ? " (READ-ONLY)" : "", info->deferred_remove ? " (DEFERRED REMOVE)" : ""); /* FIXME Old value is being printed when it's being changed. */ if (dm_task_get_read_ahead(dmt, &read_ahead)) printf("Read Ahead: %" PRIu32 "\n", read_ahead); if (!info->live_table && !info->inactive_table) printf("Tables present: None\n"); else printf("Tables present: %s%s%s\n", info->live_table ? "LIVE" : "", info->live_table && info->inactive_table ? " & " : "", info->inactive_table ? "INACTIVE" : ""); if (info->open_count != -1) printf("Open count: %d\n", info->open_count); printf("Event number: %" PRIu32 "\n", info->event_nr); printf("Major, minor: %d, %d\n", info->major, info->minor); if (info->target_count != -1) printf("Number of targets: %d\n", info->target_count); if ((uuid = dm_task_get_uuid(dmt)) && *uuid) printf("UUID: %s\n", uuid); putchar('\n'); } static int _display_info(struct dm_task *dmt) { struct dm_info info; int r = 1; if (!dm_task_get_info(dmt, &info)) return_0; if (!info.exists) { log_error("Device does not exist."); return 0; } if (!_switches[COLS_ARG]) _display_info_long(dmt, &info); else r = _display_info_cols(dmt, &info); return r; } static int _set_task_device(struct dm_task *dmt, const char *name, int optional) { if (name) { if (!dm_task_set_name(dmt, name)) return_0; } else if (_switches[UUID_ARG]) { if (!dm_task_set_uuid(dmt, _uuid)) return_0; } else if (_switches[MAJOR_ARG] && _switches[MINOR_ARG]) { if (!dm_task_set_major(dmt, _int_args[MAJOR_ARG]) || !dm_task_set_minor(dmt, _int_args[MINOR_ARG])) return_0; } else if (!optional) { log_error("No device specified."); return 0; } return 1; } static int _set_task_add_node(struct dm_task *dmt) { if (!dm_task_set_add_node(dmt, DEFAULT_DM_ADD_NODE)) return_0; if (_switches[ADD_NODE_ON_RESUME_ARG] && !dm_task_set_add_node(dmt, DM_ADD_NODE_ON_RESUME)) return_0; if (_switches[ADD_NODE_ON_CREATE_ARG] && !dm_task_set_add_node(dmt, DM_ADD_NODE_ON_CREATE)) return_0; return 1; } static int _load(CMD_ARGS) { int r = 0; struct dm_task *dmt; const char *file = NULL; const char *name = NULL; if (_switches[NOTABLE_ARG]) { log_error("--notable only available when creating new device."); return 0; } if (!_switches[UUID_ARG] && !_switches[MAJOR_ARG]) { if (!argc) { log_error("Please specify device."); return 0; } name = argv[0]; argc--; argv++; } else if (argc > 1) { log_error("Too many command line arguments."); return 0; } if (argc == 1) file = argv[0]; if (!(dmt = dm_task_create(DM_DEVICE_RELOAD))) return_0; if (!_set_task_device(dmt, name, 0)) goto_out; if (!_switches[NOTABLE_ARG] && !_parse_file(dmt, file)) goto_out; if (_switches[READ_ONLY] && !dm_task_set_ro(dmt)) goto_out; if (_switches[NOOPENCOUNT_ARG] && !dm_task_no_open_count(dmt)) goto_out; if (_switches[INACTIVE_ARG] && !dm_task_query_inactive_table(dmt)) goto_out; if (_switches[CHECKS_ARG] && !dm_task_enable_checks(dmt)) goto_out; if (!_task_run(dmt)) goto_out; r = 1; if (_switches[VERBOSE_ARG]) r = _display_info(dmt); out: dm_task_destroy(dmt); return r; } static int _create_one_device(const char *name, const char *file) { int r = 0; struct dm_task *dmt; uint32_t cookie = 0; uint16_t udev_flags = 0; if (!(dmt = dm_task_create(DM_DEVICE_CREATE))) return_0; if (!dm_task_set_name(dmt, name)) goto_out; if (_switches[UUID_ARG] && !dm_task_set_uuid(dmt, _uuid)) goto_out; if (!_switches[NOTABLE_ARG] && !_parse_file(dmt, file)) goto_out; if (_switches[READ_ONLY] && !dm_task_set_ro(dmt)) goto_out; if (_switches[MAJOR_ARG] && !dm_task_set_major(dmt, _int_args[MAJOR_ARG])) goto_out; if (_switches[MINOR_ARG] && !dm_task_set_minor(dmt, _int_args[MINOR_ARG])) goto_out; if (_switches[UID_ARG] && !dm_task_set_uid(dmt, _int_args[UID_ARG])) goto_out; if (_switches[GID_ARG] && !dm_task_set_gid(dmt, _int_args[GID_ARG])) goto_out; if (_switches[MODE_ARG] && !dm_task_set_mode(dmt, _int_args[MODE_ARG])) goto_out; if (_switches[NOOPENCOUNT_ARG] && !dm_task_no_open_count(dmt)) goto_out; if (_switches[INACTIVE_ARG] && !dm_task_query_inactive_table(dmt)) goto_out; if (_switches[READAHEAD_ARG] && !dm_task_set_read_ahead(dmt, _int_args[READAHEAD_ARG], _read_ahead_flags)) goto_out; if (_switches[NOUDEVRULES_ARG]) udev_flags |= DM_UDEV_DISABLE_DM_RULES_FLAG | DM_UDEV_DISABLE_SUBSYSTEM_RULES_FLAG; if (_switches[CHECKS_ARG] && !dm_task_enable_checks(dmt)) goto_out; if (!_set_task_add_node(dmt)) goto_out; if (_udev_cookie) cookie = _udev_cookie; if (_udev_only) udev_flags |= DM_UDEV_DISABLE_LIBRARY_FALLBACK; if (_switches[NOTABLE_ARG] || !_added_target) cookie = 0; // ADD event -> no udev event handling else if (!dm_task_set_cookie(dmt, &cookie, udev_flags)) goto_out; if (!_task_run(dmt)) goto_out; r = 1; out: if (!_udev_cookie) (void) dm_udev_wait(cookie); if (r && _switches[VERBOSE_ARG]) r = _display_info(dmt); dm_task_destroy(dmt); return r; } #define DEFAULT_BUF_SIZE 4096 static char *_slurp_stdin(void) { char *newbuf, *buf, *pos; size_t bufsize = DEFAULT_BUF_SIZE; size_t total = 0; ssize_t n = 0; if (!(buf = malloc(bufsize))) { log_error("Buffer memory allocation failed."); return NULL; } pos = buf; do { do n = read(STDIN_FILENO, pos, (size_t) bufsize - total - 1); while ((n < 0) && ((errno == EINTR) || (errno == EAGAIN))); if (n < 0) { log_error("Read from stdin aborted: %s", strerror(errno)); free(buf); return NULL; } if (!n) break; total += n; pos += n; if (total == bufsize - 1) { bufsize *= 2; if (!(newbuf = realloc(buf, bufsize))) { log_error("Buffer memory extension to %" PRIsize_t " bytes failed.", bufsize); free(buf); return NULL; } buf = newbuf; } } while (1); buf[total] = '\0'; return buf; } static int _create_concise(const struct command *cmd, int argc, char **argv) { char *concise_format; char *c, *n; char *fields[5] = { NULL }; /* name,uuid,minor,flags,table */ int f = 0; if (_switches[TABLE_ARG] || _switches[MINOR_ARG] || _switches[UUID_ARG] || _switches[NOTABLE_ARG] || _switches[INACTIVE_ARG]){ log_error("--concise is incompatible with --[no]table, --minor, --uuid and --inactive."); return 0; } if (argc) concise_format = argv[0]; else if (!(concise_format = _slurp_stdin())) return_0; /* Work through input string c, parsing into sets of 5 fields. */ /* Strip out any characters quoted by backslashes in-place. */ /* Read characters from c and prepare them in situ for final processing at n */ c = n = fields[f] = concise_format; while (*c) { /* Quoted character? Skip past quote. */ if (*c == '\\') { if (!*(++c)) { log_error("Backslash must be followed by another character at end of string."); *n = '\0'; log_error("Parsed %d fields: name: %s uuid: %s minor: %s flags: %s table: %s", f + 1, fields[0], fields[1], fields[2], fields[3], fields[4]); goto out; } /* Don't interpret next character */ *n++ = *c++; continue; } /* Comma marking end of field? */ if (*c == ',' && f < 4) { /* Terminate string */ *n++ = '\0', c++; /* Store start of next field */ fields[++f] = n; /* Skip any whitespace after field-separating commas */ while(isspace(*c)) c++; continue; } /* Comma marking end of a table line? */ if (*c == ',' && f >= 4) { /* Replace comma with newline to match standard table input format */ *n++ = '\n', c++; continue; } /* Semi-colon marking end of device? */ if (*c == ';' || *(c + 1) == '\0') { /* End of input? */ if (*c != ';') /* Copy final character */ *n++ = *c; /* Terminate string */ *n++ = '\0', c++; if (f != 4) { log_error("Five comma-separated fields are required for each device"); log_error("Parsed %d fields: name: %s uuid: %s minor: %s flags: %s table: %s", f + 1, fields[0], fields[1], fields[2], fields[3], fields[4]); goto out; } /* Set up parameters the same way as when specified directly on command line */ if (*fields[1]) { _switches[UUID_ARG] = 1; _uuid = fields[1]; } if (*fields[2]) { _switches[MINOR_ARG] = 1; _int_args[MINOR_ARG] = atoi(fields[2]); } if (!strcmp(fields[3], "ro")) _switches[READ_ONLY] = 1; else if (*fields[3] && strcmp(fields[3], "rw")) { log_error("Invalid flags parameter '%s' must be 'ro' or 'rw' or empty.", fields[3]); _uuid = NULL; goto out; } _table = fields[4]; /* Create the device */ if (!_create_one_device(fields[0], NULL)) { _uuid = _table = NULL; goto out; } /* Clear parameters ready for any further devices */ _switches[UUID_ARG] = 0; _switches[MINOR_ARG] = 0; _switches[READ_ONLY] = 0; _uuid = _table = NULL; f = 0; fields[0] = n; fields[1] = fields[2] = fields[3] = fields[4] = NULL; /* Skip any whitespace after semi-colons */ while(isspace(*c)) c++; continue; } /* Normal character */ *n++ = *c++; } if (fields[0] != n) { *n = '\0'; log_error("Incomplete entry: five comma-separated fields are required for each device"); log_error("Parsed %d fields: name: %s uuid: %s minor: %s flags: %s table: %s", f + 1, fields[0], fields[1], fields[2], fields[3], fields[4]); goto out; } return 1; out: if (!argc) free(concise_format); return 0; } static int _create(CMD_ARGS) { const char *name; const char *file = NULL; if (_switches[CONCISE_ARG]) { if (argc > 1) { log_error("dmsetup create --concise takes at most one argument"); return 0; } return _create_concise(cmd, argc, argv); } if (!argc) { log_error("Please provide a name for the new device."); return 0; } name = argv[0]; if (argc == 2) file = argv[1]; return _create_one_device(name, file); } static int _do_rename(const char *name, const char *new_name, const char *new_uuid) { int r = 0; struct dm_task *dmt; uint32_t cookie = 0; uint16_t udev_flags = 0; if (!(dmt = dm_task_create(DM_DEVICE_RENAME))) return_0; /* FIXME Kernel doesn't support uuid or device number here yet */ if (!_set_task_device(dmt, name, 0)) goto_out; if (new_uuid) { if (!dm_task_set_newuuid(dmt, new_uuid)) goto_out; } else if (!new_name || !dm_task_set_newname(dmt, new_name)) goto_out; if (_switches[NOOPENCOUNT_ARG] && !dm_task_no_open_count(dmt)) goto_out; if (_switches[INACTIVE_ARG] && !dm_task_query_inactive_table(dmt)) goto_out; if (_switches[CHECKS_ARG] && !dm_task_enable_checks(dmt)) goto_out; if (_switches[NOUDEVRULES_ARG]) udev_flags |= DM_UDEV_DISABLE_DM_RULES_FLAG | DM_UDEV_DISABLE_SUBSYSTEM_RULES_FLAG; if (_udev_cookie) cookie = _udev_cookie; if (_udev_only) udev_flags |= DM_UDEV_DISABLE_LIBRARY_FALLBACK; if (!dm_task_set_cookie(dmt, &cookie, udev_flags) || !_task_run(dmt)) goto_out; r = 1; out: if (!_udev_cookie) (void) dm_udev_wait(cookie); dm_task_destroy(dmt); return r; } static int _rename(CMD_ARGS) { const char *name = (argc == 2) ? argv[0] : NULL; return _switches[SETUUID_ARG] ? _do_rename(name, NULL, argv[argc - 1]) : _do_rename(name, argv[argc - 1], NULL); } static int _message(CMD_ARGS) { int r = 0, i; size_t sz = 1; struct dm_task *dmt; char *str; const char *response; uint64_t sector; char *endptr; if (!(dmt = dm_task_create(DM_DEVICE_TARGET_MSG))) return_0; if (_switches[UUID_ARG] || _switches[MAJOR_ARG]) { if (!_set_task_device(dmt, NULL, 0)) goto_out; } else { if (!_set_task_device(dmt, argv[0], 0)) goto_out; argc--; argv++; } errno = 0; sector = strtoull(argv[0], &endptr, 10); if (errno || *endptr || endptr == argv[0]) { log_error("Invalid sector."); goto out; } if (!dm_task_set_sector(dmt, sector)) goto_out; argc--; argv++; if (argc <= 0) log_error("No message supplied."); for (i = 0; i < argc; i++) sz += strlen(argv[i]) + 1; if (!(str = dm_zalloc(sz))) { log_error("Message string allocation failed."); goto out; } for (i = 0; i < argc; i++) { if (i) strcat(str, " "); strcat(str, argv[i]); } i = dm_task_set_message(dmt, str); free(str); if (!i) goto_out; if (_switches[NOOPENCOUNT_ARG] && !dm_task_no_open_count(dmt)) goto_out; if (_switches[INACTIVE_ARG] && !dm_task_query_inactive_table(dmt)) goto_out; if (_switches[CHECKS_ARG] && !dm_task_enable_checks(dmt)) goto_out; if (!_task_run(dmt)) goto_out; if ((response = dm_task_get_message_response(dmt))) { if (!*response || response[strlen(response) - 1] == '\n') fputs(response, stdout); else puts(response); } r = 1; out: dm_task_destroy(dmt); return r; } static int _setgeometry(CMD_ARGS) { int r = 0; struct dm_task *dmt; if (!(dmt = dm_task_create(DM_DEVICE_SET_GEOMETRY))) return_0; if (_switches[UUID_ARG] || _switches[MAJOR_ARG]) { if (!_set_task_device(dmt, NULL, 0)) goto_out; } else { if (!_set_task_device(dmt, argv[0], 0)) goto_out; argc--; argv++; } if (!dm_task_set_geometry(dmt, argv[0], argv[1], argv[2], argv[3])) goto_out; if (_switches[NOOPENCOUNT_ARG] && !dm_task_no_open_count(dmt)) goto_out; if (_switches[INACTIVE_ARG] && !dm_task_query_inactive_table(dmt)) goto_out; if (_switches[CHECKS_ARG] && !dm_task_enable_checks(dmt)) goto_out; /* run the task */ if (!_task_run(dmt)) goto_out; r = 1; out: dm_task_destroy(dmt); return r; } static int _splitname(CMD_ARGS) { struct dmsetup_report_obj obj = { NULL }; int r; if (!(obj.split_name = _get_split_name((argc == 2) ? argv[1] : "LVM", argv[0], '\0'))) return_0; r = dm_report_object(_report, &obj); _destroy_split_name(obj.split_name); return r; } static uint32_t _get_cookie_value(const char *str_value) { unsigned long int value; char *p; errno = 0; value = strtoul(str_value, &p, 0); if (errno || !value || (*p) || (value > UINT32_MAX)) { log_error("Incorrect cookie value."); return 0; } return (uint32_t) value; } static int _udevflags(CMD_ARGS) { uint32_t cookie; uint16_t flags; int i; static const char _dm_flag_names[][32] = { "DISABLE_DM_RULES", "DISABLE_SUBSYSTEM_RULES", "DISABLE_DISK_RULES", "DISABLE_OTHER_RULES", "LOW_PRIORITY", "DISABLE_LIBRARY_FALLBACK", "PRIMARY_SOURCE", }; if (!(cookie = _get_cookie_value(argv[0]))) return_0; flags = cookie >> DM_UDEV_FLAGS_SHIFT; for (i = 0; i < DM_UDEV_FLAGS_SHIFT; i++) if (1 << i & flags) { if (i < DM_UDEV_FLAGS_SHIFT / 2 && i < DM_ARRAY_SIZE(_dm_flag_names)) printf("DM_UDEV_%s_FLAG='1'\n", _dm_flag_names[i]); else if (i < DM_UDEV_FLAGS_SHIFT / 2) /* * This is just a fallback. Each new DM flag * should have its symbolic name assigned. */ printf("DM_UDEV_FLAG%d='1'\n", i); else /* * We can't assign symbolic names to subsystem * flags. Their semantics vary based on the * subsystem that is currently used. */ printf("DM_SUBSYSTEM_UDEV_FLAG%d='1'\n", i - DM_UDEV_FLAGS_SHIFT / 2); } return 1; } static int _udevcomplete(CMD_ARGS) { uint32_t cookie; if (!(cookie = _get_cookie_value(argv[0]))) return_0; printf("DM_COOKIE_COMPLETED=0x%-10x\n", cookie); /* * Strip flags from the cookie and use cookie magic instead. * If the cookie has non-zero prefix and the base is zero then * this one carries flags to control udev rules only and it is * not meant to be for notification. Return with success in this * situation. */ if (!(cookie &= ~DM_UDEV_FLAGS_MASK)) return 1; cookie |= DM_COOKIE_MAGIC << DM_UDEV_FLAGS_SHIFT; return dm_udev_complete(cookie); } #ifndef UDEV_SYNC_SUPPORT static const char _cmd_not_supported[] = "Command not supported. Recompile with \"--enable-udev_sync\" to enable."; static int _udevcreatecookie(CMD_ARGS) { log_error(_cmd_not_supported); return 0; } static int _udevreleasecookie(CMD_ARGS) { log_error(_cmd_not_supported); return 0; } static int _udevcomplete_all(CMD_ARGS) { log_error(_cmd_not_supported); return 0; } static int _udevcookies(CMD_ARGS) { log_error(_cmd_not_supported); return 0; } #else /* UDEV_SYNC_SUPPORT */ static int _set_up_udev_support(const char *dev_dir) { int dirs_diff; const char *env; size_t len = strlen(dev_dir), udev_dir_len = strlen(DM_UDEV_DEV_DIR); if (_switches[NOUDEVSYNC_ARG]) dm_udev_set_sync_support(0); if (!_udev_cookie) { env = getenv(DM_UDEV_COOKIE_ENV_VAR_NAME); if (env && *env && (_udev_cookie = _get_cookie_value(env))) log_debug("Using udev transaction 0x%08" PRIX32 " defined by %s environment variable.", _udev_cookie, DM_UDEV_COOKIE_ENV_VAR_NAME); } else if (_switches[UDEVCOOKIE_ARG]) log_debug("Using udev transaction 0x%08" PRIX32 " defined by --udevcookie option.", _udev_cookie); /* * Normally, there's always a fallback action by libdevmapper if udev * has not done its job correctly, e.g. the nodes were not created. * If using udev transactions by specifying existing cookie value, * we need to disable node creation by libdevmapper completely, * disabling any fallback actions, since any synchronization happens * at the end of the transaction only. We need to do this to prevent * races between udev and libdevmapper but only in case udev "dev path" * is the same as "dev path" used by libdevmapper. */ /* * DM_UDEV_DEV_DIR always has '/' at its end. * If the dev_dir does not have it, be sure * to make the right comparison without the '/' char! */ if (dev_dir[len - 1] != '/') udev_dir_len--; dirs_diff = udev_dir_len != len || strncmp(DM_UDEV_DEV_DIR, dev_dir, len); _udev_only = !dirs_diff && (_udev_cookie || !_switches[VERIFYUDEV_ARG]); if (dirs_diff) { log_debug("The path %s used for creating device nodes that is " "set via DM_DEV_DIR environment variable differs from " "the path %s that is used by udev. All warnings " "about udev not working correctly while processing " "particular nodes will be suppressed. These nodes " "and symlinks will be managed in each directory " "separately.", dev_dir, DM_UDEV_DEV_DIR); dm_udev_set_checking(0); } return 1; } static int _udevcreatecookie(CMD_ARGS) { uint32_t cookie; if (!dm_udev_create_cookie(&cookie)) return_0; if (cookie) printf("0x%08" PRIX32 "\n", cookie); return 1; } static int _udevreleasecookie(CMD_ARGS) { if (argv[0] && !(_udev_cookie = _get_cookie_value(argv[0]))) return_0; if (!_udev_cookie) { log_error("No udev transaction cookie given."); return 0; } return dm_udev_wait(_udev_cookie); } __attribute__((format(printf, 1, 2))) static char _yes_no_prompt(const char *prompt, ...) { int c = 0, ret = 0; va_list ap; do { if (c == '\n' || !c) { va_start(ap, prompt); vprintf(prompt, ap); va_end(ap); } if ((c = getchar()) == EOF) { ret = 'n'; break; } c = tolower(c); if ((c == 'y') || (c == 'n')) ret = c; } while (!ret || c != '\n'); if (c != '\n') putchar('\n'); return ret; } static int _udevcomplete_all(CMD_ARGS) { int max_id, id, sid; struct seminfo sinfo; struct semid_ds sdata; int counter = 0; int skipped = 0; unsigned age = 0; time_t t; if (argc == 1 && (sscanf(argv[0], "%u", &age) != 1)) { log_error("Failed to read age_in_minutes parameter."); return 0; } if (!_switches[YES_ARG]) { log_warn("WARNING: This operation will destroy all semaphores %s%.0d%swith keys " "that have a prefix %" PRIu16 " (0x%" PRIx16 ").", age ? "older than " : "", age, age ? " minutes " : "", DM_COOKIE_MAGIC, DM_COOKIE_MAGIC); if (_yes_no_prompt("Do you really want to continue? [y/n]: ") == 'n') { log_print("Semaphores with keys prefixed by %" PRIu16 " (0x%" PRIx16 ") NOT destroyed.", DM_COOKIE_MAGIC, DM_COOKIE_MAGIC); return 1; } } if ((max_id = semctl(0, 0, SEM_INFO, &sinfo)) < 0) { log_sys_error("semctl", "SEM_INFO"); return 0; } for (id = 0; id <= max_id; id++) { if ((sid = semctl(id, 0, SEM_STAT, &sdata)) < 0) continue; if (sdata.sem_perm.__key >> 16 == DM_COOKIE_MAGIC) { t = time(NULL); if (sdata.sem_ctime + age * 60 > t || sdata.sem_otime + age * 60 > t) { skipped++; continue; } if (semctl(sid, 0, IPC_RMID, 0) < 0) { log_error("Could not cleanup notification semaphore " "with semid %d and cookie value " FMTu32 " (0x" FMTx32 ").", sid, sdata.sem_perm.__key, sdata.sem_perm.__key); continue; } counter++; } } log_print("%d semaphores with keys prefixed by " FMTu16 " (0x" FMTx16 ") destroyed. %d skipped.", counter, DM_COOKIE_MAGIC, DM_COOKIE_MAGIC, skipped); return 1; } static int _udevcookies(CMD_ARGS) { int max_id, id, sid; struct seminfo sinfo; struct semid_ds sdata; int val; char otime_str[26], ctime_str[26]; char *otimes, *ctimes; if ((max_id = semctl(0, 0, SEM_INFO, &sinfo)) < 0) { log_sys_error("sem_ctl", "SEM_INFO"); return 0; } printf("Cookie Semid Value Last semop time Last change time\n"); for (id = 0; id <= max_id; id++) { if ((sid = semctl(id, 0, SEM_STAT, &sdata)) < 0) continue; if (sdata.sem_perm.__key >> 16 == DM_COOKIE_MAGIC) { if ((val = semctl(sid, 0, GETVAL)) < 0) { log_error("semid %d: sem_ctl failed for " "cookie 0x%" PRIx32 ": %s", sid, sdata.sem_perm.__key, strerror(errno)); continue; } if ((otimes = ctime_r((const time_t *) &sdata.sem_otime, (char *)&otime_str))) otime_str[strlen(otimes)-1] = '\0'; if ((ctimes = ctime_r((const time_t *) &sdata.sem_ctime, (char *)&ctime_str))) ctime_str[strlen(ctimes)-1] = '\0'; printf("0x%-10x %-10d %-10d %s %s\n", sdata.sem_perm.__key, sid, val, otimes ? : "unknown", ctimes? : "unknown"); } } return 1; } #endif /* UDEV_SYNC_SUPPORT */ static int _version(CMD_ARGS) { char version[80]; if (dm_get_library_version(version, sizeof(version))) printf("Library version: %s\n", version); if (!dm_driver_version(version, sizeof(version))) return_0; printf("Driver version: %s\n", version); /* don't output column headings for 'dmstats version'. */ if (_report) { dm_report_free(_report); _report = NULL; } return 1; } static int _simple(int task, const char *name, uint32_t event_nr, int display) { uint32_t cookie = 0; uint16_t udev_flags = 0; int udev_wait_flag = task == DM_DEVICE_RESUME || task == DM_DEVICE_REMOVE; int r = 0; struct dm_task *dmt; if (!(dmt = dm_task_create(task))) return_0; if (!_set_task_device(dmt, name, 0)) goto_out; if (event_nr && !dm_task_set_event_nr(dmt, event_nr)) goto_out; if (_switches[NOFLUSH_ARG] && !dm_task_no_flush(dmt)) goto_out; if (_switches[NOOPENCOUNT_ARG] && !dm_task_no_open_count(dmt)) goto_out; if (_switches[INACTIVE_ARG] && !dm_task_query_inactive_table(dmt)) goto_out; if (_switches[NOLOCKFS_ARG] && !dm_task_skip_lockfs(dmt)) goto_out; if (_switches[CHECKS_ARG] && !dm_task_enable_checks(dmt)) goto_out; /* FIXME: needs to coperate with udev */ if (!_set_task_add_node(dmt)) goto_out; if (_switches[READAHEAD_ARG] && !dm_task_set_read_ahead(dmt, _int_args[READAHEAD_ARG], _read_ahead_flags)) goto_out; if (_switches[NOUDEVRULES_ARG]) udev_flags |= DM_UDEV_DISABLE_DM_RULES_FLAG | DM_UDEV_DISABLE_SUBSYSTEM_RULES_FLAG; if (_udev_cookie) cookie = _udev_cookie; if (_udev_only) udev_flags |= DM_UDEV_DISABLE_LIBRARY_FALLBACK; if (udev_wait_flag && !dm_task_set_cookie(dmt, &cookie, udev_flags)) goto_out; if (_switches[RETRY_ARG] && task == DM_DEVICE_REMOVE) dm_task_retry_remove(dmt); if (_switches[DEFERRED_ARG] && (task == DM_DEVICE_REMOVE || task == DM_DEVICE_REMOVE_ALL)) dm_task_deferred_remove(dmt); r = _task_run(dmt); out: if (!_udev_cookie && udev_wait_flag) (void) dm_udev_wait(cookie); if (r && display && _switches[VERBOSE_ARG]) r = _display_info(dmt); dm_task_destroy(dmt); return r; } static int _suspend(CMD_ARGS) { return _simple(DM_DEVICE_SUSPEND, argc ? argv[0] : NULL, 0, 1); } static int _resume(CMD_ARGS) { return _simple(DM_DEVICE_RESUME, argc ? argv[0] : NULL, 0, 1); } static int _clear(CMD_ARGS) { return _simple(DM_DEVICE_CLEAR, argc ? argv[0] : NULL, 0, 1); } static int _wait(CMD_ARGS) { const char *name = NULL; if (!_switches[UUID_ARG] && !_switches[MAJOR_ARG]) { if (!argc) { log_error("No device specified."); return 0; } name = argv[0]; argc--, argv++; } return _simple(DM_DEVICE_WAITEVENT, name, (argc) ? (uint32_t) atoi(argv[argc - 1]) : 0, 1); } static int _process_all(const struct command *cmd, const char *subcommand, int argc, char **argv, int silent, int (*fn) (CMD_ARGS)) { int r = 1; struct dm_names *names; unsigned next = 0; struct dm_task *dmt; if (!(dmt = dm_task_create(DM_DEVICE_LIST))) return_0; if (_switches[CHECKS_ARG] && !dm_task_enable_checks(dmt)) goto_out; if (!_task_run(dmt)) { r = 0; goto_out; } if (!(names = dm_task_get_names(dmt))) { r = 0; goto_out; } if (!names->dev) { if (!silent) printf("No devices found\n"); goto out; } do { names = (struct dm_names *)((char *) names + next); if (!fn(cmd, subcommand, argc, argv, names, 1)) r = 0; next = names->next; } while (next); out: dm_task_destroy(dmt); return r; } static uint64_t _get_device_size(const char *name) { uint64_t start, length, size = UINT64_C(0); struct dm_info info; char *target_type, *params; struct dm_task *dmt; void *next = NULL; if (!(dmt = dm_task_create(DM_DEVICE_TABLE))) return_0; if (!_set_task_device(dmt, name, 0)) goto_out; if (_switches[NOOPENCOUNT_ARG] && !dm_task_no_open_count(dmt)) goto_out; if (_switches[INACTIVE_ARG] && !dm_task_query_inactive_table(dmt)) goto_out; if (_switches[CHECKS_ARG] && !dm_task_enable_checks(dmt)) goto_out; if (!_task_run(dmt)) goto_out; if (!dm_task_get_info(dmt, &info) || !info.exists) goto_out; do { next = dm_get_next_target(dmt, next, &start, &length, &target_type, ¶ms); size += length; } while (next); out: dm_task_destroy(dmt); return size; } static int _error_device(CMD_ARGS) { struct dm_task *dmt; const char *name; uint64_t size; int r = 0; name = names ? names->name : argv[0]; if (!name || !*name) { log_error("No device specified."); return 0; } size = _get_device_size(name); if (!(dmt = dm_task_create(DM_DEVICE_RELOAD))) return_0; if (!_set_task_device(dmt, name, 0)) goto_bad; if (!dm_task_add_target(dmt, UINT64_C(0), size, "error", "")) goto_bad; if (_switches[READ_ONLY] && !dm_task_set_ro(dmt)) goto_bad; if (_switches[NOOPENCOUNT_ARG] && !dm_task_no_open_count(dmt)) goto_bad; if (_switches[INACTIVE_ARG] && !dm_task_query_inactive_table(dmt)) goto_bad; if (_switches[CHECKS_ARG] && !dm_task_enable_checks(dmt)) goto_bad; if (!_task_run(dmt)) goto_bad; if (_switches[FORCE_ARG]) /* Avoid hang on flushing with --force */ _switches[NOLOCKFS_ARG] = _switches[NOFLUSH_ARG] = 1; if (!_simple(DM_DEVICE_RESUME, name, 0, 0)) { _simple(DM_DEVICE_CLEAR, name, 0, 0); goto_bad; } r = 1; bad: dm_task_destroy(dmt); return r; } static int _remove(CMD_ARGS) { if (_switches[FORCE_ARG] && argc) { /* * 'remove --force' option is doing 2 operations on the same device * this is not compatible with the use of --udevcookie/DM_UDEV_COOKIE. * Udevd collision could be partially avoided with --retry. */ if (_udev_cookie) log_warn("WARNING: Use of cookie and --force is not compatible."); (void) _error_device(cmd, NULL, argc, argv, NULL, 0); } return _simple(DM_DEVICE_REMOVE, argc ? argv[0] : NULL, 0, 0); } static int _count_devices(CMD_ARGS) { _num_devices++; return 1; } static int _remove_all(CMD_ARGS) { int r; /* Remove all closed devices */ r = _simple(DM_DEVICE_REMOVE_ALL, "", 0, 0) | dm_mknodes(NULL); if (!_switches[FORCE_ARG]) return r; _num_devices = 0; r |= _process_all(cmd, NULL, argc, argv, 1, _count_devices); /* No devices left? */ if (!_num_devices) return r; r |= _process_all(cmd, NULL, argc, argv, 1, _error_device); r |= _simple(DM_DEVICE_REMOVE_ALL, "", 0, 0) | dm_mknodes(NULL); _num_devices = 0; r |= _process_all(cmd, NULL, argc, argv, 1, _count_devices); if (!_num_devices) return r; log_error("Unable to remove %d device(s).", _num_devices); return r; } static void _display_dev(struct dm_task *dmt, const char *name) { struct dm_info info; if (dm_task_get_info(dmt, &info)) printf("%s\t(%u, %u)\n", name, info.major, info.minor); } static int _mknodes(CMD_ARGS) { return dm_mknodes(argc ? argv[0] : NULL); } static int _exec_command(const char *name) { static char path[PATH_MAX]; static char *args[ARGS_MAX + 1]; static int argc = 0; char *c; pid_t pid; if (argc < 0) return_0; if (!dm_mknodes(name)) return_0; if (dm_snprintf(path, sizeof(path), "%s/%s", dm_dir(), name) < 0) return_0; if (!argc) { c = _command_to_exec; while (argc < ARGS_MAX) { while (*c && isspace(*c)) c++; if (!*c) break; args[argc++] = c; while (*c && !isspace(*c)) c++; if (*c) *c++ = '\0'; } if (!argc) { argc = -1; return_0; } if (argc == ARGS_MAX) { log_error("Too many args to --exec."); argc = -1; return 0; } args[argc++] = path; args[argc] = NULL; } if (!(pid = fork())) { execvp(args[0], args); _exit(127); } else if (pid < (pid_t) 0) return 0; TEMP_FAILURE_RETRY(waitpid(pid, NULL, 0)); return 1; } /* * Print string s using a backslash to quote each character that has a special * meaning in the concise format - comma, semi-colon and backslash. */ static void _print_string_quoted(const char *s) { while (*s) { if (strchr(",;\\", *s)) putchar('\\'); putchar(*s++); } } static void hide_key(char *params, const char *name) { char *c = strstr(params, name); if (!c) return; c += strlen(name); /* key is optional */ c = strpbrk(c, " :"); if (!c || *c++ != ':') return; while (*c && *c != ' ') *c++ = '0'; } static int _status(CMD_ARGS) { int r = 0; struct dm_task *dmt; void *next = NULL; uint64_t start, length; char *target_type = NULL; char *params, *c; int cmdno; const char *name = NULL; int matched = 0; int ls_only = 0; int use_concise = 0; struct dm_info info; if (names) name = names->name; else { if (!argc && !_switches[UUID_ARG] && !_switches[MAJOR_ARG]) /* FIXME Respect deps in concise mode, so they are correctly ordered for recreation */ return _process_all(cmd, NULL, argc, argv, 0, _status); name = argv[0]; } if (!strcmp(cmd->name, "table")) { cmdno = DM_DEVICE_TABLE; /* --concise only applies to 'table' */ if (_switches[CONCISE_ARG]) use_concise = 1; } else cmdno = DM_DEVICE_STATUS; if (!strcmp(cmd->name, "ls")) ls_only = 1; if (!(dmt = dm_task_create(cmdno))) return_0; if (!_set_task_device(dmt, name, 0)) goto_out; if (_switches[NOOPENCOUNT_ARG] && !dm_task_no_open_count(dmt)) goto_out; if (_switches[INACTIVE_ARG] && !dm_task_query_inactive_table(dmt)) goto_out; if (_switches[CHECKS_ARG] && !dm_task_enable_checks(dmt)) goto_out; if (_switches[NOFLUSH_ARG] && !dm_task_no_flush(dmt)) goto_out; if (!strcmp(cmd->name, "measure") && !dm_task_ima_measurement(dmt)) goto_out; if (!_task_run(dmt)) goto_out; if (!dm_task_get_info(dmt, &info)) goto_out; if (!info.exists) { log_error("Device does not exist."); goto out; } if (!name) name = dm_task_get_name(dmt); /* Fetch targets and print 'em */ do { next = dm_get_next_target(dmt, next, &start, &length, &target_type, ¶ms); /* Skip if target type doesn't match */ if (_switches[TARGET_ARG] && (!target_type || strcmp(target_type, _target))) continue; if (ls_only) { if (!_switches[EXEC_ARG] || !_command_to_exec || _switches[VERBOSE_ARG]) _display_dev(dmt, name); next = NULL; } else if (!_switches[EXEC_ARG] || !_command_to_exec || _switches[VERBOSE_ARG]) { if (!use_concise) { if (!matched && _switches[VERBOSE_ARG]) _display_info(dmt); if (multiple_devices && !_switches[VERBOSE_ARG]) printf("%s: ", name); } else if (!matched) { /* * Before first target of device in concise output, * print basic device information in the appropriate format. * Separate devices by a semi-colon. */ if (_concise_output_produced) putchar(';'); _concise_output_produced = 1; _print_string_quoted(name); putchar(','); _print_string_quoted(dm_task_get_uuid(dmt)); printf(",%u,%s", info.minor, info.read_only ? "ro" : "rw"); } /* Next print any target-specific information */ if (target_type) { /* Suppress encryption keys */ if (!_switches[SHOWKEYS_ARG] && cmdno == DM_DEVICE_TABLE) { if (!strcmp(target_type, "crypt")) { c = params; while (*c && *c != ' ') c++; if (*c) c++; /* * Do not suppress kernel key references prefixed * with colon ':'. Displaying those references is * harmless. crypt target supports kernel keys * starting with v1.15.0 (merged in kernel 4.10) */ if (*c != ':') while (*c && *c != ' ') *c++ = '0'; } else if (!strcmp(target_type, "integrity")) { /* * "internal_hash", "journal_crypt" and "journal_mac" * params allow keys optionally in hexbyte * representation. */ hide_key(params, "internal_hash:"); hide_key(params, "journal_crypt:"); hide_key(params, "journal_mac:"); } } if (use_concise) putchar(','); printf(FMTu64 " " FMTu64 " %s ", start, length, target_type); if (use_concise) _print_string_quoted(params); else printf("%s", params); } /* --concise places all devices on a single output line */ if (!use_concise) putchar('\n'); } matched = 1; } while (next); if (multiple_devices && _switches[VERBOSE_ARG] && matched && !ls_only && (!use_concise || _switches[VERBOSE_ARG] > 1)) putchar('\n'); if (matched && _switches[EXEC_ARG] && _command_to_exec && !_exec_command(name)) goto_out; r = 1; out: dm_task_destroy(dmt); return r; } /* Show target names and their version numbers */ static int _targets(CMD_ARGS) { int r = 0; struct dm_task *dmt; struct dm_versions *target; struct dm_versions *last_target; if (!(dmt = dm_task_create(DM_DEVICE_LIST_VERSIONS))) return_0; if (_switches[CHECKS_ARG] && !dm_task_enable_checks(dmt)) goto_out; if (!_task_run(dmt)) goto_out; target = dm_task_get_versions(dmt); /* Fetch targets and print 'em */ do { last_target = target; printf("%-16s v%d.%d.%d\n", target->name, target->version[0], target->version[1], target->version[2]); target = (struct dm_versions *)((char *) target + target->next); } while (last_target != target); r = 1; out: dm_task_destroy(dmt); return r; } /* Show target names and their version numbers */ static int _target_version(CMD_ARGS) { int r = 0; struct dm_task *dmt; struct dm_versions *target; if (!(dmt = dm_task_create(DM_DEVICE_GET_TARGET_VERSION))) return_0; if (!dm_task_set_name(dmt, argv[0])) goto_out; if (!_task_run(dmt)) goto_out; target = dm_task_get_versions(dmt); printf("%-16s v%d.%d.%d\n", target->name, target->version[0], target->version[1], target->version[2]); r = 1; out: dm_task_destroy(dmt); return r; } static int _info(CMD_ARGS) { int r = 0; struct dm_task *dmt; char *name = NULL; if (names) name = names->name; else { if (!argc && !_switches[UUID_ARG] && !_switches[MAJOR_ARG]) return _process_all(cmd, NULL, argc, argv, 0, _info); name = argv[0]; } if (!(dmt = dm_task_create(DM_DEVICE_INFO))) return_0; if (!_set_task_device(dmt, name, 0)) goto_out; if (_switches[NOOPENCOUNT_ARG] && !dm_task_no_open_count(dmt)) goto_out; if (_switches[INACTIVE_ARG] && !dm_task_query_inactive_table(dmt)) goto_out; if (_switches[CHECKS_ARG] && !dm_task_enable_checks(dmt)) goto_out; if (!_task_run(dmt)) goto_out; r = _display_info(dmt); out: dm_task_destroy(dmt); return r; } static int _deps(CMD_ARGS) { int r = 0; uint32_t i; struct dm_deps *deps; struct dm_task *dmt; struct dm_info info; char *name = NULL; char dev_name[PATH_MAX]; int major, minor; if (names) name = names->name; else { if (!argc && !_switches[UUID_ARG] && !_switches[MAJOR_ARG]) return _process_all(cmd, NULL, argc, argv, 0, _deps); name = argv[0]; } if (!(dmt = dm_task_create(DM_DEVICE_DEPS))) return_0; if (!_set_task_device(dmt, name, 0)) goto_out; if (_switches[NOOPENCOUNT_ARG] && !dm_task_no_open_count(dmt)) goto_out; if (_switches[INACTIVE_ARG] && !dm_task_query_inactive_table(dmt)) goto_out; if (_switches[CHECKS_ARG] && !dm_task_enable_checks(dmt)) goto_out; if (!_task_run(dmt)) goto_out; if (!dm_task_get_info(dmt, &info)) goto_out; if (!(deps = dm_task_get_deps(dmt))) goto_out; if (!info.exists) { printf("Device does not exist.\n"); r = 1; goto out; } if (_switches[VERBOSE_ARG]) _display_info(dmt); if (multiple_devices && !_switches[VERBOSE_ARG]) printf("%s: ", name); printf("%d dependencies\t:", deps->count); for (i = 0; i < deps->count; i++) { major = (int) MAJOR(deps->device[i]); minor = (int) MINOR(deps->device[i]); if ((_dev_name_type == DN_BLK || _dev_name_type == DN_MAP) && dm_device_get_name(major, minor, _dev_name_type == DN_BLK, dev_name, PATH_MAX)) printf(" (%s)", dev_name); else printf(" (%d, %d)", major, minor); } putchar('\n'); if (multiple_devices && _switches[VERBOSE_ARG]) putchar('\n'); r = 1; out: dm_task_destroy(dmt); return r; } static int _display_name(CMD_ARGS) { char dev_name[PATH_MAX]; if (!names) return 1; if ((_dev_name_type == DN_BLK || _dev_name_type == DN_MAP) && dm_device_get_name((int) MAJOR(names->dev), (int) MINOR(names->dev), _dev_name_type == DN_BLK, dev_name, PATH_MAX)) printf("%s\t(%s)\n", names->name, dev_name); else printf("%s\t(%d:%d)\n", names->name, (int) MAJOR(names->dev), (int) MINOR(names->dev)); return 1; } /* * Tree drawing code */ enum { TR_DEVICE=0, /* display device major:minor number */ TR_BLKDEVNAME, /* display device kernel name */ TR_TABLE, TR_STATUS, TR_ACTIVE, TR_RW, TR_OPENCOUNT, TR_UUID, TR_COMPACT, TR_TRUNCATE, TR_BOTTOMUP, NUM_TREEMODE, }; static int _tree_switches[NUM_TREEMODE]; #define TR_PRINT_ATTRIBUTE ( _tree_switches[TR_ACTIVE] || \ _tree_switches[TR_RW] || \ _tree_switches[TR_OPENCOUNT] || \ _tree_switches[TR_UUID] ) #define TR_PRINT_TARGETS ( _tree_switches[TR_TABLE] || \ _tree_switches[TR_STATUS] ) /* Compact - fewer newlines */ #define TR_PRINT_COMPACT (_tree_switches[TR_COMPACT] && \ !TR_PRINT_ATTRIBUTE && \ !TR_PRINT_TARGETS) /* FIXME Get rid of this */ #define MAX_DEPTH 100 /* Drawing character definition from pstree */ /* [pstree comment] UTF-8 defines by Johan Myreen, updated by Ben Winslow */ #define UTF_V "\342\224\202" /* U+2502, Vertical line drawing char */ #define UTF_VR "\342\224\234" /* U+251C, Vertical and right */ #define UTF_H "\342\224\200" /* U+2500, Horizontal */ #define UTF_UR "\342\224\224" /* U+2514, Up and right */ #define UTF_HD "\342\224\254" /* U+252C, Horizontal and down */ #define VT_BEG "\033(0\017" /* use graphic chars */ #define VT_END "\033(B" /* back to normal char set */ #define VT_V "x" /* see UTF definitions above */ #define VT_VR "t" #define VT_H "q" #define VT_UR "m" #define VT_HD "w" static const struct { const char empty_2[16]; /* */ const char branch_2[16]; /* |- */ const char vert_2[16]; /* | */ const char last_2[16]; /* `- */ const char single_3[16]; /* --- */ const char first_3[16]; /* -+- */ } _tsym_ascii = { " ", "|-", "| ", "`-", "---", "-+-" }, _tsym_utf = { " ", UTF_VR UTF_H, UTF_V " ", UTF_UR UTF_H, UTF_H UTF_H UTF_H, UTF_H UTF_HD UTF_H }, _tsym_vt100 = { " ", VT_BEG VT_VR VT_H VT_END, VT_BEG VT_V VT_END " ", VT_BEG VT_UR VT_H VT_END, VT_BEG VT_H VT_H VT_H VT_END, VT_BEG VT_H VT_HD VT_H VT_END }, *_tsym = &_tsym_ascii; /* * Tree drawing functions. */ /* FIXME Get rid of these statics - use dynamic struct */ /* FIXME Explain what these vars are for */ static int _tree_width[MAX_DEPTH], _tree_more[MAX_DEPTH]; static int _termwidth = 80; /* Maximum output width */ static int _cur_x = 1; /* Current horizontal output position */ static char _last_char = 0; static void _out_char(const unsigned c) { /* Only first UTF-8 char counts */ _cur_x += ((c & 0xc0) != 0x80); if (!_tree_switches[TR_TRUNCATE]) { putchar((int) c); return; } /* Truncation? */ if (_cur_x <= _termwidth) putchar((int) c); if (_cur_x == _termwidth + 1 && ((c & 0xc0) != 0x80)) { if (_last_char || (c & 0x80)) { putchar('.'); putchar('.'); putchar('.'); } else { _last_char = c; _cur_x--; } } } static void _out_string(const char *str) { while (*str) _out_char((unsigned char) *str++); } /* non-negative integers only */ static unsigned _out_int(unsigned num) { unsigned digits = 0; unsigned divi; if (!num) { _out_char('0'); return 1; } /* non zero case */ for (divi = 1; num / divi; divi *= 10) digits++; for (divi /= 10; divi; divi /= 10) _out_char('0' + (num / divi) % 10); return digits; } static void _out_newline(void) { if (_last_char && _cur_x == _termwidth) putchar(_last_char); _last_char = 0; putchar('\n'); _cur_x = 1; } static void _out_prefix(unsigned depth) { unsigned x, d; for (d = 0; d < depth; d++) { for (x = _tree_width[d] + 1; x > 0; x--) _out_char(' '); _out_string(d == depth - 1 ? !_tree_more[depth] ? _tsym->last_2 : _tsym->branch_2 : _tree_more[d + 1] ? _tsym->vert_2 : _tsym->empty_2); } } static void _out_string_delim(int attr) { _out_string(attr ? ", " : " ["); } /* * Display tree */ static void _display_tree_attributes(struct dm_tree_node *node) { int attr = 0; const char *uuid; const struct dm_info *info; uuid = dm_tree_node_get_uuid(node); info = dm_tree_node_get_info(node); if (!info->exists) return; if (_tree_switches[TR_ACTIVE]) { _out_string_delim(attr++); _out_string(info->suspended ? "SUSPENDED" : "ACTIVE"); } if (_tree_switches[TR_RW]) { _out_string_delim(attr++); _out_string(info->read_only ? "RO" : "RW"); } if (_tree_switches[TR_OPENCOUNT]) { _out_string_delim(attr++); (void) _out_int((unsigned) info->open_count); } if (_tree_switches[TR_UUID]) { _out_string_delim(attr++); _out_string(uuid && *uuid ? uuid : ""); } if (attr) _out_char(']'); } /* FIXME Display table or status line. (Disallow both?) */ static void _display_tree_targets(struct dm_tree_node *node, unsigned depth) { } static void _display_tree_node(struct dm_tree_node *node, unsigned depth, unsigned first_child __attribute__((unused)), unsigned last_child, unsigned has_children) { int offset; const char *name; const struct dm_info *info; int first_on_line = 0; char dev_name[PATH_MAX]; /* Sub-tree for targets has 2 more depth */ if (depth + 2 > MAX_DEPTH) return; name = dm_tree_node_get_name(node); if ((!name || !*name) && (!_tree_switches[TR_DEVICE] && !_tree_switches[TR_BLKDEVNAME])) return; /* Indicate whether there are more nodes at this depth */ _tree_more[depth] = !last_child; _tree_width[depth] = 0; if (_cur_x == 1) first_on_line = 1; if (!TR_PRINT_COMPACT || first_on_line) _out_prefix(depth); /* Remember the starting point for compact */ offset = _cur_x; if (TR_PRINT_COMPACT && !first_on_line) _out_string(_tree_more[depth] ? _tsym->first_3 : _tsym->single_3); /* display node */ if (name) _out_string(name); info = dm_tree_node_get_info(node); if (_tree_switches[TR_BLKDEVNAME] && dm_device_get_name(info->major, info->minor, 1, dev_name, PATH_MAX)) { _out_string(name ? " <" : "<"); _out_string(dev_name); _out_char('>'); } if (_tree_switches[TR_DEVICE]) { _out_string(name ? " (" : "("); (void) _out_int(info->major); _out_char(':'); (void) _out_int(info->minor); _out_char(')'); } /* display additional info */ if (TR_PRINT_ATTRIBUTE) _display_tree_attributes(node); if (TR_PRINT_COMPACT) _tree_width[depth] = _cur_x - offset; if (!TR_PRINT_COMPACT || !has_children) _out_newline(); if (TR_PRINT_TARGETS) { _tree_more[depth + 1] = has_children; _display_tree_targets(node, depth + 2); } } /* * Walk the dependency tree */ static void _display_tree_walk_children(struct dm_tree_node *node, unsigned depth) { struct dm_tree_node *child, *next_child; void *handle = NULL; uint32_t inverted = _tree_switches[TR_BOTTOMUP]; unsigned first_child = 1; unsigned has_children; next_child = dm_tree_next_child(&handle, node, inverted); while ((child = next_child)) { next_child = dm_tree_next_child(&handle, node, inverted); has_children = dm_tree_node_num_children(child, inverted) ? 1 : 0; _display_tree_node(child, depth, first_child, next_child ? 0U : 1U, has_children); if (has_children) _display_tree_walk_children(child, depth + 1); first_child = 0; } } static int _add_dep(CMD_ARGS) { if (names && !dm_tree_add_dev(_dtree, (unsigned) MAJOR(names->dev), (unsigned) MINOR(names->dev))) return_0; return 1; } /* * Create and walk dependency tree * * An incomplete _dtree may still be used by the caller, * but the error must be reported. */ static int _build_whole_deptree(const struct command *cmd) { if (_dtree) return 1; if (!(_dtree = dm_tree_create())) return_0; if (!_process_all(cmd, NULL, 0, NULL, 0, _add_dep)) return_0; return 1; } static int _display_tree(CMD_ARGS) { int r; r = _build_whole_deptree(cmd); if (_dtree) _display_tree_walk_children(dm_tree_find_node(_dtree, 0, 0), 0); return r; } /* * Report device information */ /* dm specific display functions */ static int _int32_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { const int32_t value = *(const int32_t *)data; return dm_report_field_int32(rh, field, &value); } static int _uint32_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { const uint32_t value = *(const int32_t *)data; return dm_report_field_uint32(rh, field, &value); } static int _show_units(void) { /* --nosuffix overrides --units */ if (_switches[NOSUFFIX_ARG]) return_0; return (_int_args[UNITS_ARG]) ? 1 : 0; } static int _dm_name_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { const char *name = dm_task_get_name((const struct dm_task *) data); return dm_report_field_string(rh, field, &name); } static int _dm_mangled_name_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { char *name; int r = 0; if ((name = dm_task_get_name_mangled((const struct dm_task *) data))) { r = dm_report_field_string(rh, field, (const char * const *) &name); free(name); } return r; } static int _dm_unmangled_name_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { char *name; int r = 0; if ((name = dm_task_get_name_unmangled((const struct dm_task *) data))) { r = dm_report_field_string(rh, field, (const char * const *) &name); free(name); } return r; } static int _dm_uuid_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { const char *uuid = dm_task_get_uuid((const struct dm_task *) data); if (!uuid || !*uuid) uuid = ""; return dm_report_field_string(rh, field, &uuid); } static int _dm_mangled_uuid_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { char *uuid; int r = 0; if ((uuid = dm_task_get_uuid_mangled((const struct dm_task *) data))) { r = dm_report_field_string(rh, field, (const char * const *) &uuid); free(uuid); } return r; } static int _dm_unmangled_uuid_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { char *uuid; int r = 0; if ((uuid = dm_task_get_uuid_unmangled((const struct dm_task *) data))) { r = dm_report_field_string(rh, field, (const char * const *) &uuid); free(uuid); } return r; } static int _dm_read_ahead_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { uint32_t value; if (!dm_task_get_read_ahead((const struct dm_task *) data, &value)) value = 0; return dm_report_field_uint32(rh, field, &value); } static int _dm_blk_name_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { char dev_name[PATH_MAX]; const char *s = dev_name; const struct dm_info *info = data; if (!dm_device_get_name(info->major, info->minor, 1, dev_name, PATH_MAX)) { log_error("Could not resolve block device name for %d:%d.", info->major, info->minor); return 0; } return dm_report_field_string(rh, field, &s); } static int _dm_info_status_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { char buf[5]; const char *s = buf; const struct dm_info *info = data; buf[0] = info->live_table ? 'L' : '-'; buf[1] = info->inactive_table ? 'I' : '-'; buf[2] = info->suspended ? 's' : '-'; buf[3] = info->read_only ? 'r' : 'w'; buf[4] = '\0'; return dm_report_field_string(rh, field, &s); } static int _dm_info_table_loaded_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { const struct dm_info *info = data; if (info->live_table) { if (info->inactive_table) dm_report_field_set_value(field, "Both", NULL); else dm_report_field_set_value(field, "Live", NULL); return 1; } if (info->inactive_table) dm_report_field_set_value(field, "Inactive", NULL); else dm_report_field_set_value(field, "None", NULL); return 1; } static int _dm_info_suspended_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { const struct dm_info *info = data; if (info->suspended) dm_report_field_set_value(field, "Suspended", NULL); else dm_report_field_set_value(field, "Active", NULL); return 1; } static int _dm_info_read_only_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { const struct dm_info *info = data; if (info->read_only) dm_report_field_set_value(field, "Read-only", NULL); else dm_report_field_set_value(field, "Writeable", NULL); return 1; } static int _dm_info_devno_disp(struct dm_report *rh, struct dm_pool *mem, struct dm_report_field *field, const void *data, void *private) { char buf[PATH_MAX], *repstr; const struct dm_info *info = data; if (!dm_pool_begin_object(mem, 8)) { log_error("dm_pool_begin_object failed"); return 0; } if (private) { if (!dm_device_get_name(info->major, info->minor, 1, buf, PATH_MAX)) { stack; goto out_abandon; } } else { if (dm_snprintf(buf, sizeof(buf), "%d:%d", info->major, info->minor) < 0) { log_error("dm_pool_alloc failed"); goto out_abandon; } } if (!dm_pool_grow_object(mem, buf, strlen(buf) + 1)) { log_error("dm_pool_grow_object failed"); goto out_abandon; } repstr = dm_pool_end_object(mem); dm_report_field_set_value(field, repstr, repstr); return 1; out_abandon: dm_pool_abandon_object(mem); return 0; } static int _dm_tree_names(struct dm_report *rh, struct dm_pool *mem, struct dm_report_field *field, const void *data, void *private, unsigned inverted) { const struct dm_tree_node *node = data; struct dm_tree_node *parent; void *t = NULL; const char *name; int first_node = 1; char *repstr; if (!dm_pool_begin_object(mem, 16)) { log_error("dm_pool_begin_object failed"); return 0; } while ((parent = dm_tree_next_child(&t, node, inverted))) { name = dm_tree_node_get_name(parent); if (!name || !*name) continue; if (!first_node && !dm_pool_grow_object(mem, ",", 1)) { log_error("dm_pool_grow_object failed"); goto out_abandon; } if (!dm_pool_grow_object(mem, name, 0)) { log_error("dm_pool_grow_object failed"); goto out_abandon; } if (first_node) first_node = 0; } if (!dm_pool_grow_object(mem, "\0", 1)) { log_error("dm_pool_grow_object failed"); goto out_abandon; } repstr = dm_pool_end_object(mem); dm_report_field_set_value(field, repstr, repstr); return 1; out_abandon: dm_pool_abandon_object(mem); return 0; } static int _dm_deps_names_disp(struct dm_report *rh, struct dm_pool *mem, struct dm_report_field *field, const void *data, void *private) { return _dm_tree_names(rh, mem, field, data, private, 0); } static int _dm_tree_parents_names_disp(struct dm_report *rh, struct dm_pool *mem, struct dm_report_field *field, const void *data, void *private) { return _dm_tree_names(rh, mem, field, data, private, 1); } static int _dm_tree_parents_devs_disp(struct dm_report *rh, struct dm_pool *mem, struct dm_report_field *field, const void *data, void *private) { const struct dm_tree_node *node = data; struct dm_tree_node *parent; void *t = NULL; const struct dm_info *info; int first_node = 1; char buf[DM_MAX_TYPE_NAME], *repstr; if (!dm_pool_begin_object(mem, 16)) { log_error("dm_pool_begin_object failed"); return 0; } while ((parent = dm_tree_next_child(&t, node, 1))) { info = dm_tree_node_get_info(parent); if (!info->major && !info->minor) continue; if (!first_node && !dm_pool_grow_object(mem, ",", 1)) { log_error("dm_pool_grow_object failed"); goto out_abandon; } if (dm_snprintf(buf, sizeof(buf), "%d:%d", info->major, info->minor) < 0) { log_error("dm_snprintf failed"); goto out_abandon; } if (!dm_pool_grow_object(mem, buf, 0)) { log_error("dm_pool_grow_object failed"); goto out_abandon; } if (first_node) first_node = 0; } if (!dm_pool_grow_object(mem, "\0", 1)) { log_error("dm_pool_grow_object failed"); goto out_abandon; } repstr = dm_pool_end_object(mem); dm_report_field_set_value(field, repstr, repstr); return 1; out_abandon: dm_pool_abandon_object(mem); return 0; } static int _dm_tree_parents_count_disp(struct dm_report *rh, struct dm_pool *mem, struct dm_report_field *field, const void *data, void *private) { const struct dm_tree_node *node = data; int num_parent = dm_tree_node_num_children(node, 1); return dm_report_field_int(rh, field, &num_parent); } static int _dm_deps_disp_common(struct dm_report *rh, struct dm_pool*mem, struct dm_report_field *field, const void *data, void *private, int disp_blk_dev_names) { const struct dm_deps *deps = data; char buf[PATH_MAX], *repstr; int major, minor; unsigned i; if (!dm_pool_begin_object(mem, 16)) { log_error("dm_pool_begin_object failed"); return 0; } for (i = 0; i < deps->count; i++) { major = (int) MAJOR(deps->device[i]); minor = (int) MINOR(deps->device[i]); if (disp_blk_dev_names) { if (!dm_device_get_name(major, minor, 1, buf, PATH_MAX)) { log_error("Could not resolve block device " "name for %d:%d.", major, minor); goto out_abandon; } } else if (dm_snprintf(buf, sizeof(buf), "%d:%d", major, minor) < 0) { log_error("dm_snprintf failed"); goto out_abandon; } if (!dm_pool_grow_object(mem, buf, 0)) { log_error("dm_pool_grow_object failed"); goto out_abandon; } if (i + 1 < deps->count && !dm_pool_grow_object(mem, ",", 1)) { log_error("dm_pool_grow_object failed"); goto out_abandon; } } if (!dm_pool_grow_object(mem, "\0", 1)) { log_error("dm_pool_grow_object failed"); goto out_abandon; } repstr = dm_pool_end_object(mem); dm_report_field_set_value(field, repstr, repstr); return 1; out_abandon: dm_pool_abandon_object(mem); return 0; } static int _dm_deps_disp(struct dm_report *rh, struct dm_pool *mem, struct dm_report_field *field, const void *data, void *private) { return _dm_deps_disp_common(rh, mem, field, data, private, 0); } static int _dm_deps_blk_names_disp(struct dm_report *rh, struct dm_pool *mem, struct dm_report_field *field, const void *data, void *private) { return _dm_deps_disp_common(rh, mem, field, data, private, 1); } static int _dm_subsystem_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { return dm_report_field_string(rh, field, (const char *const *) data); } static int _dm_vg_name_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { return dm_report_field_string(rh, field, (const char *const *) data); } static int _dm_lv_name_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { return dm_report_field_string(rh, field, (const char *const *) data); } static int _dm_lv_layer_name_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { return dm_report_field_string(rh, field, (const char *const *) data); } /** * All _dm_stats_*_disp functions for basic counters are identical: * obtain the value for the current region and area and pass it to * dm_report_field_uint64(). */ #define MK_STATS_COUNTER_DISP_FN(counter) \ static int _dm_stats_ ## counter ## _disp(struct dm_report *rh, \ struct dm_pool *mem __attribute__((unused)), \ struct dm_report_field *field, const void *data, \ void *private __attribute__((unused))) \ { \ const struct dm_stats *dms = (const struct dm_stats *) data; \ uint64_t value = dm_stats_get_ ## counter(dms, DM_STATS_REGION_CURRENT, \ DM_STATS_AREA_CURRENT); \ return dm_report_field_uint64(rh, field, &value); \ } MK_STATS_COUNTER_DISP_FN(reads) MK_STATS_COUNTER_DISP_FN(reads_merged) MK_STATS_COUNTER_DISP_FN(read_sectors) MK_STATS_COUNTER_DISP_FN(read_nsecs) MK_STATS_COUNTER_DISP_FN(writes) MK_STATS_COUNTER_DISP_FN(writes_merged) MK_STATS_COUNTER_DISP_FN(write_sectors) MK_STATS_COUNTER_DISP_FN(write_nsecs) MK_STATS_COUNTER_DISP_FN(io_in_progress) MK_STATS_COUNTER_DISP_FN(io_nsecs) MK_STATS_COUNTER_DISP_FN(weighted_io_nsecs) MK_STATS_COUNTER_DISP_FN(total_read_nsecs) MK_STATS_COUNTER_DISP_FN(total_write_nsecs) #undef MK_STATS_COUNTER_DISP_FN static int _dm_stats_region_id_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { const struct dm_stats *dms = (const struct dm_stats *) data; uint64_t group_id, region_id = dm_stats_get_current_region(dms); char *group_buf = NULL, *repstr; if (dm_stats_current_object_type(dms) == DM_STATS_OBJECT_TYPE_GROUP) { group_id = dm_stats_get_group_id(dms, dm_stats_get_current_region(dms)); if (!dm_stats_get_group_descriptor(dms, group_id, &group_buf)) return 0; /* group_buf will disappear with the current handle */ repstr = dm_pool_strdup(mem, group_buf); dm_report_field_set_value(field, repstr, &group_id); return 1; } return dm_report_field_uint64(rh, field, ®ion_id); } static int _dm_stats_region_start_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { const struct dm_stats *dms = (const struct dm_stats *) data; uint64_t region_start; const char *repstr; double *sortval; char units = _disp_units; uint64_t factor = _disp_factor; if (!dm_stats_get_current_region_start(dms, ®ion_start)) return_0; if (!(repstr = dm_size_to_string(mem, region_start, units, 1, factor, _show_units(), DM_SIZE_UNIT))) return_0; if (!(sortval = dm_pool_alloc(mem, sizeof(uint64_t)))) return_0; *sortval = (double) region_start; dm_report_field_set_value(field, repstr, sortval); return 1; } static int _dm_stats_region_len_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { const struct dm_stats *dms = (const struct dm_stats *) data; uint64_t region_length; const char *repstr; double *sortval; char units = _disp_units; uint64_t factor = _disp_factor; if (!dm_stats_get_current_region_len(dms, ®ion_length)) return_0; if (!(repstr = dm_size_to_string(mem, region_length, units, 1, factor, _show_units(), DM_SIZE_UNIT))) return_0; if (!(sortval = dm_pool_alloc(mem, sizeof(uint64_t)))) return_0; *sortval = (double) region_length; dm_report_field_set_value(field, repstr, sortval); return 1; } static int _dm_stats_area_id_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { const struct dm_stats *dms = (const struct dm_stats *) data; uint64_t area_id = dm_stats_get_current_area(dms); if (dm_stats_current_object_type(dms) == DM_STATS_OBJECT_TYPE_GROUP) area_id = 0; return dm_report_field_uint64(rh, field, &area_id); } static int _dm_stats_area_start_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { const struct dm_stats *dms = (const struct dm_stats *) data; uint64_t area_start; const char *repstr; double *sortval; char units = _disp_units; uint64_t factor = _disp_factor; if (!dm_stats_get_current_area_start(dms, &area_start)) return_0; if (!(repstr = dm_size_to_string(mem, area_start, units, 1, factor, _show_units(), DM_SIZE_UNIT))) return_0; if (!(sortval = dm_pool_alloc(mem, sizeof(uint64_t)))) return_0; *sortval = (double) area_start; dm_report_field_set_value(field, repstr, sortval); return 1; } static int _dm_stats_area_offset_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { const struct dm_stats *dms = (const struct dm_stats *) data; uint64_t area_offset; const char *repstr; double *sortval; char units = _disp_units; uint64_t factor = _disp_factor; if (!dm_stats_get_current_area_offset(dms, &area_offset)) return_0; if (!(repstr = dm_size_to_string(mem, area_offset, units, 1, factor, _show_units(), DM_SIZE_UNIT))) return_0; if (!(sortval = dm_pool_alloc(mem, sizeof(uint64_t)))) return_0; *sortval = (double) area_offset; dm_report_field_set_value(field, repstr, sortval); return 1; } static int _dm_stats_area_len_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { const struct dm_stats *dms = (const struct dm_stats *) data; uint64_t area_len; const char *repstr; double *sortval; char units = _disp_units; uint64_t factor = _disp_factor; if (!dm_stats_get_current_area_len(dms, &area_len)) return_0; if (!(repstr = dm_size_to_string(mem, area_len, units, 1, factor, _show_units(), DM_SIZE_UNIT))) return_0; if (!(sortval = dm_pool_alloc(mem, sizeof(uint64_t)))) return_0; *sortval = (double) area_len; dm_report_field_set_value(field, repstr, sortval); return 1; } static int _dm_stats_area_count_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { const struct dm_stats *dms = (const struct dm_stats *) data; uint64_t area_count, region; region = dm_stats_get_current_region(dms); if (!(area_count = dm_stats_get_region_nr_areas(dms, region))) return_0; return dm_report_field_uint64(rh, field, &area_count); } static int _dm_stats_group_id_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { const struct dm_stats *dms = (const struct dm_stats *) data; uint64_t group_id; group_id = dm_stats_get_group_id(dms, dm_stats_get_current_region(dms)); if (!dm_stats_group_present(dms, group_id)) { dm_report_field_set_value(field, "-", &group_id); return 1; } return dm_report_field_uint64(rh, field, &group_id); } static int _dm_stats_program_id_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { const struct dm_stats *dms = (const struct dm_stats *) data; const char *program_id; if (!(program_id = dm_stats_get_current_region_program_id(dms))) return_0; return dm_report_field_string(rh, field, (const char * const *) &program_id); } static int _dm_stats_user_data_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { const struct dm_stats *dms = (const struct dm_stats *) data; const char *user_data; if (!(user_data = dm_stats_get_current_region_aux_data(dms))) return_0; return dm_report_field_string(rh, field, (const char * const *) &user_data); } static int _dm_stats_name_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { const struct dm_stats *dms = (const struct dm_stats *) data; const char *stats_name; if (!(stats_name = dm_stats_get_alias(dms, DM_STATS_REGION_CURRENT))) return_0; return dm_report_field_string(rh, field, (const char * const *) &stats_name); } static int _dm_stats_object_type_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { const struct dm_stats *dms = (const struct dm_stats *) data; int type = dm_stats_current_object_type(dms); return dm_report_field_string(rh, field, &_stats_types[type]); } static int _dm_stats_precise_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { const struct dm_stats *dms = (const struct dm_stats *) data; int precise; precise = dm_stats_get_current_region_precise_timestamps(dms); return dm_report_field_int(rh, field, (const int *) &precise); } static const char *_get_histogram_string(const struct dm_stats *dms, int rel, int vals, int bounds) { const struct dm_histogram *dmh; int flags = 0, width = (_switches[NOHEADINGS_ARG]) ? -1 : 0; if (!(dmh = dm_stats_get_histogram(dms, DM_STATS_REGION_CURRENT, DM_STATS_AREA_CURRENT))) return ""; /* No histogram. */ flags |= (vals) ? DM_HISTOGRAM_VALUES : 0; flags |= bounds; flags |= (rel) ? DM_HISTOGRAM_PERCENT : 0; flags |= (_switches[NOTIMESUFFIX_ARG]) ? 0 : DM_HISTOGRAM_SUFFIX; /* FIXME: make unit conversion optional. */ return dm_histogram_to_string(dmh, -1, width, flags); } static int _stats_hist_count_disp(struct dm_report *rh, struct dm_report_field *field, const void *data, int bounds) { const struct dm_stats *dms = (const struct dm_stats *) data; const char *histogram; histogram = _get_histogram_string(dms, 0, 1, bounds); /* counts */ if (!histogram) return_0; return dm_report_field_string(rh, field, (const char * const *) &histogram); } static int _dm_stats_hist_count_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { return _stats_hist_count_disp(rh, field, data, 0); } static int _dm_stats_hist_count_bounds_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { return _stats_hist_count_disp(rh, field, data, DM_HISTOGRAM_BOUNDS_LOWER); } static int _dm_stats_hist_count_ranges_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { return _stats_hist_count_disp(rh, field, data, DM_HISTOGRAM_BOUNDS_RANGE); } static int _stats_hist_percent_disp(struct dm_report *rh, struct dm_report_field *field, const void *data, int bounds) { /* FIXME: configurable to-string options. */ const struct dm_stats *dms = (const struct dm_stats *) data; const char *histogram; histogram = _get_histogram_string(dms, 1, 1, bounds); /* relative values */ if (!histogram) return_0; return dm_report_field_string(rh, field, (const char * const *) &histogram); } static int _dm_stats_hist_percent_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { return _stats_hist_percent_disp(rh, field, data, 0); } static int _dm_stats_hist_percent_bounds_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { return _stats_hist_percent_disp(rh, field, data, DM_HISTOGRAM_BOUNDS_LOWER); } static int _dm_stats_hist_percent_ranges_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { return _stats_hist_percent_disp(rh, field, data, DM_HISTOGRAM_BOUNDS_RANGE); } static int _stats_hist_bounds_disp(struct dm_report *rh, struct dm_report_field *field, const void *data, int bounds) { /* FIXME: configurable to-string options. */ const struct dm_stats *dms = (const struct dm_stats *) data; const char *histogram; histogram = _get_histogram_string(dms, 0, 0, bounds); if (!histogram) return_0; return dm_report_field_string(rh, field, (const char * const *) &histogram); } static int _dm_stats_hist_bounds_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { return _stats_hist_bounds_disp(rh, field, data, DM_HISTOGRAM_BOUNDS_LOWER); } static int _dm_stats_hist_ranges_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { return _stats_hist_bounds_disp(rh, field, data, DM_HISTOGRAM_BOUNDS_RANGE); } static int _dm_stats_hist_bins_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { const struct dm_stats *dms = (const struct dm_stats *) data; int bins; bins = dm_stats_get_region_nr_histogram_bins(dms, DM_STATS_REGION_CURRENT); return dm_report_field_int(rh, field, (const int *) &bins); } static int _dm_stats_rrqm_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { const struct dm_stats *dms = (const struct dm_stats *) data; char buf[64]; char *repstr; double *sortval, rrqm; if (!dm_stats_get_rd_merges_per_sec(dms, &rrqm, DM_STATS_REGION_CURRENT, DM_STATS_AREA_CURRENT)) return_0; if (dm_snprintf(buf, sizeof(buf), "%.2f", rrqm) < 0) return_0; if (!(repstr = dm_pool_strdup(mem, buf))) return_0; if (!(sortval = dm_pool_alloc(mem, sizeof(uint64_t)))) return_0; *sortval = rrqm; dm_report_field_set_value(field, repstr, sortval); return 1; } static int _dm_stats_wrqm_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { const struct dm_stats *dms = (const struct dm_stats *) data; char buf[64]; char *repstr; double *sortval, wrqm; if (!dm_stats_get_wr_merges_per_sec(dms, &wrqm, DM_STATS_REGION_CURRENT, DM_STATS_AREA_CURRENT)) return_0; if (dm_snprintf(buf, sizeof(buf), "%.2f", wrqm) < 0) return_0; if (!(repstr = dm_pool_strdup(mem, buf))) return_0; if (!(sortval = dm_pool_alloc(mem, sizeof(uint64_t)))) return_0; *sortval = wrqm; dm_report_field_set_value(field, repstr, sortval); return 1; } static int _dm_stats_rs_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { const struct dm_stats *dms = (const struct dm_stats *) data; char buf[64]; char *repstr; double *sortval, rs; if (!dm_stats_get_reads_per_sec(dms, &rs, DM_STATS_REGION_CURRENT, DM_STATS_AREA_CURRENT)) return_0; if (dm_snprintf(buf, sizeof(buf), "%.2f", rs) < 0) return_0; if (!(repstr = dm_pool_strdup(mem, buf))) return_0; if (!(sortval = dm_pool_alloc(mem, sizeof(uint64_t)))) return_0; *sortval = rs; dm_report_field_set_value(field, repstr, sortval); return 1; } static int _dm_stats_ws_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { const struct dm_stats *dms = (const struct dm_stats *) data; char buf[64]; char *repstr; double *sortval, ws; if (!dm_stats_get_writes_per_sec(dms, &ws, DM_STATS_REGION_CURRENT, DM_STATS_AREA_CURRENT)) return_0; if (dm_snprintf(buf, sizeof(buf), "%.2f", ws) < 0) return_0; if (!(repstr = dm_pool_strdup(mem, buf))) return_0; if (!(sortval = dm_pool_alloc(mem, sizeof(uint64_t)))) return_0; *sortval = ws; dm_report_field_set_value(field, repstr, sortval); return 1; } static int _dm_stats_read_secs_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { const struct dm_stats *dms = (const struct dm_stats *) data; const char *repstr; double *sortval, rsec; char units = _disp_units; uint64_t factor = _disp_factor; if (!dm_stats_get_read_sectors_per_sec(dms, &rsec, DM_STATS_REGION_CURRENT, DM_STATS_AREA_CURRENT)) return_0; if (!(repstr = dm_size_to_string(mem, (uint64_t) rsec, units, 1, factor, _show_units(), DM_SIZE_UNIT))) return_0; if (!(sortval = dm_pool_alloc(mem, sizeof(uint64_t)))) return_0; *sortval = rsec; dm_report_field_set_value(field, repstr, sortval); return 1; } static int _dm_stats_write_secs_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { const struct dm_stats *dms = (const struct dm_stats *) data; const char *repstr; double *sortval, wsec; char units = _disp_units; uint64_t factor = _disp_factor; if (!dm_stats_get_write_sectors_per_sec(dms, &wsec, DM_STATS_REGION_CURRENT, DM_STATS_AREA_CURRENT)) return_0; if (!(repstr = dm_size_to_string(mem, (uint64_t) wsec, units, 1, factor, _show_units(), DM_SIZE_UNIT))) return_0; if (!(sortval = dm_pool_alloc(mem, sizeof(uint64_t)))) return_0; *sortval = wsec; dm_report_field_set_value(field, repstr, sortval); return 1; } static int _dm_stats_arqsz_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { const struct dm_stats *dms = (const struct dm_stats *) data; const char *repstr; double *sortval, arqsz; char units = _disp_units; uint64_t factor = _disp_factor; if (!dm_stats_get_average_request_size(dms, &arqsz, DM_STATS_REGION_CURRENT, DM_STATS_AREA_CURRENT)) return_0; if (!(repstr = dm_size_to_string(mem, (uint64_t) arqsz, units, 1, factor, _show_units(), DM_SIZE_UNIT))) return_0; if (!(sortval = dm_pool_alloc(mem, sizeof(uint64_t)))) return_0; *sortval = arqsz; dm_report_field_set_value(field, repstr, sortval); return 1; } static int _dm_stats_qusz_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { const struct dm_stats *dms = (const struct dm_stats *) data; char buf[64]; char *repstr; double *sortval, qusz; if (!dm_stats_get_average_queue_size(dms, &qusz, DM_STATS_REGION_CURRENT, DM_STATS_AREA_CURRENT)) return_0; if (dm_snprintf(buf, sizeof(buf), "%.2f", qusz) < 0) return_0; if (!(repstr = dm_pool_strdup(mem, buf))) return_0; if (!(sortval = dm_pool_alloc(mem, sizeof(uint64_t)))) return_0; *sortval = qusz; dm_report_field_set_value(field, repstr, sortval); return 1; } static int _dm_stats_await_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { const struct dm_stats *dms = (const struct dm_stats *) data; char buf[64]; char *repstr; double *sortval, await; if (!dm_stats_get_average_wait_time(dms, &await, DM_STATS_REGION_CURRENT, DM_STATS_AREA_CURRENT)) return_0; /* FIXME: make scale configurable */ /* display in msecs */ await /= NSEC_PER_MSEC; if (dm_snprintf(buf, sizeof(buf), "%.2f", await) < 0) return_0; if (!(repstr = dm_pool_strdup(mem, buf))) return_0; if (!(sortval = dm_pool_alloc(mem, sizeof(uint64_t)))) return_0; *sortval = await; dm_report_field_set_value(field, repstr, sortval); return 1; } static int _dm_stats_r_await_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { const struct dm_stats *dms = (const struct dm_stats *) data; char buf[64]; char *repstr; double *sortval, r_await; if (!dm_stats_get_average_rd_wait_time(dms, &r_await, DM_STATS_REGION_CURRENT, DM_STATS_AREA_CURRENT)) return_0; /* FIXME: make scale configurable */ /* display in msecs */ r_await /= NSEC_PER_MSEC; if (dm_snprintf(buf, sizeof(buf), "%.2f", r_await) < 0) return_0; if (!(repstr = dm_pool_strdup(mem, buf))) return_0; if (!(sortval = dm_pool_alloc(mem, sizeof(uint64_t)))) return_0; *sortval = r_await; dm_report_field_set_value(field, repstr, sortval); return 1; } static int _dm_stats_w_await_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { const struct dm_stats *dms = (const struct dm_stats *) data; char buf[64]; char *repstr; double *sortval, w_await; if (!dm_stats_get_average_wr_wait_time(dms, &w_await, DM_STATS_REGION_CURRENT, DM_STATS_AREA_CURRENT)) return_0; /* FIXME: make scale configurable */ /* display in msecs */ w_await /= NSEC_PER_MSEC; if (dm_snprintf(buf, sizeof(buf), "%.2f", w_await) < 0) return_0; if (!(repstr = dm_pool_strdup(mem, buf))) return_0; if (!(sortval = dm_pool_alloc(mem, sizeof(uint64_t)))) return_0; *sortval = w_await; dm_report_field_set_value(field, repstr, sortval); return 1; } static int _dm_stats_tput_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { const struct dm_stats *dms = (const struct dm_stats *) data; char buf[64]; char *repstr; double *sortval, tput; if (!dm_stats_get_throughput(dms, &tput, DM_STATS_REGION_CURRENT, DM_STATS_AREA_CURRENT)) return_0; if (dm_snprintf(buf, sizeof(buf), "%.2f", tput) < 0) return_0; if (!(repstr = dm_pool_strdup(mem, buf))) return_0; if (!(sortval = dm_pool_alloc(mem, sizeof(uint64_t)))) return_0; *sortval = tput; dm_report_field_set_value(field, repstr, sortval); return 1; } static int _dm_stats_svctm_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { const struct dm_stats *dms = (const struct dm_stats *) data; char buf[64]; char *repstr; double *sortval, svctm; if (!dm_stats_get_service_time(dms, &svctm, DM_STATS_REGION_CURRENT, DM_STATS_AREA_CURRENT)) return_0; /* FIXME: make scale configurable */ /* display in msecs */ svctm /= NSEC_PER_MSEC; if (dm_snprintf(buf, sizeof(buf), "%.2f", svctm) < 0) return_0; if (!(repstr = dm_pool_strdup(mem, buf))) return_0; if (!(sortval = dm_pool_alloc(mem, sizeof(uint64_t)))) return_0; *sortval = svctm; dm_report_field_set_value(field, repstr, sortval); return 1; } static int _dm_stats_util_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { const struct dm_stats *dms = (const struct dm_stats *) data; dm_percent_t util; if (!dm_stats_get_utilization(dms, &util, DM_STATS_REGION_CURRENT, DM_STATS_AREA_CURRENT)) return_0; dm_report_field_percent(rh, field, &util); return 1; } static int _dm_stats_sample_interval_ns_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { /* FIXME: use internal interval estimate when supported by libdm */ return dm_report_field_uint64(rh, field, &_last_interval); } static int _dm_stats_sample_interval_disp(struct dm_report *rh, struct dm_pool *mem __attribute__((unused)), struct dm_report_field *field, const void *data, void *private __attribute__((unused))) { char buf[64]; char *repstr; double *sortval; if (!(sortval = dm_pool_alloc(mem, sizeof(*sortval)))) return_0; *sortval = (double)_last_interval / (double) NSEC_PER_SEC; if (dm_snprintf(buf, sizeof(buf), "%2.6f", *sortval) < 0) return_0; if (!(repstr = dm_pool_strdup(mem, buf))) return_0; dm_report_field_set_value(field, repstr, sortval); return 1; } static void *_task_get_obj(void *obj) { return ((struct dmsetup_report_obj *)obj)->task; } static void *_info_get_obj(void *obj) { return ((struct dmsetup_report_obj *)obj)->info; } static void *_deps_get_obj(void *obj) { return dm_task_get_deps(((struct dmsetup_report_obj *)obj)->deps_task); } static void *_tree_get_obj(void *obj) { return ((struct dmsetup_report_obj *)obj)->tree_node; } static void *_split_name_get_obj(void *obj) { return ((struct dmsetup_report_obj *)obj)->split_name; } static void *_stats_get_obj(void *obj) { return ((struct dmsetup_report_obj *)obj)->stats; } static const struct dm_report_object_type _report_types[] = { { DR_TASK, "Mapped Device Name", "name_", _task_get_obj }, { DR_INFO, "Mapped Device Information", "info_", _info_get_obj }, { DR_DEPS, "Mapped Device Relationship Information", "deps_", _deps_get_obj }, { DR_TREE, "Mapped Device Relationship Information", "tree_", _tree_get_obj }, { DR_NAME, "Mapped Device Name Components", "splitname_", _split_name_get_obj }, { DR_STATS, "Mapped Device Statistics","stats_", _stats_get_obj }, { DR_STATS_META, "Mapped Device Statistics Region Information","region_", _stats_get_obj }, { 0, "", "", NULL } }; /* Column definitions */ /* N.B. Field names must not contain the substring 'help' as this will disable --count. */ #define OFFSET_OF(strct, field) (((char*)&((struct strct*)0)->field) - (char*)0) #define STR (DM_REPORT_FIELD_TYPE_STRING) #define NUM (DM_REPORT_FIELD_TYPE_NUMBER) #define SIZ (DM_REPORT_FIELD_TYPE_SIZE) #define TIM (DM_REPORT_FIELD_TYPE_TIME) #define FIELD_O(type, strct, sorttype, head, field, width, func, id, desc) {DR_ ## type, sorttype, OFFSET_OF(strct, field), width, id, head, &_ ## func ## _disp, desc}, #define FIELD_F(type, sorttype, head, width, func, id, desc) {DR_ ## type, sorttype, 0, width, id, head, &_ ## func ## _disp, desc}, static const struct dm_report_field_type _report_fields[] = { /* *INDENT-OFF* */ FIELD_F(TASK, STR, "Name", 16, dm_name, "name", "Name of mapped device.") FIELD_F(TASK, STR, "MangledName", 16, dm_mangled_name, "mangled_name", "Mangled name of mapped device.") FIELD_F(TASK, STR, "UnmangledName", 16, dm_unmangled_name, "unmangled_name", "Unmangled name of mapped device.") FIELD_F(TASK, STR, "UUID", 32, dm_uuid, "uuid", "Unique (optional) identifier for mapped device.") FIELD_F(TASK, STR, "MangledUUID", 32, dm_mangled_uuid, "mangled_uuid", "Mangled unique (optional) identifier for mapped device.") FIELD_F(TASK, STR, "UnmangledUUID", 32, dm_unmangled_uuid, "unmangled_uuid", "Unmangled unique (optional) identifier for mapped device.") /* FIXME Next one should be INFO */ FIELD_F(TASK, NUM, "RAhead", 6, dm_read_ahead, "read_ahead", "Read ahead value.") FIELD_F(INFO, STR, "BlkDevName", 16, dm_blk_name, "blkdevname", "Name of block device.") FIELD_F(INFO, STR, "Stat", 4, dm_info_status, "attr", "(L)ive, (I)nactive, (s)uspended, (r)ead-only, read-(w)rite.") FIELD_F(INFO, STR, "Tables", 6, dm_info_table_loaded, "tables_loaded", "Which of the live and inactive table slots are filled.") FIELD_F(INFO, STR, "Suspended", 9, dm_info_suspended, "suspended", "Whether the device is suspended.") FIELD_F(INFO, STR, "Read-only", 9, dm_info_read_only, "readonly", "Whether the device is read-only or writeable.") FIELD_F(INFO, STR, "DevNo", 5, dm_info_devno, "devno", "Device major and minor numbers") FIELD_O(INFO, dm_info, NUM, "Maj", major, 3, int32, "major", "Block device major number.") FIELD_O(INFO, dm_info, NUM, "Min", minor, 3, int32, "minor", "Block device minor number.") FIELD_O(INFO, dm_info, NUM, "Open", open_count, 4, int32, "open", "Number of references to open device, if requested.") FIELD_O(INFO, dm_info, NUM, "Targ", target_count, 4, int32, "segments", "Number of segments in live table, if present.") FIELD_O(INFO, dm_info, NUM, "Event", event_nr, 6, uint32, "events", "Number of most recent event.") FIELD_O(DEPS, dm_deps, NUM, "#Devs", count, 5, int32, "device_count", "Number of devices used by this one.") FIELD_F(TREE, STR, "DevNamesUsed", 16, dm_deps_names, "devs_used", "List of names of mapped devices used by this one.") FIELD_F(DEPS, STR, "DevNosUsed", 16, dm_deps, "devnos_used", "List of device numbers of devices used by this one.") FIELD_F(DEPS, STR, "BlkDevNamesUsed", 16, dm_deps_blk_names, "blkdevs_used", "List of names of block devices used by this one.") FIELD_F(TREE, NUM, "#Refs", 5, dm_tree_parents_count, "device_ref_count", "Number of mapped devices referencing this one.") FIELD_F(TREE, STR, "RefNames", 8, dm_tree_parents_names, "names_using_dev", "List of names of mapped devices using this one.") FIELD_F(TREE, STR, "RefDevNos", 9, dm_tree_parents_devs, "devnos_using_dev", "List of device numbers of mapped devices using this one.") FIELD_O(NAME, dm_split_name, STR, "Subsys", subsystem, 6, dm_subsystem, "subsystem", "Userspace subsystem responsible for this device.") FIELD_O(NAME, dm_split_name, STR, "VG", vg_name, 4, dm_vg_name, "vg_name", "LVM Volume Group name.") FIELD_O(NAME, dm_split_name, STR, "LV", lv_name, 4, dm_lv_name, "lv_name", "LVM Logical Volume name.") FIELD_O(NAME, dm_split_name, STR, "LVLayer", lv_layer, 7, dm_lv_layer_name, "lv_layer", "LVM device layer.") /* basic stats counters */ FIELD_F(STATS, NUM, "#Reads", 6, dm_stats_reads, "read_count", "Count of reads completed.") FIELD_F(STATS, NUM, "#RdMrgs", 7, dm_stats_reads_merged, "reads_merged_count", "Count of read requests merged.") FIELD_F(STATS, NUM, "#RdSectors", 10, dm_stats_read_sectors, "read_sector_count", "Count of sectors read.") FIELD_F(STATS, NUM, "AccRdTime", 11, dm_stats_read_nsecs, "read_time", "Accumulated duration of all read requests (ns).") FIELD_F(STATS, NUM, "#Writes", 7, dm_stats_writes, "write_count", "Count of writes completed.") FIELD_F(STATS, NUM, "#WrMrgs", 7, dm_stats_writes_merged, "writes_merged_count", "Count of write requests merged.") FIELD_F(STATS, NUM, "#WrSectors", 10, dm_stats_write_sectors, "write_sector_count", "Count of sectors written.") FIELD_F(STATS, NUM, "AccWrTime", 11, dm_stats_write_nsecs, "write_time", "Accumulated duration of all writes (ns).") FIELD_F(STATS, NUM, "#InProg", 7, dm_stats_io_in_progress, "in_progress_count", "Count of requests currently in progress.") FIELD_F(STATS, NUM, "IoTicks", 7, dm_stats_io_nsecs, "io_ticks", "Nanoseconds spent servicing requests.") FIELD_F(STATS, NUM, "QueueTicks", 10, dm_stats_weighted_io_nsecs, "queue_ticks", "Total nanoseconds spent in queue.") FIELD_F(STATS, NUM, "RdTicks", 7, dm_stats_total_read_nsecs, "read_ticks", "Nanoseconds spent servicing reads.") FIELD_F(STATS, NUM, "WrTicks", 7, dm_stats_total_write_nsecs, "write_ticks", "Nanoseconds spent servicing writes.") /* Stats derived metrics */ FIELD_F(STATS, NUM, "RMrg/s", 6, dm_stats_rrqm, "reads_merged_per_sec", "Read requests merged per second.") FIELD_F(STATS, NUM, "WMrg/s", 6, dm_stats_wrqm, "writes_merged_per_sec", "Write requests merged per second.") FIELD_F(STATS, NUM, "R/s", 3, dm_stats_rs, "reads_per_sec", "Reads per second.") FIELD_F(STATS, NUM, "W/s", 3, dm_stats_ws, "writes_per_sec", "Writes per second.") FIELD_F(STATS, NUM, "RSz/s", 5, dm_stats_read_secs, "read_size_per_sec", "Size of data read per second.") FIELD_F(STATS, NUM, "WSz/s", 5, dm_stats_write_secs, "write_size_per_sec", "Size of data written per second.") FIELD_F(STATS, NUM, "AvgRqSz", 7, dm_stats_arqsz, "avg_request_size", "Average request size.") FIELD_F(STATS, NUM, "QSize", 5, dm_stats_qusz, "queue_size", "Average queue size.") FIELD_F(STATS, NUM, "AWait", 5, dm_stats_await, "await", "Averate wait time.") FIELD_F(STATS, NUM, "RdAWait", 7, dm_stats_r_await, "read_await", "Averate read wait time.") FIELD_F(STATS, NUM, "WrAWait", 7, dm_stats_w_await, "write_await", "Averate write wait time.") FIELD_F(STATS, NUM, "Throughput", 10, dm_stats_tput, "throughput", "Throughput.") FIELD_F(STATS, NUM, "SvcTm", 5, dm_stats_svctm, "service_time", "Service time.") FIELD_F(STATS, NUM, "Util%", 5, dm_stats_util, "util", "Utilization.") /* Histogram fields */ FIELD_F(STATS, STR, "Histogram Counts", 16, dm_stats_hist_count, "hist_count", "Latency histogram counts.") FIELD_F(STATS, STR, "Histogram Counts", 16, dm_stats_hist_count_bounds, "hist_count_bounds", "Latency histogram counts with bin boundaries.") FIELD_F(STATS, STR, "Histogram Counts", 16, dm_stats_hist_count_ranges, "hist_count_ranges", "Latency histogram counts with bin ranges.") FIELD_F(STATS, STR, "Histogram%", 10, dm_stats_hist_percent, "hist_percent", "Relative latency histogram.") FIELD_F(STATS, STR, "Histogram%", 10, dm_stats_hist_percent_bounds, "hist_percent_bounds", "Relative latency histogram with bin boundaries.") FIELD_F(STATS, STR, "Histogram%", 10, dm_stats_hist_percent_ranges, "hist_percent_ranges", "Relative latency histogram with bin ranges.") /* Stats interval duration estimates */ FIELD_F(STATS, NUM, "IntervalNs", 10, dm_stats_sample_interval_ns, "interval_ns", "Sampling interval in nanoseconds.") FIELD_F(STATS, NUM, "Interval", 8, dm_stats_sample_interval, "interval", "Sampling interval.") /* Stats report meta-fields */ FIELD_F(STATS_META, NUM, "RgID", 4, dm_stats_region_id, "region_id", "Region ID.") FIELD_F(STATS_META, SIZ, "RgStart", 7, dm_stats_region_start, "region_start", "Region start.") FIELD_F(STATS_META, SIZ, "RgSize", 6, dm_stats_region_len, "region_len", "Region length.") FIELD_F(STATS_META, NUM, "ArID", 4, dm_stats_area_id, "area_id", "Area ID.") FIELD_F(STATS_META, SIZ, "ArStart", 7, dm_stats_area_start, "area_start", "Area offset from start of device.") FIELD_F(STATS_META, SIZ, "ArSize", 6, dm_stats_area_len, "area_len", "Area length.") FIELD_F(STATS_META, SIZ, "ArOff", 5, dm_stats_area_offset, "area_offset", "Area offset from start of region.") FIELD_F(STATS_META, NUM, "#Areas", 6, dm_stats_area_count, "area_count", "Area count.") FIELD_F(STATS_META, NUM, "GrpID", 5, dm_stats_group_id, "group_id", "Group ID.") FIELD_F(STATS_META, STR, "ProgID", 6, dm_stats_program_id, "program_id", "Program ID.") FIELD_F(STATS_META, STR, "UserData", 8, dm_stats_user_data, "user_data", "Auxiliary data.") FIELD_F(STATS_META, STR, "Precise", 7, dm_stats_precise, "precise", "Set if nanosecond precision counters are enabled.") FIELD_F(STATS_META, STR, "#Bins", 9, dm_stats_hist_bins, "hist_bins", "The number of histogram bins configured.") FIELD_F(STATS_META, STR, "Histogram Bounds", 16, dm_stats_hist_bounds, "hist_bounds", "Latency histogram bin boundaries.") FIELD_F(STATS_META, STR, "Histogram Ranges", 16, dm_stats_hist_ranges, "hist_ranges", "Latency histogram bin ranges.") FIELD_F(STATS_META, STR, "Name", 16, dm_stats_name, "stats_name", "Stats name of current object.") FIELD_F(STATS_META, STR, "ObjType", 7, dm_stats_object_type, "obj_type", "Type of stats object being reported.") {0, 0, 0, 0, "", "", NULL, NULL}, /* *INDENT-ON* */ }; #undef FIELD_O #undef FIELD_F #undef STR #undef NUM #undef SIZ static const char _default_report_options[] = "name,major,minor,attr,open,segments,events,uuid"; static const char _splitname_report_options[] = "vg_name,lv_name,lv_layer"; /* Stats counters & derived metrics. */ #define RD_COUNTERS "read_count,reads_merged_count,read_sector_count,read_time,read_ticks" #define WR_COUNTERS "write_count,writes_merged_count,write_sector_count,write_time,write_ticks" #define IO_COUNTERS "in_progress_count,io_ticks,queue_ticks" #define COUNTERS RD_COUNTERS "," WR_COUNTERS "," IO_COUNTERS #define METRICS "reads_merged_per_sec,writes_merged_per_sec," \ "reads_per_sec,writes_per_sec," \ "read_size_per_sec,write_size_per_sec," \ "avg_request_size,queue_size,util," \ "await,read_await,write_await" /* Device, region and area metadata. */ #define STATS_DEV_INFO "statsname,group_id,region_id,obj_type" #define STATS_AREA_INFO "area_id,area_start,area_len" #define STATS_AREA_INFO_FULL STATS_DEV_INFO ",region_start,region_len,area_count,area_id,area_start,area_len" #define STATS_REGION_INFO STATS_DEV_INFO ",region_start,region_len,area_count,area_len" /* Minimal set of fields for histogram report. */ #define STATS_HIST STATS_REGION_INFO ",util,await" /* Default stats report options. */ static const char _stats_default_report_options[] = STATS_DEV_INFO "," STATS_AREA_INFO "," METRICS; static const char _stats_raw_report_options[] = STATS_DEV_INFO "," STATS_AREA_INFO "," COUNTERS; static const char _stats_list_options[] = STATS_REGION_INFO ",program_id"; static const char _stats_area_list_options[] = STATS_AREA_INFO_FULL ",program_id"; static const char _stats_hist_list_options[] = STATS_REGION_INFO ",hist_bins,hist_bounds"; static const char _stats_hist_area_list_options[] = STATS_AREA_INFO_FULL ",hist_bins,hist_bounds"; static const char _stats_hist_options[] = STATS_HIST ",hist_count_bounds"; static const char _stats_hist_relative_options[] = STATS_HIST ",hist_percent_bounds"; static int _report_init(const struct command *cmd, const char *subcommand) { const char *options = _default_report_options; const char *opt_fields = NULL; /* optional fields from command line */ const char *keys = ""; const char *separator = " "; const char *selection = NULL; char *tmpopts = NULL; int aligned = 1, headings = 1, buffered = 1, field_prefixes = 0; int quoted = 1, columns_as_rows = 0; uint32_t flags = 0; size_t len = 0; int r = 0; if (cmd && !strcmp(cmd->name, "splitname")) { options = (char *) _splitname_report_options; _report_type |= DR_NAME; } if (cmd && !strcmp(cmd->name, "stats")) { _report_type |= DR_STATS_META; if (!strcmp(subcommand, "list")) { if (!_switches[HISTOGRAM_ARG]) options = ((_switches[VERBOSE_ARG]) ? _stats_area_list_options : _stats_list_options); else options = ((_switches[VERBOSE_ARG]) ? _stats_hist_area_list_options : _stats_hist_list_options); } else { if (_switches[HISTOGRAM_ARG]) options = ((_switches[RELATIVE_ARG]) ? _stats_hist_relative_options : _stats_hist_options); else options = ((!_switches[RAW_ARG]) ? _stats_default_report_options : _stats_raw_report_options); _report_type |= DR_STATS; } } if (cmd && !strcmp(cmd->name, "list")) { options = _stats_list_options; _report_type |= DR_STATS_META; } if (_switches[NOHEADINGS_ARG] && _switches[HEADINGS_ARG]) { log_error("Only one of --headings and --noheadings permitted."); return 0; } /* emulate old dmsetup behaviour */ if (_switches[NOHEADINGS_ARG]) { separator = ":"; aligned = 0; headings = 0; } if (_switches[HEADINGS_ARG]) headings = _int_args[HEADINGS_ARG]; if (_switches[UNBUFFERED_ARG]) buffered = 0; if (_switches[ROWS_ARG]) columns_as_rows = 1; if (_switches[UNQUOTED_ARG]) quoted = 0; if (_switches[NAMEPREFIXES_ARG]) { aligned = 0; field_prefixes = 1; } if (_switches[OPTIONS_ARG] && _string_args[OPTIONS_ARG]) { /* Count & interval forbidden for help. */ /* FIXME Detect "help" correctly and exit */ if (strstr(_string_args[OPTIONS_ARG], "help")) { _switches[COUNT_ARG] = 0; _count = 1; _switches[INTERVAL_ARG] = 0; headings = 0; } if (*_string_args[OPTIONS_ARG] != '+') options = _string_args[OPTIONS_ARG]; else { opt_fields = _string_args[OPTIONS_ARG] + 1; len = strlen(options) + strlen(opt_fields) + 2; if (!(tmpopts = malloc(len))) { log_error("Failed to allocate option string."); return 0; } if (dm_snprintf(tmpopts, len, "%s,%s", options, opt_fields) < 0) { free(tmpopts); return 0; } options = tmpopts; } } if (_switches[SORT_ARG] && _string_args[SORT_ARG]) { keys = _string_args[SORT_ARG]; buffered = 1; if (cmd && (!strcmp(cmd->name, "status") || !strcmp(cmd->name, "table"))) { log_error("--sort is not yet supported with status and table."); goto out; } } if (_switches[SEPARATOR_ARG] && _string_args[SEPARATOR_ARG]) { separator = _string_args[SEPARATOR_ARG]; aligned = 0; } if (_switches[SELECT_ARG] && _string_args[SELECT_ARG]) selection = _string_args[SELECT_ARG]; if (aligned) flags |= DM_REPORT_OUTPUT_ALIGNED; if (buffered) flags |= DM_REPORT_OUTPUT_BUFFERED; if (headings) { flags |= DM_REPORT_OUTPUT_HEADINGS; if (headings == 2) flags |= DM_REPORT_OUTPUT_FIELD_IDS_IN_HEADINGS; } if (field_prefixes) flags |= DM_REPORT_OUTPUT_FIELD_NAME_PREFIX; if (!quoted) flags |= DM_REPORT_OUTPUT_FIELD_UNQUOTED; if (columns_as_rows) flags |= DM_REPORT_OUTPUT_COLUMNS_AS_ROWS; if (!(_report = dm_report_init_with_selection(&_report_type, _report_types, _report_fields, options, separator, flags, keys, selection, NULL, NULL))) goto_out; r = 1; if ((_report_type & DR_TREE) && cmd) { r = _build_whole_deptree(cmd); if (!_dtree) { log_error("Internal device dependency tree creation failed."); goto out; } } if (!_switches[INTERVAL_ARG]) _int_args[INTERVAL_ARG] = 1; /* 1s default. */ _interval = NSEC_PER_SEC * (uint64_t) _int_args[INTERVAL_ARG]; if (field_prefixes) dm_report_set_output_field_name_prefix(_report, "dm_"); out: free(tmpopts); return r; } /* * List devices */ static int _ls(CMD_ARGS) { if ((_switches[TARGET_ARG] && _target) || (_switches[EXEC_ARG] && _command_to_exec)) return _status(cmd, NULL, argc, argv, NULL, 0); if ((_switches[TREE_ARG])) return _display_tree(cmd, NULL, 0, NULL, NULL, 0); return _process_all(cmd, NULL, argc, argv, 0, _display_name); } static int _mangle(CMD_ARGS) { const char *name, *uuid; char *new_name = NULL, *new_uuid = NULL; struct dm_task *dmt; struct dm_info info; int r = 0; int target_format; if (names) name = names->name; else { if (!argc && !_switches[UUID_ARG] && !_switches[MAJOR_ARG]) return _process_all(cmd, NULL, argc, argv, 0, _mangle); name = argv[0]; } if (!(dmt = dm_task_create(DM_DEVICE_STATUS))) return_0; if (!(_set_task_device(dmt, name, 0))) goto_out; if (!_switches[CHECKS_ARG] && !dm_task_enable_checks(dmt)) goto_out; if (!_task_run(dmt)) goto_out; if (!dm_task_get_info(dmt, &info) || !info.exists) goto_out; uuid = dm_task_get_uuid(dmt); target_format = _switches[MANGLENAME_ARG] ? _int_args[MANGLENAME_ARG] : DEFAULT_DM_NAME_MANGLING; if (target_format == DM_STRING_MANGLING_AUTO) { if (strstr(name, "\\x5cx")) { log_error("The name \"%s\" seems to be mangled more than once. " "Manual intervention required to rename the device.", name); goto out; } if (strstr(uuid, "\\x5cx")) { log_error("The UUID \"%s\" seems to be mangled more than once. " "Manual intervention required to correct the device UUID.", uuid); goto out; } } if (target_format == DM_STRING_MANGLING_NONE) { if (!(new_name = dm_task_get_name_unmangled(dmt))) goto_out; if (!(new_uuid = dm_task_get_uuid_unmangled(dmt))) goto_out; } else { if (!(new_name = dm_task_get_name_mangled(dmt))) goto_out; if (!(new_uuid = dm_task_get_uuid_mangled(dmt))) goto_out; } /* We can't rename the UUID, the device must be reactivated manually. */ if (strcmp(uuid, new_uuid)) { log_error("%s: %s: UUID in incorrect form. ", name, uuid); log_error("Unable to change device UUID. The device must be deactivated first."); r = 0; goto out; } /* Nothing to do if the name is in correct form already. */ if (!strcmp(name, new_name)) { log_print("%s: %s: name %salready in correct form", name, *uuid ? uuid : "[no UUID]", *uuid ? "and UUID " : ""); r = 1; goto out; } else log_print("%s: renaming to %s", name, new_name); /* Rename to correct form of the name. */ r = _do_rename(name, new_name, NULL); out: free(new_name); free(new_uuid); dm_task_destroy(dmt); return r; } static int _stats(CMD_ARGS); static int _bind_stats_device(struct dm_stats *dms, const char *name) { if (name && !dm_stats_bind_name(dms, name)) return_0; else if (_switches[UUID_ARG] && !dm_stats_bind_uuid(dms, _uuid)) return_0; else if (_switches[MAJOR_ARG] && _switches[MINOR_ARG] && !dm_stats_bind_devno(dms, _int_args[MAJOR_ARG], _int_args[MINOR_ARG])) return_0; return 1; } static int _stats_clear_one_region(struct dm_stats *dms, uint64_t region_id) { if (!dm_stats_region_present(dms, region_id)) { log_error("No such region: %"PRIu64".", region_id); return 0; } if (!dm_stats_clear_region(dms, region_id)) { log_error("Clearing statistics region %"PRIu64" failed.", region_id); return 0; } log_info("Cleared statistics region %"PRIu64".", region_id); return 1; } static int _stats_clear_regions(struct dm_stats *dms, uint64_t region_id) { int allregions = (region_id == DM_STATS_REGIONS_ALL); if (!dm_stats_list(dms, NULL)) return_0; if (!dm_stats_get_nr_regions(dms)) return 1; if (!allregions) return _stats_clear_one_region(dms, region_id); dm_stats_foreach_region(dms) { region_id = dm_stats_get_current_region(dms); if (!_stats_clear_one_region(dms, region_id)) return_0; } return 1; } static int _stats_clear(CMD_ARGS) { struct dm_stats *dms; uint64_t region_id; char *name = NULL; int allregions = _switches[ALL_REGIONS_ARG]; /* clear does not use a report */ if (_report) { dm_report_free(_report); _report = NULL; } if (!_switches[REGION_ID_ARG] && !_switches[ALL_REGIONS_ARG]) { log_error("Please specify a --regionid or use --allregions."); return 0; } if (names) name = names->name; else { if (!argc && !_switches[UUID_ARG] && !_switches[MAJOR_ARG]) return _process_all(cmd, subcommand, argc, argv, 0, _stats_clear); name = argv[0]; } region_id = (allregions) ? DM_STATS_REGIONS_ALL : (uint64_t) _int_args[REGION_ID_ARG]; if (!(dms = dm_stats_create(DM_STATS_PROGRAM_ID))) return_0; if (!_bind_stats_device(dms, name)) goto_out; if (!_stats_clear_regions(dms, region_id)) goto_out; dm_stats_destroy(dms); return 1; out: dm_stats_destroy(dms); return 0; } static uint64_t _factor_from_units(const char *argptr, char *unit_type) { return dm_units_to_factor(argptr, unit_type, 0, NULL); } /** * Parse a start, length, or area size argument in bytes from a string * using optional units as supported by _factor_from_units(). */ static int _size_from_string(const char *argptr, uint64_t *size, const char *name) { uint64_t factor; char *endptr = NULL, unit_type; if (!argptr) return_0; errno = 0; *size = strtoull(argptr, &endptr, 10); if (errno || endptr == argptr) { *size = 0; log_error("Invalid %s argument: \"%s\"", name, (*argptr) ? argptr : ""); return 0; } if (*endptr == '\0') { *size *= 512; return 1; } factor = _factor_from_units(endptr, &unit_type); if (factor) *size *= factor; return 1; } /* * FIXME: expose this from libdm-stats */ static uint64_t _nr_areas_from_step(uint64_t len, int64_t step) { /* Default is one area. */ if (!step || !len) return 1; /* --areas */ if (step < 0) return (uint64_t)(-step); /* --areasize - cast step to unsigned as it cannot be -ve here. */ return (len / step) + !!(len % (uint64_t) step); } /* maximum length of a string representation of an integer */ #define max_int_strlen(i) (strlen(#i)) #define MAX_UINT64_STRLEN max_int_strlen(UINT64_MAX) static int _stats_group_segments(struct dm_stats *dms, uint64_t *region_ids, int count, const char *alias) { /* NULL, commas, and count * region_id */ size_t bufsize = 1 + count + count * MAX_UINT64_STRLEN; char *this_region, *regions = NULL; uint64_t group_id; int r, i; this_region = regions = malloc(bufsize); if (!regions) { log_error("Could not allocate memory for region_id table."); return 0; } *regions = 0; for (i = 0; i < count; i++) { /* * We don't expect large numbers of segments (compared to e.g. * --filemap): use a fixed-size buffer based on the number of * region identifiers and do not collapse continuous ranges * of identifiers in the group descriptor argument. */ r = dm_snprintf(this_region, bufsize, FMTu64 "%s", region_ids[i], (i < (count - 1)) ? "," : ""); if (r < 0) goto_bad; this_region += r; bufsize -= r; } /* refresh handle */ if (!(r = dm_stats_list(dms, NULL))) goto bad; if ((r = dm_stats_create_group(dms, regions, alias, &group_id))) printf("Grouped regions %s as group ID " FMTu64 "%s%s\n", regions, group_id, (alias) ? " with alias " : "", (alias) ? : ""); else log_error("Failed to create group for regions %s.", regions); bad: free(regions); return r; } /* * Create a single region starting at start and spanning len sectors, * or, if the segments argument is no-zero create one region for each * segment present in the mapped device. Passing zero for segments, * start, and length will create a single segment spanning the whole * device. */ static int _do_stats_create_regions(struct dm_stats *dms, const char *name, uint64_t start, uint64_t len, int64_t step, int segments, const char *program_id, const char *user_data) { uint64_t this_start = 0, this_len = len, region_id = UINT64_C(0); const char *devname = NULL, *histogram = _string_args[BOUNDS_ARG]; int r = 0, count = 0, precise = _switches[PRECISE_ARG]; struct dm_histogram *bounds = NULL; /* histogram bounds */ uint64_t *region_ids = NULL; /* segments */ char *target_type, *params; /* unused */ struct dm_task *dmt; struct dm_info info; void *next = NULL; if (_switches[ALIAS_ARG] && _switches[NOGROUP_ARG]) { log_error("Cannot set alias with --nogroup."); dm_stats_destroy(dms); return 0; } if (histogram && !(bounds = dm_histogram_bounds_from_string(histogram))) return_0; if (!(dmt = dm_task_create(DM_DEVICE_TABLE))) { dm_histogram_bounds_destroy(bounds); dm_stats_destroy(dms); return_0; } if (!_set_task_device(dmt, name, 0)) goto_out; if (!dm_task_no_open_count(dmt)) goto_out; if (_switches[CHECKS_ARG] && !dm_task_enable_checks(dmt)) goto_out; if (!_task_run(dmt)) goto_out; if (!dm_task_get_info(dmt, &info) || !info.exists) goto_out; if (!(devname = dm_task_get_name(dmt))) goto_out; if (!segments || (info.target_count == 1)) region_ids = ®ion_id; else if (!(region_ids = malloc(info.target_count * sizeof(*region_ids)))) { log_error("Failed to allocated region IDs."); goto out; } do { uint64_t segment_start, segment_len; next = dm_get_next_target(dmt, next, &segment_start, &segment_len, &target_type, ¶ms); /* Accumulate whole-device size for nr_areas calculation. */ if (!segments && !len) this_len += segment_len; /* Segments or whole-device. */ if (segments || !next) { /* * this_start and this_len hold the start and length in * sectors of the to-be-created region: this is either the * segment start/len (for --segments), the value of the * --start/--length arguments, or 0/0 for a default * whole-device region). */ this_start = (segments) ? segment_start : start; this_len = (segments) ? segment_len : this_len; /* coverity[ptr_arith] intentional */ if (!(r = dm_stats_create_region(dms, ®ion_ids[count], this_start, this_len, step, precise, bounds, program_id, user_data))) { log_error("%s: Could not create statistics region.", devname); goto out; } printf("%s: Created new region with "FMTu64" area(s) as " "region ID "FMTu64"\n", devname, _nr_areas_from_step(this_len, step), region_ids[count++]); } } while (next); if (!_switches[NOGROUP_ARG] && segments) r = _stats_group_segments(dms, region_ids, count, _string_args[ALIAS_ARG]); out: if (region_ids != ®ion_id) free(region_ids); dm_task_destroy(dmt); dm_stats_destroy(dms); dm_histogram_bounds_destroy(bounds); return r; } /* * Returns the full absolute path, or NULL if the path could * not be resolved. */ static char *_get_abspath(const char *path) { char *_path; #ifdef HAVE_CANONICALIZE_FILE_NAME _path = canonicalize_file_name(path); #else /* FIXME Provide alternative */ log_error(INTERNAL_ERROR "Unimplemented _get_abspath."); _path = NULL; #endif return _path; } static int _stats_check_filemap_switches(void) { if (_switches[AREAS_ARG] || _switches[AREA_SIZE_ARG]) { log_error("--filemap is incompatible with --areas and --area-size."); return 0; } if (_switches[START_ARG] || _switches[LENGTH_ARG]) { log_error("--filemap is incompatible with --start and --length."); return 0; } if (_switches[SEGMENTS_ARG]) { log_error("--filemap and --segments are incompatible."); return 0; } if (_switches[USER_DATA_ARG]) { log_error("--userdata is not yet supported with --filemap."); return 0; } if (_switches[UUID_ARG] || _switches[MAJOR_ARG]) { log_error("--uuid and --major are incompatible with --filemap."); return 0; } if (_switches[ALL_DEVICES_ARG]) { log_error("--alldevices is incompatible with --filemap."); return 0; } return 1; } static dm_filemapd_mode_t _stats_get_filemapd_mode(void) { if (_switches[NOMONITOR_ARG] || _switches[NOGROUP_ARG]) return DM_FILEMAPD_FOLLOW_NONE; if (!_switches[FOLLOW_ARG]) return DM_FILEMAPD_FOLLOW_INODE; return dm_filemapd_mode_from_string(_string_args[FOLLOW_ARG]); } static int _stats_create_file(CMD_ARGS) { const char *alias, *program_id = DM_STATS_PROGRAM_ID; const char *bounds_str = _string_args[BOUNDS_ARG]; int foreground = _switches[FOREGROUND_ARG]; int verbose = _switches[VERBOSE_ARG]; uint64_t *regions, *region, count = 0; struct dm_histogram *bounds = NULL; char *path, *abspath = NULL; struct dm_stats *dms = NULL; int group, fd = -1, precise; dm_filemapd_mode_t mode; if (names) { log_error("Device names are not compatibile with --filemap."); return 0; } if (!_stats_check_filemap_switches()) return 0; /* _stats_create_file does not use _process_all() */ if (!argc) { log_error("--filemap requires a file path argument"); return 0; } path = argv[0]; if (_switches[PRECISE_ARG]) { if (!dm_stats_driver_supports_precise()) { log_error("Using --precise requires driver version " "4.32.0 or later."); return 0; } } if (_switches[BOUNDS_ARG]) { if (!dm_stats_driver_supports_histogram()) { log_error("Using --bounds requires driver version " "4.32.0 or later."); return 0; } } if (!(abspath = _get_abspath(path))) { log_error("Could not canonicalize file name: %s", path); return 0; } if (bounds_str && !(bounds = dm_histogram_bounds_from_string(bounds_str))) { free(abspath); return_0; } if (_switches[PROGRAM_ID_ARG]) program_id = _string_args[PROGRAM_ID_ARG]; if (!strlen(program_id) && !_switches[FORCE_ARG]) program_id = DM_STATS_PROGRAM_ID; precise = _switches[PRECISE_ARG]; group = !_switches[NOGROUP_ARG]; mode = _stats_get_filemapd_mode(); if (!_switches[NOMONITOR_ARG] && (mode == DM_FILEMAPD_FOLLOW_NONE)) goto bad; if (!(dms = dm_stats_create(DM_STATS_PROGRAM_ID))) goto_bad; fd = open(abspath, O_RDONLY); if (fd < 0) { log_error("Could not open %s for reading", abspath); goto bad; } if (!dm_stats_bind_from_fd(dms, fd)) goto_bad; if (!strlen(program_id)) /* force creation of a region with no id */ dm_stats_set_program_id(dms, 1, NULL); if (group && !_switches[ALIAS_ARG]) alias = dm_basename(abspath); else if (group) alias = _string_args[ALIAS_ARG]; else if (!_switches[ALIAS_ARG]) alias = NULL; else { log_error("Cannot set alias with --nogroup."); goto bad; } regions = dm_stats_create_regions_from_fd(dms, fd, group, precise, bounds, alias); if (!regions) { log_error("Could not create regions from file %s.", abspath); goto bad; } if (!_switches[NOMONITOR_ARG] && group) { if (!dm_stats_start_filemapd(fd, regions[0], abspath, mode, foreground, verbose)) log_warn("Failed to start filemap monitoring daemon."); } if (close(fd)) log_sys_debug("close", abspath); fd = -1; for (region = regions; *region != DM_STATS_REGIONS_ALL; region++) count++; if (group) { printf("%s: Created new group with "FMTu64" region(s) as " "group ID "FMTu64".\n", path, count, regions[0]); } else { region = regions; do printf("%s: Created new region with 1 area as " "region ID "FMTu64".\n", path, *region); while (*(++region) != DM_STATS_REGIONS_ALL); } free(regions); free(abspath); free(bounds); dm_stats_destroy(dms); return 1; bad: free(abspath); free(bounds); if ((fd > -1) && close(fd)) log_sys_debug("close", path); if (dms) dm_stats_destroy(dms); return 0; } static int _stats_create(CMD_ARGS) { struct dm_stats *dms; const char *name, *user_data = "", *program_id = DM_STATS_PROGRAM_ID; uint64_t start = 0, len = 0, areas = 0, area_size = 0; int64_t step = 0; /* create does not use a report */ if (_report) { dm_report_free(_report); _report = NULL; } if (_switches[ALL_REGIONS_ARG]) { log_error("Cannot use --allregions with create."); return 0; } if (_switches[ALL_PROGRAMS_ARG]) { log_error("Cannot use --allprograms with create."); return 0; } if (_switches[AREAS_ARG] && _switches[AREA_SIZE_ARG]) { log_error("Please specify one of --areas and --areasize."); return 0; } if (_switches[PROGRAM_ID_ARG] && !strlen(_string_args[PROGRAM_ID_ARG]) && !_switches[FORCE_ARG]) { log_error("Creating a region with no program " "id requires --force."); return 0; } if (_switches[FILEMAP_ARG]) return _stats_create_file(cmd, subcommand, argc, argv, names, multiple_devices); if (names) name = names->name; else { if (!argc && !_switches[UUID_ARG] && !_switches[MAJOR_ARG]) { if (!_switches[ALL_DEVICES_ARG]) { log_error("Please specify device(s) or use " "--alldevices."); return 0; } return _process_all(cmd, subcommand, argc, argv, 0, _stats_create); } name = argv[0]; } if (_switches[AREAS_ARG]) areas = (uint64_t) _int_args[AREAS_ARG]; if (_switches[AREA_SIZE_ARG]) if (!_size_from_string(_string_args[AREA_SIZE_ARG], &area_size, "areasize")) return_0; areas = (areas) ? areas : 1; /* bytes to sectors or -(areas): promote to signed before conversion */ step = (area_size) ? ((int64_t) area_size / 512) : -((int64_t) areas); if (_switches[START_ARG]) { if (!_size_from_string(_string_args[START_ARG], &start, "start")) return_0; } /* bytes to sectors */ start /= 512; if (_switches[LENGTH_ARG]) { if (!_size_from_string(_string_args[LENGTH_ARG], &len, "length")) return_0; } /* bytes to sectors */ len /= 512; if (_switches[PROGRAM_ID_ARG]) program_id = _string_args[PROGRAM_ID_ARG]; if (!strlen(program_id) && !_switches[FORCE_ARG]) program_id = DM_STATS_PROGRAM_ID; if (_switches[USER_DATA_ARG]) user_data = _string_args[USER_DATA_ARG]; if (!(dms = dm_stats_create(DM_STATS_PROGRAM_ID))) return_0; if (!_bind_stats_device(dms, name)) goto_bad; if (_switches[PRECISE_ARG]) { if (!dm_stats_driver_supports_precise()) { log_error("Using --precise requires driver version " "4.32.0 or later."); goto bad; } } if (_switches[BOUNDS_ARG]) { if (!dm_stats_driver_supports_histogram()) { log_error("Using --bounds requires driver version " "4.32.0 or later."); goto bad; } } if (!strlen(program_id)) /* force creation of a region with no id */ dm_stats_set_program_id(dms, 1, NULL); return _do_stats_create_regions(dms, name, start, len, step, _switches[SEGMENTS_ARG], program_id, user_data); bad: dm_stats_destroy(dms); return 0; } static int _stats_delete(CMD_ARGS) { struct dm_stats *dms; uint64_t region_id, group_id; char *name = NULL; const char *program_id = DM_STATS_PROGRAM_ID; int allregions = _switches[ALL_REGIONS_ARG]; int r = 0; /* delete does not use a report */ if (_report) { dm_report_free(_report); _report = NULL; } if (_switches[REGION_ID_ARG] && _switches[GROUP_ID_ARG]) { log_error("Please use one of --regionid and --groupid."); return 0; } if (!_switches[REGION_ID_ARG] && !allregions && !_switches[GROUP_ID_ARG]) { log_error("Please specify a --regionid or --groupid, or use --allregions."); return 0; } if (names) name = names->name; else { if (!argc && !_switches[UUID_ARG] && !_switches[MAJOR_ARG]) { if (!_switches[ALL_DEVICES_ARG]) { log_error("Please specify device(s) or use " "--alldevices."); return 0; } return _process_all(cmd, subcommand, argc, argv, 0, _stats_delete); } name = argv[0]; } if (_switches[PROGRAM_ID_ARG]) program_id = _string_args[PROGRAM_ID_ARG]; if (_switches[ALL_PROGRAMS_ARG]) program_id = DM_STATS_ALL_PROGRAMS; region_id = (uint64_t) _int_args[REGION_ID_ARG]; group_id = (uint64_t) _int_args[GROUP_ID_ARG]; if (!(dms = dm_stats_create(program_id))) return_0; if (!_bind_stats_device(dms, name)) goto_out; /* allregions and group delete require a listed handle */ if ((allregions || _switches[GROUP_ID_ARG]) && !dm_stats_list(dms, program_id)) goto_out; if (allregions && !dm_stats_get_nr_regions(dms)) { /* no regions present */ r = 1; goto out; } if (_switches[GROUP_ID_ARG]) { if (!dm_stats_delete_group(dms, group_id, 1)) { log_error("Could not delete statistics group."); goto out; } printf("Deleted statistics group " FMTu64 ".\n", group_id); } else if (_switches[ALL_REGIONS_ARG]) { dm_stats_foreach_region(dms) { region_id = dm_stats_get_current_region(dms); if (!dm_stats_delete_region(dms, region_id)) { log_error("Could not delete statistics region."); goto out; } log_info("Deleted statistics region %" PRIu64, region_id); } } else { if (!dm_stats_delete_region(dms, region_id)) { log_error("Could not delete statistics region"); goto out; } log_info("Deleted statistics region " FMTu64 ".", region_id); } r = 1; out: dm_stats_destroy(dms); return r; } static int _stats_print_one_region(struct dm_stats *dms, int clear, uint64_t region_id) { char *stbuff = NULL; /*FIXME: line control for large regions */ if (!(stbuff = dm_stats_print_region(dms, region_id, 0, 0, clear))) { log_error("Could not print statistics region."); return 0; } printf("%s", stbuff); dm_stats_buffer_destroy(dms, stbuff); return 1; } static int _stats_print(CMD_ARGS) { struct dm_stats *dms; char *name, *stbuff = NULL; uint64_t region_id; unsigned clear = (unsigned) _switches[CLEAR_ARG]; int allregions = _switches[ALL_REGIONS_ARG]; int r = 0; /* print does not use a report */ if (_report) { dm_report_free(_report); _report = NULL; } if (!_switches[REGION_ID_ARG] && !allregions) { log_error("Please specify a --regionid or use --allregions."); return 0; } if (names) name = names->name; else { if (!argc && !_switches[UUID_ARG] && !_switches[MAJOR_ARG]) return _process_all(cmd, subcommand, argc, argv, 0, _stats_print); name = argv[0]; } if (!(dms = dm_stats_create(DM_STATS_PROGRAM_ID))) return_0; if (!_bind_stats_device(dms, name)) goto_out; if (!dm_stats_list(dms, NULL)) goto_out; if (allregions && !dm_stats_get_nr_regions(dms)) { r = 1; goto out; } if (!allregions) { region_id = (uint64_t) _int_args[REGION_ID_ARG]; if (!_stats_print_one_region(dms, clear, region_id)) goto_out; r = 1; goto out; } dm_stats_foreach_region(dms) { region_id = dm_stats_get_current_region(dms); if (!_stats_print_one_region(dms, clear, region_id)) goto_out; /*FIXME: line control for large regions */ if (!(stbuff = dm_stats_print_region(dms, region_id, 0, 0, clear))) { log_error("Could not print statistics region."); goto out; } printf("%s", stbuff); dm_stats_buffer_destroy(dms, stbuff); } r = 1; out: dm_stats_destroy(dms); return r; } static int _stats_report(CMD_ARGS) { int r = 0, objtype_args; struct dm_task *dmt; char *name = NULL; objtype_args = (_switches[AREA_ARG] || _switches[REGION_ARG] || _switches[GROUP_ARG]); if (_switches[PROGRAM_ID_ARG]) _program_id = _string_args[PROGRAM_ID_ARG]; if (_switches[ALL_PROGRAMS_ARG]) _program_id = ""; if (_switches[VERBOSE_ARG] && subcommand && !strcmp(subcommand, "list")) _statstype |= (DM_STATS_WALK_ALL | DM_STATS_WALK_SKIP_SINGLE_AREA); /* suppress duplicates unless the user has requested all regions */ if (subcommand && !objtype_args && !strcmp(subcommand, "report")) /* suppress duplicate rows of output */ _statstype |= (DM_STATS_WALK_ALL | DM_STATS_WALK_SKIP_SINGLE_AREA); if (names) name = names->name; else { if (!argc && !_switches[UUID_ARG] && !_switches[MAJOR_ARG]) return _process_all(cmd, subcommand, argc, argv, 0, _info); name = argv[0]; } if (!(dmt = dm_task_create(DM_DEVICE_INFO))) return_0; if (!_set_task_device(dmt, name, 0)) goto_out; if (_switches[CHECKS_ARG] && !dm_task_enable_checks(dmt)) goto_out; if (!_task_run(dmt)) goto_out; r = _display_info(dmt); out: dm_task_destroy(dmt); if (!r && _report) { dm_report_free(_report); _report = NULL; } return r; } static int _stats_group(CMD_ARGS) { const char *name, *alias = NULL, *regions = NULL; struct dm_stats *dms; uint64_t group_id; int r = 0; /* group does not use a report */ if (_report) { dm_report_free(_report); _report = NULL; } if (!_switches[REGIONS_ARG]) { log_error("Group requires --regions."); return 0; } regions = _string_args[REGIONS_ARG]; if (names) name = names->name; else { if (!argc && !_switches[UUID_ARG] && !_switches[MAJOR_ARG]) { if (!_switches[ALL_DEVICES_ARG]) { log_error("Please specify device(s) or use " "--alldevices."); return 0; } return _process_all(cmd, subcommand, argc, argv, 0, _stats_group); } name = argv[0]; } if (_switches[ALIAS_ARG]) alias = _string_args[ALIAS_ARG]; if (!(dms = dm_stats_create(DM_STATS_PROGRAM_ID))) return_0; if (!_bind_stats_device(dms, name)) goto_out; if (!dm_stats_list(dms, NULL)) goto_out; if(!dm_stats_create_group(dms, regions, alias, &group_id)) { log_error("Could not create group on %s: %s", name, regions); goto out; } printf("Grouped regions %s as group ID " FMTu64 " on %s\n", regions, group_id, name); r = 1; out: dm_stats_destroy(dms); return r; } static int _stats_ungroup(CMD_ARGS) { struct dm_stats *dms; uint64_t group_id; char *name; int r = 0; /* ungroup does not use a report */ if (_report) { dm_report_free(_report); _report = NULL; } if (!_switches[GROUP_ID_ARG]) { log_error("Please specify group id."); return 0; } group_id = (uint64_t) _int_args[GROUP_ID_ARG]; if (names) name = names->name; else { if (!argc && !_switches[UUID_ARG] && !_switches[MAJOR_ARG]) { if (!_switches[ALL_DEVICES_ARG]) { log_error("Please specify device(s) or use " "--alldevices."); return 0; } return _process_all(cmd, subcommand, argc, argv, 0, _stats_ungroup); } name = argv[0]; } if (!(dms = dm_stats_create(DM_STATS_PROGRAM_ID))) return_0; if (!_bind_stats_device(dms, name)) goto_out; if (!dm_stats_list(dms, NULL)) goto_out; if (!(r = dm_stats_delete_group(dms, group_id, 0))) log_error("Could not delete group " FMTu64 " on %s.", group_id, name); printf("Removed group ID "FMTu64" on %s\n", group_id, name); out: dm_stats_destroy(dms); return r; } static int _stats_update_file(CMD_ARGS) { uint64_t group_id, *region, *regions = NULL, count = 0; const char *program_id = DM_STATS_PROGRAM_ID; int foreground = _switches[FOREGROUND_ARG]; int verbose = _switches[VERBOSE_ARG]; char *path, *abspath = NULL; struct dm_stats *dms = NULL; dm_filemapd_mode_t mode; int fd = -1; if (names) { log_error("Device names are not compatibile with update_filemap."); return 0; } if (!_stats_check_filemap_switches()) return 0; /* _stats_update_file does not use _process_all() */ if (!argc) { log_error("update_filemap requires a file path argument"); return 0; } if (!_switches[GROUP_ID_ARG]) { log_error("--groupid is required to update a filemap group."); return 0; } path = argv[0]; if (!(abspath = _get_abspath(path))) { log_error("Could not canonicalize file name: %s", path); return 0; } group_id = (uint64_t) _int_args[GROUP_ID_ARG]; mode = _stats_get_filemapd_mode(); if (!_switches[NOMONITOR_ARG] && (mode == DM_FILEMAPD_FOLLOW_NONE)) goto bad; if (_switches[PROGRAM_ID_ARG]) program_id = _string_args[PROGRAM_ID_ARG]; if (!strlen(program_id) && !_switches[FORCE_ARG]) program_id = DM_STATS_PROGRAM_ID; if (!(dms = dm_stats_create(DM_STATS_PROGRAM_ID))) goto_bad; fd = open(abspath, O_RDONLY); if (fd < 0) { log_error("Could not open %s for reading", abspath); goto bad; } if (!dm_stats_bind_from_fd(dms, fd)) goto_bad; if (!strlen(program_id)) /* force creation of a region with no id */ dm_stats_set_program_id(dms, 1, NULL); /* * Start dmfilemapd - it will test the file descriptor to determine * whether it is necessary to call dm_stats_update_regions_from_fd(). * * If starting the daemon fails, fall back to a direct update. */ if (!_switches[NOMONITOR_ARG]) { if (dm_stats_start_filemapd(fd, group_id, abspath, mode, foreground, verbose)) goto out; log_warn("Failed to start filemap monitoring daemon."); /* fall back to one-shot update */ } /* * --nomonitor and fall back case - perform a one-shot update directly * from dmsetup. */ regions = dm_stats_update_regions_from_fd(dms, fd, group_id); if (!regions) { log_error("Could not update regions from file %s", abspath); goto bad; } for (region = regions; *region != DM_STATS_REGIONS_ALL; region++) count++; if (group_id != regions[0]) { printf("Group ID changed from " FMTu64 " to " FMTu64, group_id, regions[0]); group_id = regions[0]; } printf("%s: Updated group ID " FMTu64 " with "FMTu64" region(s).\n", path, group_id, count); out: if (close(fd)) log_sys_debug("close", abspath); free(regions); free(abspath); dm_stats_destroy(dms); return 1; bad: free(abspath); if ((fd > -1) && close(fd)) log_sys_debug("close", path); dm_stats_destroy(dms); return 0; } /* * Command dispatch tables and usage. */ static int _stats_help(CMD_ARGS); /* * dmsetup stats [options] [device_name] * dmstats [options] [device_name] * * clear [--allregions|--regionid id] [--alldevices|...] * create [--start [--length ] * [--areas ] [--areasize ] * [--programid ] [--userdata ] * [--bounds histogram_boundaries] [--precise] * [--alldevices|...] * create --filemap [--nogroup] [--nomonitor] [--follow=mode] * [--programid ] [--userdata ] * [--bounds histogram_boundaries] [--precise] [] * delete [--allprograms|--programid id] * [--allregions|--regionid id] * [--alldevices|...] * group [--alias NAME] --regions * [--allprograms|--programid id] [--alldevices|...] * list [--allprograms|--programid id] [--allregions|--regionid id] * print [--clear] [--allprograms|--programid id] * [--allregions|--regionid id] * [--alldevices|...] * report [--interval ] [--count ] * [--units ] [--programid ] [--regionid ] * [-o ] [-O|--sort ] * [-S|--select ] [--nameprefixes] * [--noheadings|--headings none|abbrev|full|0|1|2] * [--separator ] * [--allprograms|--programid id] [...] * ungroup --groupid [--allprograms|--programid id] * [--alldevices|...] */ #define INDENT "\n\t " /* groups of commonly used options */ #define AREA_OPTS "[--areas ] [--areasize ] " #define REGION_OPTS "[--start [--length ]" INDENT AREA_OPTS #define ID_OPTS "[--programid ] [--userdata ] " #define SELECT_OPTS "[--programid ] [--regionid ] " #define HIST_OPTS "[--bounds histogram_boundaries] " #define PRECISE_OPTS "[--precise] " #define SEGMENTS_OPT "[--segments] " #define EXTRA_OPTS HIST_OPTS PRECISE_OPTS #define FILE_MONITOR_OPTS "[--nomonitor] [--follow mode]" #define GROUP_ID_OPT "--groupid " #define ALL_PROGS_OPT "[--allprograms|--programid id] " #define ALL_REGIONS_OPT "[--allregions|--regionid id] " #define ALL_DEVICES_OPT "[--alldevices|...] " #define ALL_PROGS_REGIONS_DEVICES ALL_PROGS_OPT INDENT ALL_REGIONS_OPT INDENT ALL_DEVICES_OPT #define FIELD_OPTS "[-o ] [-O|--sort ]" #define DM_REPORT_OPTS FIELD_OPTS INDENT "[-S|--select ] [--nameprefixes]" INDENT \ "[--noheadings] [--separator ]" /* command options */ #define CREATE_OPTS REGION_OPTS INDENT ID_OPTS INDENT EXTRA_OPTS INDENT SEGMENTS_OPT #define FILEMAP_OPTS "--filemap [--nogroup] " FILE_MONITOR_OPTS INDENT ID_OPTS INDENT EXTRA_OPTS #define PRINT_OPTS "[--clear] " ALL_PROGS_REGIONS_DEVICES #define REPORT_OPTS "[--interval ] [--count ]" INDENT \ "[--units ] " SELECT_OPTS INDENT DM_REPORT_OPTS INDENT ALL_PROGS_OPT #define GROUP_OPTS "[--alias NAME] --regions " INDENT ALL_PROGS_OPT ALL_DEVICES_OPT #define UNGROUP_OPTS GROUP_ID_OPT ALL_PROGS_OPT INDENT ALL_DEVICES_OPT #define UPDATE_OPTS GROUP_ID_OPT INDENT FILE_MONITOR_OPTS " " /* * The 'create' command has two entries in the table, to allow for the * the fact that 'create' and 'create --filemap' have largely disjoint * sets of options. */ static const struct command _stats_subcommands[] = { {"help", "", 0, 0, 0, 0, _stats_help}, {"clear", ALL_REGIONS_OPT ALL_DEVICES_OPT, 0, -1, 1, 0, _stats_clear}, {"create", CREATE_OPTS ALL_DEVICES_OPT, 0, -1, 1, 0, _stats_create}, {"create", FILEMAP_OPTS "", 0, -1, 1, 0, _stats_create}, {"delete", ALL_PROGS_REGIONS_DEVICES, 1, -1, 1, 0, _stats_delete}, {"group", GROUP_OPTS, 1, -1, 1, 0, _stats_group}, {"list", ALL_PROGS_OPT ALL_REGIONS_OPT, 0, -1, 1, 0, _stats_report}, {"print", PRINT_OPTS, 0, -1, 1, 0, _stats_print}, {"report", REPORT_OPTS "[...]", 0, -1, 1, 0, _stats_report}, {"ungroup", UNGROUP_OPTS, 1, -1, 1, 0, _stats_ungroup}, {"update_filemap", UPDATE_OPTS, 1, 1, 0, 0, _stats_update_file}, {"version", "", 0, -1, 1, 0, _version}, }; #undef AREA_OPTS #undef REGION_OPTS #undef ID_OPTS #undef SELECT_OPTS #undef HIST_OPTS #undef PRECISE_OPTS #undef EXTRA_OPTS #undef ALL_PROGS_OPT #undef ALL_REGIONS_OPT #undef ALL_DEVICES_OPT #undef ALL_PROGS_REGIONS_DEVICES #undef FIELD_OPTS #undef DM_REPORT_OPTS #undef CREATE_OPTS #undef FILEMAP_OPTS #undef PRINT_OPTS #undef REPORT_OPTS #undef GROUP_OPTS #undef UNGROUP_OPTS static int _dmsetup_help(CMD_ARGS); static const struct command _dmsetup_commands[] = { {"help", "[-c|-C|--columns]", 0, 0, 0, 0, _dmsetup_help}, {"create", "\n" "\t [-j|--major -m|--minor ]\n" "\t [-U|--uid ] [-G|--gid ] [-M|--mode ]\n" "\t [-u|--uuid ] [--addnodeonresume|--addnodeoncreate]\n" "\t [--readahead {[+]|auto|none}]\n" "\t [-n|--notable|--table {|}]\n" "\tcreate --concise []", 0, 2, 0, 0, _create}, {"remove", "[--deferred] [-f|--force] [--retry] ...", 0, -1, 2, 0, _remove}, {"remove_all", "[-f|--force]", 0, 0, 0, 0, _remove_all}, {"suspend", "[--noflush] [--nolockfs] ...", 0, -1, 2, 0, _suspend}, {"resume", "[--noflush] [--nolockfs] ...\n" "\t [--addnodeonresume|--addnodeoncreate]\n" "\t [--readahead {[+]|auto|none}]", 0, -1, 2, 0, _resume}, {"load", " [
|]", 0, 2, 0, 0, _load}, {"clear", "", 0, -1, 2, 0, _clear}, {"reload", " [
|]", 0, 2, 0, 0, _load}, {"wipe_table", "[-f|--force] [--noflush] [--nolockfs] ...", 0, -1, 2, 0, _error_device}, {"rename", " [--setuuid] ", 1, 2, 0, 0, _rename}, {"measure", "[...]", 0, -1, 2, 0, _status}, {"message", " ", 2, -1, 0, 0, _message}, {"ls", "[--target ] [--exec ] [-o ] [--tree]", 0, 0, 0, 0, _ls}, {"info", "[...]", 0, -1, 1, 0, _info}, {"deps", "[-o ] [...]", 0, -1, 2, 0, _deps}, {"stats", " [] [...]", 1, -1, 1, 1, _stats}, {"status", "[...] [--noflush] [--target ]", 0, -1, 2, 0, _status}, {"table", "[...] [--concise] [--target ] [--showkeys]", 0, -1, 2, 0, _status}, {"wait", " [] [--noflush]", 0, 2, 0, 0, _wait}, {"mknodes", "[...]", 0, -1, 1, 0, _mknodes}, {"mangle", "[...]", 0, -1, 1, 0, _mangle}, {"udevcreatecookie", "", 0, 0, 0, 0, _udevcreatecookie}, {"udevreleasecookie", "[]", 0, 1, 0, 0, _udevreleasecookie}, {"udevflags", "", 1, 1, 0, 0, _udevflags}, {"udevcomplete", "", 1, 1, 0, 0, _udevcomplete}, {"udevcomplete_all", "[]", 0, 1, 0, 0, _udevcomplete_all}, {"udevcookies", "", 0, 0, 0, 0, _udevcookies}, {"target-version", "[...]", 1, -1, 1, 0, _target_version}, {"targets", "", 0, 0, 0, 0, _targets}, {"version", "", 0, 0, 0, 0, _version}, {"setgeometry", " ", 5, 5, 0, 0, _setgeometry}, {"splitname", " []", 1, 2, 0, 0, _splitname}, }; /* * Usage and help text. */ static void _devmap_name_usage(FILE *out) { fprintf(out, "Usage: " DEVMAP_NAME_CMD_NAME " \n\n"); } static void _stats_usage(FILE *out) { int i; fprintf(out, "Usage:\n\n" "%s\n\t" "[-h|--help]\n\t" "[-v|--verbose [-v|--verbose ...]]\n\t" "[--areas ] [--areasize ]\n\t" "[--userdata ] [--clear]\n\t" "[--count ] [--interval ]\n\t" "[-o ] [-O|--sort ]\n\t" " [--programid ]\n\t" "[--start ] [--length ]\n\t" "[--segments] [--units ]\n\n", _base_commands[_base_command].name); for (i = 0; i < DM_ARRAY_SIZE(_stats_subcommands); i++) fprintf(out, "\t%s %s\n", _stats_subcommands[i].name, _stats_subcommands[i].help); fprintf(out, "\n may be device name or (if only one) -u or -j -m \n" " are comma-separated. Use 'help -c' for list.\n\n"); } static void _dmsetup_usage(FILE *out) { int i; fprintf(out, "Usage:\n\n" "%s\n\t" "[--version] [-h|--help [-c|-C|--columns]]\n\t" "[-v|--verbose [-v|--verbose ...]] [-f|--force]\n\t" "[--checks] [--manglename {none|hex|auto}]\n\t" "[-r|--readonly] [--noopencount] [--noflush] [--nolockfs] [--inactive]\n\t" "[--udevcookie ] [--noudevrules] [--noudevsync] [--verifyudev]\n\t" "[-y|--yes] [--readahead {[+]|auto|none}] [--retry]\n\t" "[-c|-C|--columns] [-o ] [-O|--sort ]\n\t" "[-S|--select ] [--nameprefixes]\n\t" "[--noheadings|--headings none|abbrev|full|0|1|2]\n\t" "[--separator ]\n\n", _base_commands[_base_command].name); for (i = 0; i < DM_ARRAY_SIZE(_dmsetup_commands); ++i) fprintf(out, "\t%s %s\n", _dmsetup_commands[i].name, _dmsetup_commands[i].help); fprintf(out, "\n may be device name or (if only one) -u or " "-j -m \n" " is one of 'none', 'auto' and 'hex'.\n" " are comma-separated. Use 'help -c' for list.\n" " has single-device entries separated by semi-colons:\n" " ,,,,
\n" " where is 'ro' or 'rw' (the default) and any of , \n" " and may be empty. Separate extra table lines with commas.\n" " E.g.: dev1,,,,0 100 linear 253:1 0,100 100 error;dev2,,,ro,0 1 error\n" "Table_file contents may be supplied on stdin.\n" "Options are: devno, devname, blkdevname.\n" "Tree specific options are: ascii, utf, vt100; compact, inverted, notrunc;\n" " blkdevname, [no]device, active, open, rw and uuid.\n\n"); } static void _losetup_usage(FILE *out) { fprintf(out, "Usage:\n\n" "%s [-d|-a] [-e encryption] " "[-o offset] [-f|loop_device] [file]\n\n", _base_commands[_base_command].name); } static void _usage(FILE *out) { switch (_base_commands[_base_command].type) { case DMSETUP_TYPE: _dmsetup_usage(out); break; case LOSETUP_TYPE: _losetup_usage(out); break; case STATS_TYPE: _stats_usage(out); break; case DEVMAP_NAME_TYPE: _devmap_name_usage(out); break; } } static int _stats_help(CMD_ARGS) { _usage(stderr); if (_switches[COLS_ARG] || (argc && !strcmp(argv[0], "report"))) { _switches[OPTIONS_ARG] = 1; _string_args[OPTIONS_ARG] = (char *) "help"; _switches[SORT_ARG] = 0; if (_report) { dm_report_free(_report); _report = NULL; } (void) _report_init(cmd, "help"); if (_report) { dm_report_free(_report); _report = NULL; } } return 1; } static int _dmsetup_help(CMD_ARGS) { _usage(stderr); if (_switches[COLS_ARG]) { _switches[OPTIONS_ARG] = 1; _string_args[OPTIONS_ARG] = (char *) "help"; _switches[SORT_ARG] = 0; if (_report) { dm_report_free(_report); _report = NULL; } (void) _report_init(cmd, ""); if (_report) { dm_report_free(_report); _report = NULL; } } return 1; } static const struct command *_find_command(const struct command *commands, size_t cmd_cnt, const char *name) { int i; if (name) for (i = 0; i < cmd_cnt; i++) if (!strcmp(commands[i].name, name)) return commands + i; return NULL; } static const struct command *_find_dmsetup_command(const char *name) { return _find_command(_dmsetup_commands, DM_ARRAY_SIZE(_dmsetup_commands), name); } static const struct command *_find_stats_subcommand(const char *name) { return _find_command(_stats_subcommands, DM_ARRAY_SIZE(_stats_subcommands), name); } static int _stats(CMD_ARGS) { const struct command *stats_cmd; if (_switches[AREA_ARG] || _switches[REGION_ARG] || _switches[GROUP_ARG]) _statstype = 0; /* switches will OR flags in */ else _statstype = DM_STATS_WALK_REGION | DM_STATS_WALK_GROUP; if (_switches[AREA_ARG]) _statstype |= DM_STATS_WALK_AREA; if (_switches[REGION_ARG]) _statstype |= DM_STATS_WALK_REGION; if (_switches[GROUP_ARG]) _statstype |= DM_STATS_WALK_GROUP; if (!(stats_cmd = _find_stats_subcommand(subcommand))) { log_error("Unknown stats command."); _stats_help(stats_cmd, NULL, argc, argv, NULL, multiple_devices); return 0; } if (_switches[ALL_PROGRAMS_ARG] && _switches[PROGRAM_ID_ARG]) { log_error("Please supply one of --allprograms and --programid"); return 0; } if (_switches[ALL_REGIONS_ARG] && _switches[REGION_ID_ARG]) { log_error("Please supply one of --allregions and --regionid"); return 0; } if (_switches[FOLLOW_ARG] && _switches[NOMONITOR_ARG]) { log_error("Use of --follow is incompatible with --nomonitor."); return 0; } /* * Pass the sub-command through to allow a single function to be * used to implement several distinct sub-commands (e.g. 'report' * and 'list' share a single implementation. */ if (!stats_cmd->fn(stats_cmd, subcommand, argc, argv, NULL, multiple_devices)) return_0; return 1; } static int _process_tree_options(const char *options) { const char *s, *end; struct winsize winsz = { 0 }; size_t len; /* Symbol set default */ if (!strcmp(nl_langinfo(CODESET), "UTF-8")) _tsym = &_tsym_utf; else _tsym = &_tsym_ascii; /* Default */ _tree_switches[TR_DEVICE] = 1; _tree_switches[TR_TRUNCATE] = 1; /* parse */ for (s = options; s && *s; s++) { len = 0; for (end = s; *end && *end != ','; end++, len++) ; if (!strncmp(s, "device", len)) _tree_switches[TR_DEVICE] = 1; else if (!strncmp(s, "blkdevname", len)) _tree_switches[TR_BLKDEVNAME] = 1; else if (!strncmp(s, "nodevice", len)) _tree_switches[TR_DEVICE] = 0; else if (!strncmp(s, "status", len)) _tree_switches[TR_STATUS] = 1; else if (!strncmp(s, "table", len)) _tree_switches[TR_TABLE] = 1; else if (!strncmp(s, "active", len)) _tree_switches[TR_ACTIVE] = 1; else if (!strncmp(s, "open", len)) _tree_switches[TR_OPENCOUNT] = 1; else if (!strncmp(s, "uuid", len)) _tree_switches[TR_UUID] = 1; else if (!strncmp(s, "rw", len)) _tree_switches[TR_RW] = 1; else if (!strncmp(s, "utf", len)) _tsym = &_tsym_utf; else if (!strncmp(s, "vt100", len)) _tsym = &_tsym_vt100; else if (!strncmp(s, "ascii", len)) _tsym = &_tsym_ascii; else if (!strncmp(s, "inverted", len)) _tree_switches[TR_BOTTOMUP] = 1; else if (!strncmp(s, "compact", len)) _tree_switches[TR_COMPACT] = 1; else if (!strncmp(s, "notrunc", len)) _tree_switches[TR_TRUNCATE] = 0; else { log_error("Tree options not recognised: %s.", s); return 0; } if (!*end) break; s = end; } /* Truncation doesn't work well with vt100 drawing char */ if (_tsym != &_tsym_vt100) if (ioctl(1, (unsigned long) TIOCGWINSZ, &winsz) >= 0 && winsz.ws_col > 3) _termwidth = winsz.ws_col - 3; return 1; } static char *_parse_loop_device_name(const char *dev, const char *dev_dir) { char *buf; char *device = NULL; if (!(buf = malloc(PATH_MAX))) return_NULL; if (dev[0] == '/') { if (!(device = _get_abspath(dev))) goto_bad; if (strncmp(device, dev_dir, strlen(dev_dir))) goto_bad; /* If dev_dir does not end in a slash, ensure that the following byte in the device string is "/". */ if (dev_dir[strlen(dev_dir) - 1] != '/' && device[strlen(dev_dir)] != '/') goto_bad; if (!_dm_strncpy(buf, strrchr(device, '/') + 1, PATH_MAX)) goto_bad; free(device); } else { /* check for device number */ if (strncmp(dev, "loop", sizeof("loop") - 1)) goto_bad; if (!_dm_strncpy(buf, dev, PATH_MAX)) goto_bad; } return buf; bad: free(device); free(buf); return NULL; } /* * create a table for a mapped device using the loop target. */ static int _loop_table(char *table, size_t tlen, char *file, char *dev __attribute__((unused)), off_t off) { struct stat fbuf; off_t size, sectors; int fd = -1; #ifdef HAVE_SYS_STATVFS_H struct statvfs fsbuf; off_t blksize; #endif if (!_switches[READ_ONLY]) fd = open(file, O_RDWR); if (fd < 0) { _switches[READ_ONLY]++; fd = open(file, O_RDONLY); } if (fd < 0) goto_bad; if (fstat(fd, &fbuf)) goto_bad; size = (fbuf.st_size - off); sectors = size >> SECTOR_SHIFT; if (_switches[VERBOSE_ARG]) log_error(LOSETUP_CMD_NAME ": set loop size to %llukB (%llu sectors).", (long long unsigned) sectors >> 1, (long long unsigned) sectors); #ifdef HAVE_SYS_STATVFS_H if (fstatvfs(fd, &fsbuf)) goto_bad; /* FIXME Fragment size currently unused */ blksize = fsbuf.f_frsize; #endif if (close(fd)) log_sys_debug("close", file); if (dm_snprintf(table, tlen, "%llu %llu loop %s %llu\n", 0ULL, (long long unsigned)sectors, file, (long long unsigned)off) < 0) return_0; if (_switches[VERBOSE_ARG] > 1) log_verbose("Table: %s", table); return 1; bad: if (fd > -1 && close(fd)) log_sys_error("close", file); return 0; } static int _process_losetup_switches(const char *base, int *argcp, char ***argvp, const char *dev_dir) { int c; int encrypt_loop = 0, delete = 0, find = 0, show_all = 0; char *device_name = NULL; char *loop_file = NULL; off_t offset = 0; #ifdef HAVE_GETOPTLONG static const struct option _long_options[] = { {0, 0, 0, 0} }; #endif optarg = (char*) ""; optind = OPTIND_INIT; while ((c = GETOPTLONG_FN(*argcp, *argvp, "ade:fo:v", _long_options, NULL)) != -1 ) { if (c == ':' || c == '?') return_0; if (c == 'a') show_all++; if (c == 'd') delete++; if (c == 'e') encrypt_loop++; if (c == 'f') find++; if (c == 'o') offset = atoi(optarg); if (c == 'v') _switches[VERBOSE_ARG]++; } *argvp += optind ; *argcp -= optind ; if (encrypt_loop){ log_error("%s: Sorry, cryptoloop is not yet implemented " "in this version.", base); return 0; } if (show_all) { log_error("%s: Sorry, show all is not yet implemented " "in this version.", base); return 0; } if (find) { log_error("%s: Sorry, find is not yet implemented " "in this version.", base); if (!*argcp) return 0; } if (!*argcp) { log_error("%s: Please specify loop_device.", base); _usage(stderr); return 0; } if (!(device_name = _parse_loop_device_name((*argvp)[0], dev_dir))) { log_error("%s: Could not parse loop_device %s", base, (*argvp)[0]); _usage(stderr); return 0; } if (delete) { *argcp = 1; (*argvp)[0] = device_name; _command = "remove"; return 1; } if (*argcp != 2) { log_error("%s: Too few arguments.", base); _usage(stderr); free(device_name); return 0; } /* FIXME move these to make them available to native dmsetup */ if (!(loop_file = _get_abspath((*argvp)[(find) ? 0 : 1]))) { log_error("%s: Could not parse loop file name %s.", base, (*argvp)[1]); _usage(stderr); free(device_name); return 0; } _table = malloc(LOOP_TABLE_SIZE); if (!_table || !_loop_table(_table, (size_t) LOOP_TABLE_SIZE, loop_file, device_name, offset)) { log_error("Could not build device-mapper table for %s.", (*argvp)[0]); free(loop_file); free(_table); free(device_name); return 0; } _switches[TABLE_ARG]++; _command = "create"; (*argvp)[0] = device_name ; *argcp = 1; free(loop_file); return 1; } static int _process_options(const char *options) { const char *s, *end; size_t len; /* Tree options are processed separately. */ if (_switches[TREE_ARG]) return _process_tree_options(_string_args[OPTIONS_ARG]); /* Column options are processed separately by _report_init (called later). */ if (_switches[COLS_ARG]) return 1; /* No options specified. */ if (!_switches[OPTIONS_ARG]) return 1; /* Set defaults. */ _dev_name_type = DN_DEVNO; /* Parse. */ for (s = options; s && *s; s++) { len = 0; for (end = s; *end && *end != ','; end++, len++) ; if (!strncmp(s, "devno", len)) _dev_name_type = DN_DEVNO; else if (!strncmp(s, "blkdevname", len)) _dev_name_type = DN_BLK; else if (!strncmp(s, "devname", len)) _dev_name_type = DN_MAP; else { log_error("Option not recognised: %s.", s); return 0; } if (!*end) break; s = end; } return 1; } static int _process_switches(int *argcp, char ***argvp, const char *dev_dir) { const char *base; char *namebase, *s; int c, r, i; #ifdef HAVE_GETOPTLONG static const struct option _long_options[] = { {"addnodeoncreate", no_argument, 0, ADD_NODE_ON_CREATE_ARG}, {"addnodeonresume", no_argument, 0, ADD_NODE_ON_RESUME_ARG}, {"alias", required_argument, 0, ALIAS_ARG}, {"alldevices", no_argument, 0, ALL_DEVICES_ARG}, {"allprograms", no_argument, 0, ALL_PROGRAMS_ARG}, {"allregions", no_argument, 0, ALL_REGIONS_ARG}, {"area", no_argument, 0, AREA_ARG}, {"areas", required_argument, 0, AREAS_ARG}, {"areasize", required_argument, 0, AREA_SIZE_ARG}, {"bounds", required_argument, 0, BOUNDS_ARG}, {"checks", no_argument, 0, CHECKS_ARG}, {"clear", no_argument, 0, CLEAR_ARG}, {"columns", no_argument, 0, COLS_ARG}, {"concise", no_argument, 0, CONCISE_ARG}, {"count", required_argument, 0, COUNT_ARG}, {"deferred", no_argument, 0, DEFERRED_ARG}, {"exec", required_argument, 0, EXEC_ARG}, {"filemap", no_argument, 0, FILEMAP_ARG}, {"follow", required_argument, 0, FOLLOW_ARG}, {"force", no_argument, 0, FORCE_ARG}, {"foreground", no_argument, 0, FOREGROUND_ARG}, {"gid", required_argument, 0, GID_ARG}, {"group", no_argument, 0, GROUP_ARG}, {"groupid", required_argument, 0, GROUP_ID_ARG}, {"headings", required_argument, 0, HEADINGS_ARG}, {"help", no_argument, 0, HELP_ARG}, {"histogram", no_argument, 0, HISTOGRAM_ARG}, {"inactive", no_argument, 0, INACTIVE_ARG}, {"interval", required_argument, 0, INTERVAL_ARG}, {"length", required_argument, 0, LENGTH_ARG}, {"major", required_argument, 0, MAJOR_ARG}, {"manglename", required_argument, 0, MANGLENAME_ARG}, {"minor", required_argument, 0, MINOR_ARG}, {"mode", required_argument, 0, MODE_ARG}, {"nameprefixes", no_argument, 0, NAMEPREFIXES_ARG}, {"noflush", no_argument, 0, NOFLUSH_ARG}, {"nogroup", no_argument, 0, NOGROUP_ARG}, {"noheadings", no_argument, 0, NOHEADINGS_ARG}, {"nolockfs", no_argument, 0, NOLOCKFS_ARG}, {"nomonitor", no_argument, 0, NOMONITOR_ARG}, {"noopencount", no_argument, 0, NOOPENCOUNT_ARG}, {"nosuffix", no_argument, 0, NOSUFFIX_ARG}, {"notable", no_argument, 0, NOTABLE_ARG}, {"notimesuffix", no_argument, 0, NOTIMESUFFIX_ARG}, {"noudevrules", no_argument, 0, NOUDEVRULES_ARG}, {"noudevsync", no_argument, 0, NOUDEVSYNC_ARG}, {"options", required_argument, 0, OPTIONS_ARG}, {"precise", no_argument, 0, PRECISE_ARG}, {"programid", required_argument, 0, PROGRAM_ID_ARG}, {"raw", no_argument, 0, RAW_ARG}, {"readahead", required_argument, 0, READAHEAD_ARG}, {"readonly", no_argument, 0, READ_ONLY}, {"region", no_argument, 0, REGION_ARG}, {"regionid", required_argument, 0, REGION_ID_ARG}, {"regions", required_argument, 0, REGIONS_ARG}, {"relative", no_argument, 0, RELATIVE_ARG}, {"retry", no_argument, 0, RETRY_ARG}, {"rows", no_argument, 0, ROWS_ARG}, {"segments", no_argument, 0, SEGMENTS_ARG}, {"select", required_argument, 0, SELECT_ARG}, {"separator", required_argument, 0, SEPARATOR_ARG}, {"setuuid", no_argument, 0, SETUUID_ARG}, {"showkeys", no_argument, 0, SHOWKEYS_ARG}, {"sort", required_argument, 0, SORT_ARG}, {"start", required_argument, 0, START_ARG}, {"table", required_argument, 0, TABLE_ARG}, {"target", required_argument, 0, TARGET_ARG}, {"tree", no_argument, 0, TREE_ARG}, {"udevcookie", required_argument, 0, UDEVCOOKIE_ARG}, {"uid", required_argument, 0, UID_ARG}, {"unbuffered", no_argument, 0, UNBUFFERED_ARG}, {"units", required_argument, 0, UNITS_ARG}, {"unquoted", no_argument, 0, UNQUOTED_ARG}, {"userdata", required_argument, 0, USER_DATA_ARG}, {"uuid", required_argument, 0, UUID_ARG}, {"verbose", required_argument, 0, VERBOSE_ARG}, {"verifyudev", no_argument, 0, VERIFYUDEV_ARG}, {"version", no_argument, 0, VERSION_ARG}, {"yes", no_argument, 0, YES_ARG}, {0, 0, 0, 0} }; #else struct option _long_options; #endif /* * Zero all the index counts. */ memset(&_switches, 0, sizeof(_switches)); memset(&_int_args, 0, sizeof(_int_args)); _read_ahead_flags = 0; if (!(namebase = strdup((*argvp)[0]))) { log_error("Failed to duplicate name."); return 0; } base = dm_basename(namebase); i = 0; do { if (!strcmp(base, _base_commands[i].name)) { _base_command = _base_commands[i].command; _base_command_type = _base_commands[i].type; break; } } while (++i < _num_base_commands); free(namebase); if (_base_command_type == DEVMAP_NAME_TYPE) { _switches[COLS_ARG]++; _switches[NOHEADINGS_ARG]++; _switches[OPTIONS_ARG]++; _switches[MAJOR_ARG]++; _switches[MINOR_ARG]++; _string_args[OPTIONS_ARG] = (char *) "name"; if (*argcp == 3) { _int_args[MAJOR_ARG] = atoi((*argvp)[1]); _int_args[MINOR_ARG] = atoi((*argvp)[2]); *argcp -= 2; *argvp += 2; } else if ((*argcp == 2) && (2 == sscanf((*argvp)[1], "%i:%i", &_int_args[MAJOR_ARG], &_int_args[MINOR_ARG]))) { *argcp -= 1; *argvp += 1; } else { _usage(stderr); return 0; } _command = "info"; (*argvp)++; (*argcp)--; return 1; } if (_base_command_type == LOSETUP_TYPE) { r = _process_losetup_switches(_base_commands[_base_command].name, argcp, argvp, dev_dir); return r; } optarg = (char*) ""; optind = OPTIND_INIT; while ((c = GETOPTLONG_FN(*argcp, *argvp, "cCfG:hj:m:M:no:O:rS:u:U:vy", _long_options, NULL)) != -1) { switch (c) { case ':': case '?': return_0; case 'C': /* 'C' == 'c' COLS_ARG */ _switches[COLS_ARG]++; break; default: if (c >= NUM_SWITCHES) { log_error("Unrecognized options %d.", c); return 0; } /* plain switch */ _switches[c]++; } switch (c) { case ALIAS_ARG: case AREA_SIZE_ARG: case BOUNDS_ARG: case FOLLOW_ARG: case LENGTH_ARG: case OPTIONS_ARG: case PROGRAM_ID_ARG: case REGIONS_ARG: case SELECT_ARG: case SEPARATOR_ARG: case SORT_ARG: case START_ARG: case UNITS_ARG: case USER_DATA_ARG: /* with string arg */ _string_args[c] = optarg; break; case AREAS_ARG: case GID_ARG: case GROUP_ID_ARG: case MAJOR_ARG: case MINOR_ARG: case REGION_ID_ARG: case UID_ARG: /* with int arg */ _int_args[c] = atoi(optarg); break; case EXEC_ARG: _command_to_exec = optarg; break; case TARGET_ARG: _target = optarg; break; case UUID_ARG: _uuid = optarg; break; case UDEVCOOKIE_ARG: _udev_cookie = _get_cookie_value(optarg); break; case COUNT_ARG: _int_args[COUNT_ARG] = atoi(optarg); if (_int_args[COUNT_ARG] < 0) { log_error("Count must be zero or greater."); return 0; } break; case MODE_ARG: /* FIXME Accept modes as per chmod */ errno = 0; _int_args[MODE_ARG] = (int) strtol(optarg, &s, 8); if (errno || !s || *s || !_int_args[MODE_ARG]) { log_error("Invalid argument for --mode: %s. %s", optarg, errno ? strerror(errno) : ""); return 0; } break; case HEADINGS_ARG: if (!strcasecmp(optarg, "none") || !strcmp(optarg, "0")) _int_args[HEADINGS_ARG] = 0; else if (!strcasecmp(optarg, "abbrev") || !strcmp(optarg, "1")) _int_args[HEADINGS_ARG] = 1; else if (!strcasecmp(optarg, "full") || !strcmp(optarg, "2")) _int_args[HEADINGS_ARG] = 2; else { log_error("Unknown headings type."); return 0; } break; case INTERVAL_ARG: _int_args[INTERVAL_ARG] = atoi(optarg); if (_int_args[INTERVAL_ARG] <= 0) { log_error("Interval must be a positive integer."); return 0; } break; case MANGLENAME_ARG: if (!strcasecmp(optarg, "none")) _int_args[MANGLENAME_ARG] = DM_STRING_MANGLING_NONE; else if (!strcasecmp(optarg, "auto")) _int_args[MANGLENAME_ARG] = DM_STRING_MANGLING_AUTO; else if (!strcasecmp(optarg, "hex")) _int_args[MANGLENAME_ARG] = DM_STRING_MANGLING_HEX; else { log_error("Unknown name mangling mode."); return 0; } dm_set_name_mangling_mode((dm_string_mangling_t) _int_args[MANGLENAME_ARG]); break; case READAHEAD_ARG: if (!strcasecmp(optarg, "auto")) _int_args[READAHEAD_ARG] = DM_READ_AHEAD_AUTO; else if (!strcasecmp(optarg, "none")) _int_args[READAHEAD_ARG] = DM_READ_AHEAD_NONE; else { for (s = optarg; isspace(*s); s++) ; if (*s == '+') _read_ahead_flags = DM_READ_AHEAD_MINIMUM_FLAG; _int_args[READAHEAD_ARG] = atoi(optarg); if (_int_args[READAHEAD_ARG] < -1) { log_error("Negative read ahead value " "(%d) is not understood.", _int_args[READAHEAD_ARG]); return 0; } } break; case TABLE_ARG: if (!(_table = strdup(optarg))) { log_error("Could not allocate memory for table string."); return 0; } break; } } if (_switches[VERBOSE_ARG] > 1) { dm_log_init_verbose(_switches[VERBOSE_ARG] - 1); if (_switches[VERBOSE_ARG] > 2) { if (!(_initial_timestamp = dm_timestamp_alloc())) stack; else if (!dm_timestamp_get(_initial_timestamp)) stack; else log_debug("Timestamp: 0.000000000 seconds"); } } if ((_switches[MAJOR_ARG] && !_switches[MINOR_ARG]) || (!_switches[MAJOR_ARG] && _switches[MINOR_ARG])) { log_error("Please specify both major number and minor number."); return 0; } if (_switches[TABLE_ARG] && _switches[NOTABLE_ARG]) { log_error("--table and --notable are incompatible."); return 0; } if (_switches[ADD_NODE_ON_RESUME_ARG] && _switches[ADD_NODE_ON_CREATE_ARG]) { log_error("--addnodeonresume and --addnodeoncreate are incompatible."); return 0; } *argvp += optind; *argcp -= optind; if (!*argcp) _command = NULL; else if (!strcmp((*argvp)[0], "stats")) { _base_command = DMSETUP_STATS_CMD; _base_command_type = STATS_TYPE; _command = "stats"; (*argvp)++; (*argcp)--; } else if (_base_command == DMSTATS_CMD) { _command = "stats"; } else if (*argcp) { _command = (*argvp)[0]; (*argvp)++; (*argcp)--; } return 1; } static int _perform_command_for_all_repeatable_args(CMD_ARGS) { do { if (!cmd->fn(cmd, subcommand, argc, argv++, NULL, multiple_devices)) { log_error("Command failed."); return 0; } } while (cmd->repeatable_cmd && argc-- > 1); return 1; } static int _do_report_wait(void) { return _do_timer_wait(); } int main(int argc, char **argv) { int ret = 1, r; const char *dev_dir; const struct command *cmd; const char *subcommand = ""; int multiple_devices; (void) setlocale(LC_ALL, ""); dev_dir = getenv(DM_DEV_DIR_ENV_VAR_NAME); if (dev_dir && *dev_dir) { if (!dm_set_dev_dir(dev_dir)) { log_error("Invalid DM_DEV_DIR environment variable value."); goto out; } } else dev_dir = DEFAULT_DM_DEV_DIR; if (!_process_switches(&argc, &argv, dev_dir)) { log_error("Couldn't process command line."); goto out; } if (_switches[HELP_ARG]) { switch (_base_command_type) { case STATS_TYPE: if ((cmd = _find_stats_subcommand("help"))) goto doit; goto unknown; default: if ((cmd = _find_dmsetup_command("help"))) goto doit; goto unknown; } } if (_switches[VERSION_ARG]) { switch (_base_command_type) { case STATS_TYPE: if ((cmd = _find_stats_subcommand("version"))) goto doit; goto unknown; default: if ((cmd = _find_dmsetup_command("version"))) goto doit; goto unknown; } } if (!_command) { _usage(stderr); goto out; } if (!(cmd = _find_dmsetup_command(_command))) { unknown: log_error("Unknown command."); _usage(stderr); goto out; } if (argc < cmd->min_args || (cmd->max_args >= 0 && argc > cmd->max_args)) { log_error("Incorrect number of arguments."); _usage(stderr); goto out; } if (!_switches[COLS_ARG] && !strcmp(cmd->name, "splitname")) _switches[COLS_ARG]++; if (!strcmp(cmd->name, "stats")) { _switches[COLS_ARG]++; if (!_switches[UNITS_ARG]) { _switches[UNITS_ARG]++; _string_args[UNITS_ARG] = (char *) "h"; } } if (!strcmp(cmd->name, "mangle")) dm_set_name_mangling_mode(DM_STRING_MANGLING_NONE); if (!_process_options(_string_args[OPTIONS_ARG])) { log_error("Couldn't process command line."); goto out; } #ifdef UDEV_SYNC_SUPPORT if (!_set_up_udev_support(dev_dir)) goto_out; #endif /* * Extract subcommand? * dmsetup [args...] */ if (cmd->has_subcommands) { subcommand = argv[0]; argc--, argv++; } /* Default to success */ ret = 0; /* When -S is given, store the real command for later and run "info -c" first */ if (_switches[SELECT_ARG] && (cmd->repeatable_cmd == 2)) { _selection_cmd = cmd; _switches[COLS_ARG] = 1; if (!(cmd = _find_dmsetup_command("info"))) { log_error(INTERNAL_ERROR "finding dmsetup info command struct."); goto out; } } if (_switches[COLS_ARG]) { if (!_report_init(cmd, subcommand)) ret = 1; if (ret || !_report) goto_out; } if (_switches[COUNT_ARG] && _int_args[COUNT_ARG]) _count = (uint64_t)_int_args[COUNT_ARG]; else if (_switches[COUNT_ARG] || _switches[INTERVAL_ARG]) _count = UINT64_MAX; if (_switches[UNITS_ARG]) { _disp_factor = _factor_from_units(_string_args[UNITS_ARG], &_disp_units); if (!_disp_factor) { log_error("Invalid --units argument."); ret = 1; goto out; } } /* Start interval timer. */ if (_count > 1) if (!_start_timer()) { ret = 1; goto_out; } doit: multiple_devices = (cmd->repeatable_cmd && argc != 1 && (argc || (!_switches[UUID_ARG] && !_switches[MAJOR_ARG]))); do { r = _perform_command_for_all_repeatable_args(cmd, subcommand, argc, argv, NULL, multiple_devices); if (_concise_output_produced) { putchar('\n'); fflush(stdout); } if (_report) { /* only output headings for repeating reports */ if (_int_args[COUNT_ARG] != 1 && !dm_report_is_empty(_report)) dm_report_column_headings(_report); dm_report_output(_report); if (_count > 1 && r) { putchar('\n'); fflush(stdout); /* wait for --interval and update timestamps */ if (!_do_report_wait()) { ret = 1; goto_out; } } } if (!r) { ret = 1; goto_out; } } while (--_count); out: if (_report) dm_report_free(_report); if (_dtree) dm_tree_free(_dtree); free(_table); if (_initial_timestamp) dm_timestamp_destroy(_initial_timestamp); return (_switches[HELP_ARG] || _switches[VERSION_ARG]) ? 0 : ret; }