| /* |
| * Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved. |
| * Copyright (C) 2004-2015 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 |
| */ |
| #include "tool.h" |
| |
| #include "dm-logging.h" |
| |
| #include <ctype.h> |
| #include <dirent.h> |
| #include <sys/wait.h> |
| #include <sys/param.h> |
| #include <locale.h> |
| #include <langinfo.h> |
| #include <time.h> |
| |
| #include <fcntl.h> |
| #include <sys/stat.h> |
| |
| #ifdef UDEV_SYNC_SUPPORT |
| # include <sys/types.h> |
| # include <sys/ipc.h> |
| # include <sys/sem.h> |
| # include <libudev.h> |
| #endif |
| |
| /* FIXME Unused so far */ |
| #undef HAVE_SYS_STATVFS_H |
| |
| #ifdef HAVE_SYS_STATVFS_H |
| # include <sys/statvfs.h> |
| #endif |
| |
| #ifdef HAVE_SYS_IOCTL_H |
| # include <sys/ioctl.h> |
| #endif |
| |
| #ifdef HAVE_SYS_TIMERFD_H |
| # include <sys/timerfd.h> |
| #endif |
| |
| #ifdef HAVE_TERMIOS_H |
| # include <termios.h> |
| #endif |
| |
| #ifdef HAVE_GETOPTLONG |
| # include <getopt.h> |
| # define GETOPTLONG_FN(a, b, c, d, e) getopt_long((a), (b), (c), (d), (e)) |
| # define OPTIND_INIT 0 |
| #else |
| struct option { |
| }; |
| extern int optind; |
| extern char *optarg; |
| # 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 "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 |
| |
| #define err(msg, x...) fprintf(stderr, msg "\n", ##x) |
| |
| /* 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 { |
| READ_ONLY = 0, |
| ADD_NODE_ON_CREATE_ARG, |
| ADD_NODE_ON_RESUME_ARG, |
| ALIAS_ARG, |
| ALL_DEVICES_ARG, |
| ALL_PROGRAMS_ARG, |
| ALL_REGIONS_ARG, |
| AREA_ARG, |
| AREAS_ARG, |
| AREA_SIZE_ARG, |
| BOUNDS_ARG, |
| CHECKS_ARG, |
| CLEAR_ARG, |
| COLS_ARG, |
| COUNT_ARG, |
| DEFERRED_ARG, |
| SELECT_ARG, |
| EXEC_ARG, |
| FILEMAP_ARG, |
| FORCE_ARG, |
| GID_ARG, |
| GROUP_ARG, |
| GROUP_ID_ARG, |
| HELP_ARG, |
| HISTOGRAM_ARG, |
| INACTIVE_ARG, |
| INTERVAL_ARG, |
| LENGTH_ARG, |
| MANGLENAME_ARG, |
| MAJOR_ARG, |
| REGIONS_ARG, |
| MINOR_ARG, |
| MODE_ARG, |
| NAMEPREFIXES_ARG, |
| NOFLUSH_ARG, |
| NOGROUP_ARG, |
| NOHEADINGS_ARG, |
| NOLOCKFS_ARG, |
| NOOPENCOUNT_ARG, |
| NOSUFFIX_ARG, |
| NOTABLE_ARG, |
| NOTIMESUFFIX_ARG, |
| UDEVCOOKIE_ARG, |
| NOUDEVRULES_ARG, |
| NOUDEVSYNC_ARG, |
| OPTIONS_ARG, |
| PRECISE_ARG, |
| PROGRAM_ID_ARG, |
| RAW_ARG, |
| READAHEAD_ARG, |
| REGION_ARG, |
| REGION_ID_ARG, |
| RELATIVE_ARG, |
| RETRY_ARG, |
| ROWS_ARG, |
| SEPARATOR_ARG, |
| SETUUID_ARG, |
| SHOWKEYS_ARG, |
| SORT_ARG, |
| START_ARG, |
| TABLE_ARG, |
| TARGET_ARG, |
| SEGMENTS_ARG, |
| TREE_ARG, |
| UID_ARG, |
| UNBUFFERED_ARG, |
| UNITS_ARG, |
| UNQUOTED_ARG, |
| USER_DATA_ARG, |
| UUID_ARG, |
| VERBOSE_ARG, |
| VERIFYUDEV_ARG, |
| VERSION_ARG, |
| YES_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 int _switches[NUM_SWITCHES]; |
| static int _int_args[NUM_SWITCHES]; |
| static char *_string_args[NUM_SWITCHES]; |
| static int _num_devices; |
| static char *_uuid; |
| static char *_table; |
| static char *_target; |
| static char *_command_to_exec; /* --exec <command> */ |
| static const char *_command; /* dmsetup <command> */ |
| 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 uint32_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'; |
| const char *_program_id = DM_STATS_PROGRAM_ID; /* program_id used for reports. */ |
| static uint64_t _statstype = 0; /* stats objects to report */ |
| |
| /* string names for stats object types */ |
| const char *_stats_types[] = { |
| "all", |
| "area", |
| "region", |
| "group", |
| NULL |
| }; |
| |
| /* report timekeeping */ |
| static struct dm_timestamp *_cycle_timestamp = NULL; |
| 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 */ |
| static int _timer_fd = -1; /* timerfd file descriptor. */ |
| |
| /* 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 |
| */ |
| |
| struct command; |
| #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; |
| const char *help; |
| int min_args; |
| int max_args; |
| int repeatable_cmd; /* Repeat to process device list? */ |
| 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 %s %n", |
| &start, &size, ttype, &n) < 3) { |
| err("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; |
| |
| 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; |
| |
| /* one-line table on cmdline */ |
| if (_table) |
| return _parse_line(dmt, _table, "", ++line); |
| |
| /* OK for empty stdin */ |
| if (file) { |
| if (!(fp = fopen(file, "r"))) { |
| err("Couldn't open '%s' for reading", file); |
| return 0; |
| } |
| } else |
| fp = stdin; |
| |
| #ifndef HAVE_GETLINE |
| buffer_size = LINE_SIZE; |
| if (!(buffer = dm_malloc(buffer_size))) { |
| err("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 |
| dm_free(buffer); |
| #else |
| free(buffer); |
| #endif |
| if (file && fclose(fp)) |
| fprintf(stderr, "%s: fclose failed: %s", file, strerror(errno)); |
| |
| 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 = dm_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 = dm_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))) { |
| dm_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 = dm_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")) |
| dm_free(split_name->vg_name); |
| |
| dm_free(split_name->subsystem); |
| dm_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) |
| { |
| return 1 + (uint64_t) _int_args[COUNT_ARG] - _count; |
| } |
| |
| #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(); |
| } |
| |
| #else /* !HAVE_SYS_TIMERFD_H */ |
| static int _start_usleep_timer(void) |
| { |
| log_debug("Using usleep for interval timekeeping."); |
| return 1; |
| } |
| |
| static int _do_usleep_wait(void) |
| { |
| static struct dm_timestamp *_last_sleep, *_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 (!_last_sleep && !_now) { |
| if (!(_last_sleep = dm_timestamp_alloc())) |
| return_0; |
| 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, _last_sleep); |
| log_debug("Interval timer delta_t: "FMTi64, delta_t); |
| |
| /* FIXME: usleep timer drift over large counts. */ |
| |
| /* adjust for time spent populating and reporting */ |
| this_interval = 2 * _interval - delta_t; |
| log_debug("Using "FMTu64" as interval.", this_interval); |
| } |
| |
| /* Signal that a new interval has begun. */ |
| _new_interval = 1; |
| dm_timestamp_copy(_last_sleep, _now); |
| |
| if (usleep(this_interval / NSEC_PER_USEC)) { |
| if (errno == EINTR) |
| log_error("Report interval interrupted by signal."); |
| if (errno == EINVAL) |
| log_error("Report interval too short."); |
| return_0; |
| } |
| |
| if (_count == 2) { |
| dm_timestamp_destroy(_last_sleep); |
| dm_timestamp_destroy(_now); |
| } |
| |
| return 1; |
| } |
| |
| static int _start_timer(void) |
| { |
| return _start_usleep_timer(); |
| } |
| |
| static int _do_timer_wait(void) |
| { |
| return _do_usleep_wait(); |
| } |
| |
| #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_fd == TIMER_STOPPED) && !_cycle_timestamp) |
| 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_fd < 0) { |
| /* 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; |
| |
| if (!info->exists) { |
| fprintf(stderr, "Device does not exist.\n"); |
| return 0; |
| } |
| |
| 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 (!dm_report_object(_report, &obj)) |
| 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)) |
| 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)) |
| goto out; |
| } |
| |
| /* group report with no groups? */ |
| if ((walk_flags == DM_STATS_WALK_GROUP) |
| && !dm_stats_get_nr_groups(obj.stats)) |
| 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; |
| |
| if (!info->exists) { |
| fprintf(stderr, "Device does not exist.\n"); |
| return; |
| } |
| |
| 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); |
| |
| printf("\n"); |
| } |
| |
| static int _display_info(struct dm_task *dmt) |
| { |
| struct dm_info info; |
| |
| if (!dm_task_get_info(dmt, &info)) |
| return_0; |
| |
| if (!_switches[COLS_ARG]) |
| _display_info_long(dmt, &info); |
| else |
| /* FIXME return code */ |
| _display_info_cols(dmt, &info); |
| |
| return info.exists ? 1 : 0; |
| } |
| |
| 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) { |
| fprintf(stderr, "No device specified.\n"); |
| 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]) { |
| err("--notable only available when creating new device\n"); |
| return 0; |
| } |
| |
| if (!_switches[UUID_ARG] && !_switches[MAJOR_ARG]) { |
| if (!argc) { |
| err("Please specify device.\n"); |
| return 0; |
| } |
| name = argv[0]; |
| argc--; |
| argv++; |
| } else if (argc > 1) { |
| err("Too many command line arguments.\n"); |
| 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(CMD_ARGS) |
| { |
| int r = 0; |
| struct dm_task *dmt; |
| const char *file = NULL; |
| uint32_t cookie = 0; |
| uint16_t udev_flags = 0; |
| |
| if (argc == 2) |
| file = argv[1]; |
| |
| if (!(dmt = dm_task_create(DM_DEVICE_CREATE))) |
| return_0; |
| |
| if (!dm_task_set_name(dmt, argv[0])) |
| 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[NOTABLE_ARG]) |
| dm_udev_set_sync_support(0); |
| |
| 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 (!dm_task_set_cookie(dmt, &cookie, udev_flags) || |
| !_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; |
| } |
| |
| 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++; |
| } |
| |
| sector = strtoull(argv[0], &endptr, 10); |
| if (*endptr || endptr == argv[0]) { |
| err("invalid sector"); |
| goto out; |
| } |
| if (!dm_task_set_sector(dmt, sector)) |
| goto_out; |
| |
| argc--; |
| argv++; |
| |
| if (argc <= 0) |
| err("No message supplied.\n"); |
| |
| for (i = 0; i < argc; i++) |
| sz += strlen(argv[i]) + 1; |
| |
| if (!(str = dm_zalloc(sz))) { |
| err("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); |
| |
| dm_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; |
| if (!(value = strtoul(str_value, &p, 0)) || |
| *p || |
| (value == ULONG_MAX && errno == ERANGE) || |
| value > 0xFFFFFFFF) { |
| err("Incorrect cookie value"); |
| return 0; |
| } |
| else |
| return (uint32_t) value; |
| } |
| |
| static int _udevflags(CMD_ARGS) |
| { |
| uint32_t cookie; |
| uint16_t flags; |
| int i; |
| static const char *dm_flag_names[] = {"DISABLE_DM_RULES", |
| "DISABLE_SUBSYSTEM_RULES", |
| "DISABLE_DISK_RULES", |
| "DISABLE_OTHER_RULES", |
| "LOW_PRIORITY", |
| "DISABLE_LIBRARY_FALLBACK", |
| "PRIMARY_SOURCE", |
| 0}; |
| |
| 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 && dm_flag_names[i]) |
| 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; |
| |
| /* |
| * 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 synchronisation 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') |
| printf("\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("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) { |
| err("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]; |
| |
| 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; |
| |
| fprintf(stderr, "Unable to remove %d device(s).\n", _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) |
| { |
| int n; |
| 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; |
| |
| n = snprintf(path, sizeof(path), "%s/%s", dm_dir(), name); |
| if (n < 0 || n > (int) sizeof(path) - 1) |
| 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) { |
| err("Too many args to --exec\n"); |
| 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; |
| } |
| |
| 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; |
| struct dm_info info; |
| |
| if (names) |
| name = names->name; |
| else { |
| if (!argc && !_switches[UUID_ARG] && !_switches[MAJOR_ARG]) |
| return _process_all(cmd, NULL, argc, argv, 0, _status); |
| name = argv[0]; |
| } |
| |
| if (!strcmp(cmd->name, "table")) |
| cmdno = DM_DEVICE_TABLE; |
| 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 (!_task_run(dmt)) |
| goto_out; |
| |
| if (!dm_task_get_info(dmt, &info) || !info.exists) |
| 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 (!matched && _switches[VERBOSE_ARG]) |
| _display_info(dmt); |
| if (multiple_devices && !_switches[VERBOSE_ARG]) |
| printf("%s: ", name); |
| if (target_type) { |
| /* Suppress encryption key */ |
| if (!_switches[SHOWKEYS_ARG] && |
| cmdno == DM_DEVICE_TABLE && |
| !strcmp(target_type, "crypt")) { |
| c = params; |
| while (*c && *c != ' ') |
| c++; |
| if (*c) |
| c++; |
| while (*c && *c != ' ') |
| *c++ = '0'; |
| } |
| printf(FMTu64 " " FMTu64 " %s %s", |
| start, length, target_type, params); |
| } |
| printf("\n"); |
| } |
| matched = 1; |
| } while (next); |
| |
| if (multiple_devices && _switches[VERBOSE_ARG] && matched && !ls_only) |
| printf("\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; |
| } |
| |
| 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); |
| } |
| printf("\n"); |
| |
| if (multiple_devices && _switches[VERBOSE_ARG]) |
| printf("\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 struct { |
| const char *empty_2; /* */ |
| const char *branch_2; /* |- */ |
| const char *vert_2; /* | */ |
| const char *last_2; /* `- */ |
| const char *single_3; /* --- */ |
| const char *first_3; /* -+- */ |
| } |
| _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); |
| } |
| } |
| |
| /* |
| * 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(attr++ ? ", " : " ["); |
| _out_string(info->suspended ? "SUSPENDED" : "ACTIVE"); |
| } |
| |
| if (_tree_switches[TR_RW]) { |
| _out_string(attr++ ? ", " : " ["); |
| _out_string(info->read_only ? "RO" : "RW"); |
| } |
| |
| if (_tree_switches[TR_OPENCOUNT]) { |
| _out_string(attr++ ? ", " : " ["); |
| (void) _out_int((unsigned) info->open_count); |
| } |
| |
| if (_tree_switches[TR_UUID]) { |
| _out_string(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 |
| */ |
| 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) |
| { |
| if (!_build_whole_deptree(cmd)) |
| return_0; |
| |
| _display_tree_walk_children(dm_tree_find_node(_dtree, 0, 0), 0); |
| |
| return 1; |
| } |
| |
| /* |
| * 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); |
| dm_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); |
| dm_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); |
| dm_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); |
| dm_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); |
| } |
| |