mirror of
git://sourceware.org/git/lvm2.git
synced 2024-10-05 12:19:48 +03:00
7408 lines
186 KiB
C
7408 lines
186 KiB
C
/*
|
|
* 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 <ctype.h>
|
|
#include <dirent.h>
|
|
#include <fcntl.h>
|
|
#include <langinfo.h>
|
|
#include <locale.h>
|
|
#include <stdlib.h>
|
|
#include <sys/param.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/wait.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.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 {
|
|
};
|
|
# 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 implements.
|
|
*/
|
|
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 <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 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 timestamp 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;
|
|
unsigned 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 cooperate 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 statistics - 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) != |