diff options
Diffstat (limited to 'libdm')
-rw-r--r-- | libdm/Makefile.in | 4 | ||||
-rw-r--r-- | libdm/datastruct/hash.c | 12 | ||||
-rw-r--r-- | libdm/ioctl/libdm-compat.h | 123 | ||||
-rw-r--r-- | libdm/ioctl/libdm-iface.c | 1229 | ||||
-rw-r--r-- | libdm/ioctl/libdm-targets.h | 9 | ||||
-rw-r--r-- | libdm/libdevmapper.h | 463 | ||||
-rw-r--r-- | libdm/libdm-common.c | 1235 | ||||
-rw-r--r-- | libdm/libdm-common.h | 32 | ||||
-rw-r--r-- | libdm/libdm-config.c | 1186 | ||||
-rw-r--r-- | libdm/libdm-deptree.c | 1794 | ||||
-rw-r--r-- | libdm/libdm-file.c | 28 | ||||
-rw-r--r-- | libdm/libdm-report.c | 77 | ||||
-rw-r--r-- | libdm/libdm-string.c | 299 | ||||
-rw-r--r-- | libdm/misc/dm-ioctl.h | 14 | ||||
-rw-r--r-- | libdm/misc/dm-log-userspace.h | 31 | ||||
-rw-r--r-- | libdm/mm/dbg_malloc.c | 24 | ||||
-rw-r--r-- | libdm/mm/dbg_malloc.h | 46 | ||||
-rw-r--r-- | libdm/mm/pool-debug.c | 46 | ||||
-rw-r--r-- | libdm/mm/pool-fast.c | 88 | ||||
-rw-r--r-- | libdm/mm/pool.c | 114 | ||||
-rw-r--r-- | libdm/regex/matcher.c | 183 | ||||
-rw-r--r-- | libdm/regex/ttree.c | 1 |
22 files changed, 5412 insertions, 1626 deletions
diff --git a/libdm/Makefile.in b/libdm/Makefile.in index 70be309..bddb0a0 100644 --- a/libdm/Makefile.in +++ b/libdm/Makefile.in @@ -25,6 +25,7 @@ SOURCES =\ libdm-deptree.c \ libdm-string.c \ libdm-report.c \ + libdm-config.c \ mm/dbg_malloc.c \ mm/pool.c \ regex/matcher.c \ @@ -33,6 +34,9 @@ SOURCES =\ $(interface)/libdm-iface.c INCLUDES = -I$(srcdir)/$(interface) -I$(srcdir) +ifeq ("@VALGRIND_POOL@", "yes") +INCLUDES += @VALGRIND_CFLAGS@ +endif ifeq ("@STATIC_LINK@", "yes") LIB_STATIC = $(interface)/libdevmapper.a diff --git a/libdm/datastruct/hash.c b/libdm/datastruct/hash.c index d4543df..30b4a97 100644 --- a/libdm/datastruct/hash.c +++ b/libdm/datastruct/hash.c @@ -1,6 +1,6 @@ /* * Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved. - * Copyright (C) 2004-2007 Red Hat, Inc. All rights reserved. + * Copyright (C) 2004-2011 Red Hat, Inc. All rights reserved. * * This file is part of the device-mapper userspace tools. * @@ -133,7 +133,7 @@ void dm_hash_destroy(struct dm_hash_table *t) dm_free(t); } -static struct dm_hash_node **_find(struct dm_hash_table *t, const char *key, +static struct dm_hash_node **_find(struct dm_hash_table *t, const void *key, uint32_t len) { unsigned h = _hash(key, len) & (t->num_slots - 1); @@ -150,15 +150,15 @@ static struct dm_hash_node **_find(struct dm_hash_table *t, const char *key, return c; } -void *dm_hash_lookup_binary(struct dm_hash_table *t, const char *key, - uint32_t len) +void *dm_hash_lookup_binary(struct dm_hash_table *t, const void *key, + uint32_t len) { struct dm_hash_node **c = _find(t, key, len); return *c ? (*c)->data : 0; } -int dm_hash_insert_binary(struct dm_hash_table *t, const char *key, +int dm_hash_insert_binary(struct dm_hash_table *t, const void *key, uint32_t len, void *data) { struct dm_hash_node **c = _find(t, key, len); @@ -180,7 +180,7 @@ int dm_hash_insert_binary(struct dm_hash_table *t, const char *key, return 1; } -void dm_hash_remove_binary(struct dm_hash_table *t, const char *key, +void dm_hash_remove_binary(struct dm_hash_table *t, const void *key, uint32_t len) { struct dm_hash_node **c = _find(t, key, len); diff --git a/libdm/ioctl/libdm-compat.h b/libdm/ioctl/libdm-compat.h deleted file mode 100644 index 5ba63ce..0000000 --- a/libdm/ioctl/libdm-compat.h +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved. - * Copyright (C) 2004-2006 Red Hat, Inc. All rights reserved. - * - * This file is part of the device-mapper userspace tools. - * - * 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 Lesser General Public License v.2.1. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#ifndef _LINUX_LIBDM_COMPAT_H -#define _LINUX_LIBDM_COMPAT_H - -#include "kdev_t.h" -#include "dm-ioctl.h" -#include <inttypes.h> -#include <sys/ioctl.h> - -struct dm_task; -struct dm_info; - -/* - * Old versions of structures for backwards compatibility. - */ - -struct dm_ioctl_v1 { - uint32_t version[3]; /* in/out */ - uint32_t data_size; /* total size of data passed in - * including this struct */ - - uint32_t data_start; /* offset to start of data - * relative to start of this struct */ - - int32_t target_count; /* in/out */ - int32_t open_count; /* out */ - uint32_t flags; /* in/out */ - - __kernel_dev_t dev; /* in/out */ - - char name[DM_NAME_LEN]; /* device name */ - char uuid[DM_UUID_LEN]; /* unique identifier for - * the block device */ -}; - -struct dm_target_spec_v1 { - int32_t status; /* used when reading from kernel only */ - uint64_t sector_start; - uint32_t length; - uint32_t next; - - char target_type[DM_MAX_TYPE_NAME]; - -}; - -struct dm_target_deps_v1 { - uint32_t count; - - __kernel_dev_t dev[0]; /* out */ -}; - -enum { - /* Top level cmds */ - DM_VERSION_CMD_V1 = 0, - DM_REMOVE_ALL_CMD_V1, - - /* device level cmds */ - DM_DEV_CREATE_CMD_V1, - DM_DEV_REMOVE_CMD_V1, - DM_DEV_RELOAD_CMD_V1, - DM_DEV_RENAME_CMD_V1, - DM_DEV_SUSPEND_CMD_V1, - DM_DEV_DEPS_CMD_V1, - DM_DEV_STATUS_CMD_V1, - - /* target level cmds */ - DM_TARGET_STATUS_CMD_V1, - DM_TARGET_WAIT_CMD_V1, -}; - -#define DM_VERSION_V1 _IOWR(DM_IOCTL, DM_VERSION_CMD_V1, struct dm_ioctl) -#define DM_REMOVE_ALL_V1 _IOWR(DM_IOCTL, DM_REMOVE_ALL_CMD_V1, struct dm_ioctl) - -#define DM_DEV_CREATE_V1 _IOWR(DM_IOCTL, DM_DEV_CREATE_CMD_V1, struct dm_ioctl) -#define DM_DEV_REMOVE_V1 _IOWR(DM_IOCTL, DM_DEV_REMOVE_CMD_V1, struct dm_ioctl) -#define DM_DEV_RELOAD_V1 _IOWR(DM_IOCTL, DM_DEV_RELOAD_CMD_V1, struct dm_ioctl) -#define DM_DEV_SUSPEND_V1 _IOWR(DM_IOCTL, DM_DEV_SUSPEND_CMD_V1, struct dm_ioctl) -#define DM_DEV_RENAME_V1 _IOWR(DM_IOCTL, DM_DEV_RENAME_CMD_V1, struct dm_ioctl) -#define DM_DEV_DEPS_V1 _IOWR(DM_IOCTL, DM_DEV_DEPS_CMD_V1, struct dm_ioctl) -#define DM_DEV_STATUS_V1 _IOWR(DM_IOCTL, DM_DEV_STATUS_CMD_V1, struct dm_ioctl) - -#define DM_TARGET_STATUS_V1 _IOWR(DM_IOCTL, DM_TARGET_STATUS_CMD_V1, struct dm_ioctl) -#define DM_TARGET_WAIT_V1 _IOWR(DM_IOCTL, DM_TARGET_WAIT_CMD_V1, struct dm_ioctl) - -/* *INDENT-OFF* */ -static struct cmd_data _cmd_data_v1[] = { - { "create", DM_DEV_CREATE_V1, {1, 0, 0} }, - { "reload", DM_DEV_RELOAD_V1, {1, 0, 0} }, - { "remove", DM_DEV_REMOVE_V1, {1, 0, 0} }, - { "remove_all", DM_REMOVE_ALL_V1, {1, 0, 0} }, - { "suspend", DM_DEV_SUSPEND_V1, {1, 0, 0} }, - { "resume", DM_DEV_SUSPEND_V1, {1, 0, 0} }, - { "info", DM_DEV_STATUS_V1, {1, 0, 0} }, - { "deps", DM_DEV_DEPS_V1, {1, 0, 0} }, - { "rename", DM_DEV_RENAME_V1, {1, 0, 0} }, - { "version", DM_VERSION_V1, {1, 0, 0} }, - { "status", DM_TARGET_STATUS_V1, {1, 0, 0} }, - { "table", DM_TARGET_STATUS_V1, {1, 0, 0} }, - { "waitevent", DM_TARGET_WAIT_V1, {1, 0, 0} }, - { "names", 0, {4, 0, 0} }, - { "clear", 0, {4, 0, 0} }, - { "mknodes", 0, {4, 0, 0} }, - { "versions", 0, {4, 1, 0} }, - { "message", 0, {4, 2, 0} }, - { "setgeometry",0, {4, 6, 0} }, -}; -/* *INDENT-ON* */ - -#endif diff --git a/libdm/ioctl/libdm-iface.c b/libdm/ioctl/libdm-iface.c index 4a8dfc6..9e78e1c 100644 --- a/libdm/ioctl/libdm-iface.c +++ b/libdm/ioctl/libdm-iface.c @@ -1,6 +1,6 @@ /* * Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved. - * Copyright (C) 2004-2007 Red Hat, Inc. All rights reserved. + * Copyright (C) 2004-2012 Red Hat, Inc. All rights reserved. * * This file is part of the device-mapper userspace tools. * @@ -17,10 +17,6 @@ #include "libdm-targets.h" #include "libdm-common.h" -#ifdef DM_COMPAT -# include "libdm-compat.h" -#endif - #include <fcntl.h> #include <dirent.h> #include <sys/ioctl.h> @@ -44,8 +40,7 @@ * in the _cmd_data arrays. */ -#if !((DM_VERSION_MAJOR == 1 && DM_VERSION_MINOR >= 0) || \ - (DM_VERSION_MAJOR == 4 && DM_VERSION_MINOR >= 0)) +#if !((DM_VERSION_MAJOR == 4 && DM_VERSION_MINOR >= 6)) #error The version of dm-ioctl.h included is incompatible. #endif @@ -73,9 +68,9 @@ static unsigned _dm_version_minor = 0; static unsigned _dm_version_patchlevel = 0; static int _log_suppress = 0; -static int _kernel_major; -static int _kernel_minor; -static int _kernel_release; +static int _kernel_major = 0; +static int _kernel_minor = 0; +static int _kernel_release = 0; /* * If the kernel dm driver only supports one major number @@ -92,16 +87,7 @@ static int _version_checked = 0; static int _version_ok = 1; static unsigned _ioctl_buffer_double_factor = 0; -/* - * Support both old and new major numbers to ease the transition. - * Clumsy, but only temporary. - */ -#if DM_VERSION_MAJOR == 4 && defined(DM_COMPAT) -const int _dm_compat = 1; -#else const int _dm_compat = 0; -#endif - /* *INDENT-OFF* */ static struct cmd_data _cmd_data_v4[] = { @@ -133,7 +119,6 @@ static struct cmd_data _cmd_data_v4[] = { }; /* *INDENT-ON* */ -#define ALIGNMENT_V1 sizeof(int) #define ALIGNMENT 8 /* FIXME Rejig library to record & use errno instead */ @@ -141,17 +126,18 @@ static struct cmd_data _cmd_data_v4[] = { # define DM_EXISTS_FLAG 0x00000004 #endif -static void *_align(void *ptr, unsigned int a) +static char *_align(char *ptr, unsigned int a) { register unsigned long agn = --a; - return (void *) (((unsigned long) ptr + agn) & ~agn); + return (char *) (((unsigned long) ptr + agn) & ~agn); } static int _uname(void) { static int _uts_set = 0; struct utsname _uts; + int parts; if (_uts_set) return 1; @@ -160,10 +146,14 @@ static int _uname(void) log_error("uname failed: %s", strerror(errno)); return 0; } - if (sscanf(_uts.release, "%d.%d.%d", + + parts = sscanf(_uts.release, "%d.%d.%d", &_kernel_major, &_kernel_minor, - &_kernel_release) != 3) { + &_kernel_release); + + /* Kernels with a major number of 2 always had 3 parts. */ + if (parts < 1 || (_kernel_major < 3 && parts < 3)) { log_error("Could not determine kernel version used."); return 0; } @@ -182,7 +172,8 @@ static int _get_proc_number(const char *file, const char *name, { FILE *fl; char nm[256]; - int c; + char *line = NULL; + size_t len; uint32_t num; if (!(fl = fopen(file, "r"))) { @@ -190,23 +181,23 @@ static int _get_proc_number(const char *file, const char *name, return 0; } - while (!feof(fl)) { - if (fscanf(fl, "%d %255s\n", &num, &nm[0]) == 2) { + while (getline(&line, &len, fl) != -1) { + if (sscanf(line, "%d %255s\n", &num, &nm[0]) == 2) { if (!strcmp(name, nm)) { if (number) { *number = num; if (fclose(fl)) log_sys_error("fclose", file); + free(line); return 1; } dm_bit_set(_dm_bitset, num); } - } else do { - c = fgetc(fl); - } while (c != EOF && c != '\n'); + } } if (fclose(fl)) log_sys_error("fclose", file); + free(line); if (number) { log_error("%s: No entry for %s found", file, name); @@ -228,7 +219,7 @@ static int _control_device_number(uint32_t *major, uint32_t *minor) } /* - * Returns 1 if exists; 0 if it doesn't; -1 if it's wrong + * Returns 1 if it exists on returning; 0 if it doesn't; -1 if it's wrong. */ static int _control_exists(const char *control, uint32_t major, uint32_t minor) { @@ -248,7 +239,7 @@ static int _control_exists(const char *control, uint32_t major, uint32_t minor) return -1; } - if (major && buf.st_rdev != MKDEV(major, minor)) { + if (major && buf.st_rdev != MKDEV((dev_t)major, minor)) { log_verbose("%s: Wrong device number: (%u, %u) instead of " "(%u, %u)", control, MAJOR(buf.st_mode), MINOR(buf.st_mode), @@ -267,8 +258,15 @@ static int _create_control(const char *control, uint32_t major, uint32_t minor) int ret; mode_t old_umask; - if (!major) - return 0; + /* + * Return if the control already exists with intended major/minor + * or there's an error unlinking an apparently incorrect one. + */ + ret = _control_exists(control, major, minor); + if (ret == -1) + return 0; /* Failed to unlink existing incorrect node */ + if (ret) + return 1; /* Already exists and correct */ (void) dm_prepare_selinux_context(dm_dir(), S_IFDIR); old_umask = umask(DM_DEV_DIR_UMASK); @@ -282,12 +280,14 @@ static int _create_control(const char *control, uint32_t major, uint32_t minor) log_verbose("Creating device %s (%u, %u)", control, major, minor); (void) dm_prepare_selinux_context(control, S_IFCHR); + old_umask = umask(DM_CONTROL_NODE_UMASK); if (mknod(control, S_IFCHR | S_IRUSR | S_IWUSR, - MKDEV(major, minor)) < 0) { + MKDEV((dev_t)major, minor)) < 0) { log_sys_error("mknod", control); (void) dm_prepare_selinux_context(NULL, 0); return 0; } + umask(old_umask); (void) dm_prepare_selinux_context(NULL, 0); return 1; @@ -357,14 +357,10 @@ static void _close_control_fd(void) } } -static int _open_and_assign_control_fd(const char *control, - int ignore_nodev) +static int _open_and_assign_control_fd(const char *control) { - _close_control_fd(); - if ((_control_fd = open(control, O_RDWR)) < 0) { - if (!(ignore_nodev && errno == ENODEV)) - log_sys_error("open", control); + log_sys_error("open", control); return 0; } @@ -375,8 +371,8 @@ static int _open_control(void) { #ifdef DM_IOCTLS char control[PATH_MAX]; - uint32_t major = 0, minor; - int dm_mod_autoload_support, needs_open; + uint32_t major = MISC_MAJOR; + uint32_t minor = MAPPER_CTRL_MINOR; if (_control_fd != -1) return 1; @@ -387,61 +383,27 @@ static int _open_control(void) snprintf(control, sizeof(control), "%s/%s", dm_dir(), DM_CONTROL_NODE); /* - * dm-mod autoloading is supported since kernel 2.6.36. - * Udev daemon will try to read modules.devname file extracted - * by depmod and create any static nodes needed. - * The /dev/mapper/control node can be created and prepared this way. - * First access to such node should load dm-mod module automatically. - */ - dm_mod_autoload_support = KERNEL_VERSION(_kernel_major, _kernel_minor, - _kernel_release) >= KERNEL_VERSION(2, 6, 36); - - /* - * If dm-mod autoloading is supported and the control node exists - * already try to open it now. This should autoload dm-mod module. + * Prior to 2.6.36 the minor number should be looked up in /proc. */ - if (dm_mod_autoload_support) { - if (!_get_proc_number(PROC_DEVICES, MISC_NAME, &major)) - /* If major not found, just fallback to hardcoded value. */ - major = MISC_MAJOR; - - /* Recreate the node with correct major and minor if needed. */ - if (!_control_exists(control, major, MAPPER_CTRL_MINOR) && - !_create_control(control, major, MAPPER_CTRL_MINOR)) - goto error; - - _open_and_assign_control_fd(control, 1); - } + if ((KERNEL_VERSION(_kernel_major, _kernel_minor, _kernel_release) < + KERNEL_VERSION(2, 6, 36)) && + !_control_device_number(&major, &minor)) + goto_bad; /* - * Get major and minor number assigned for the control node. - * In case we make use of the module autoload support, this - * information should be accessible now as well. + * Create the node with correct major and minor if not already done. + * Udev may already have created /dev/mapper/control + * from the modules.devname file generated by depmod. */ - if (!_control_device_number(&major, &minor)) - log_error("Is device-mapper driver missing from kernel?"); + if (!_create_control(control, major, minor)) + goto_bad; /* - * Check the control node and its major and minor number. - * If there's anything wrong, remove the old node and create - * a correct one. + * As of 2.6.36 kernels, the open can trigger autoloading dm-mod. */ - if ((needs_open = !_control_exists(control, major, minor)) && - !_create_control(control, major, minor)) { - _close_control_fd(); - goto error; - } - - /* - * For older kernels without dm-mod autoloading support, we always - * need to open the control node here - we still haven't done that! - * For newer kernels with dm-mod autoloading, we open it only if the - * node was recreated and corrected in previous step. - */ - if ((!dm_mod_autoload_support || needs_open) && - !_open_and_assign_control_fd(control, 0)) - goto error; - + if (!_open_and_assign_control_fd(control)) + goto_bad; + if (!_create_dm_bitset()) { log_error("Failed to set up list of device-mapper major numbers"); return 0; @@ -449,8 +411,10 @@ static int _open_control(void) return 1; -error: +bad: log_error("Failure to communicate with kernel device-mapper driver."); + if (!geteuid()) + log_error("Check that device-mapper is available in the kernel."); return 0; #else return 1; @@ -484,427 +448,18 @@ void dm_task_destroy(struct dm_task *dmt) dm_free(t); } - if (dmt->dev_name) - dm_free(dmt->dev_name); - - if (dmt->newname) - dm_free(dmt->newname); - - if (dmt->message) - dm_free(dmt->message); - _dm_zfree_dmi(dmt->dmi.v4); - - if (dmt->uuid) - dm_free(dmt->uuid); - + dm_free(dmt->dev_name); + dm_free(dmt->mangled_dev_name); + dm_free(dmt->newname); + dm_free(dmt->message); + dm_free(dmt->geometry); + dm_free(dmt->uuid); + dm_free(dmt->mangled_uuid); dm_free(dmt); } /* - * Protocol Version 1 compatibility functions. - */ - -#ifdef DM_COMPAT - -static void _dm_zfree_dmi_v1(struct dm_ioctl_v1 *dmi) -{ - if (dmi) { - memset(dmi, 0, dmi->data_size); - dm_free(dmi); - } -} - -static int _dm_task_get_driver_version_v1(struct dm_task *dmt, char *version, - size_t size) -{ - unsigned int *v; - - if (!dmt->dmi.v1) { - version[0] = '\0'; - return 0; - } - - v = dmt->dmi.v1->version; - snprintf(version, size, "%u.%u.%u", v[0], v[1], v[2]); - return 1; -} - -/* Unmarshall the target info returned from a status call */ -static int _unmarshal_status_v1(struct dm_task *dmt, struct dm_ioctl_v1 *dmi) -{ - char *outbuf = (char *) dmi + dmi->data_start; - char *outptr = outbuf; - int32_t i; - struct dm_target_spec_v1 *spec; - - for (i = 0; i < dmi->target_count; i++) { - spec = (struct dm_target_spec_v1 *) outptr; - - if (!dm_task_add_target(dmt, spec->sector_start, - (uint64_t) spec->length, - spec->target_type, - outptr + sizeof(*spec))) { - return 0; - } - - outptr = outbuf + spec->next; - } - - return 1; -} - -static int _dm_format_dev_v1(char *buf, int bufsize, uint32_t dev_major, - uint32_t dev_minor) -{ - int r; - - if (bufsize < 8) - return 0; - - r = snprintf(buf, bufsize, "%03x:%03x", dev_major, dev_minor); - if (r < 0 || r > bufsize - 1) - return 0; - - return 1; -} - -static int _dm_task_get_info_v1(struct dm_task *dmt, struct dm_info *info) -{ - if (!dmt->dmi.v1) - return 0; - - memset(info, 0, sizeof(*info)); - - info->exists = dmt->dmi.v1->flags & DM_EXISTS_FLAG ? 1 : 0; - if (!info->exists) - return 1; - - info->suspended = dmt->dmi.v1->flags & DM_SUSPEND_FLAG ? 1 : 0; - info->read_only = dmt->dmi.v1->flags & DM_READONLY_FLAG ? 1 : 0; - info->target_count = dmt->dmi.v1->target_count; - info->open_count = dmt->dmi.v1->open_count; - info->event_nr = 0; - info->major = MAJOR(dmt->dmi.v1->dev); - info->minor = MINOR(dmt->dmi.v1->dev); - info->live_table = 1; - info->inactive_table = 0; - - return 1; -} - -static const char *_dm_task_get_name_v1(const struct dm_task *dmt) -{ - return (dmt->dmi.v1->name); -} - -static const char *_dm_task_get_uuid_v1(const struct dm_task *dmt) -{ - return (dmt->dmi.v1->uuid); -} - -static struct dm_deps *_dm_task_get_deps_v1(struct dm_task *dmt) -{ - log_error("deps version 1 no longer supported by libdevmapper"); - return NULL; -} - -static struct dm_names *_dm_task_get_names_v1(struct dm_task *dmt) -{ - return (struct dm_names *) (((void *) dmt->dmi.v1) + - dmt->dmi.v1->data_start); -} - -static void *_add_target_v1(struct target *t, void *out, void *end) -{ - void *out_sp = out; - struct dm_target_spec_v1 sp; - size_t sp_size = sizeof(struct dm_target_spec_v1); - int len; - - out += sp_size; - if (out >= end) - return_NULL; - - sp.status = 0; - sp.sector_start = t->start; - sp.length = t->length; - strncpy(sp.target_type, t->type, sizeof(sp.target_type)); - - len = strlen(t->params); - - if ((out + len + 1) >= end) - return_NULL; - - strcpy((char *) out, t->params); - out += len + 1; - - /* align next block */ - out = _align(out, ALIGNMENT_V1); - - sp.next = out - out_sp; - - memcpy(out_sp, &sp, sp_size); - - return out; -} - -static struct dm_ioctl_v1 *_flatten_v1(struct dm_task *dmt) -{ - const size_t min_size = 16 * 1024; - const int (*version)[3]; - - struct dm_ioctl_v1 *dmi; - struct target *t; - size_t len = sizeof(struct dm_ioctl_v1); - void *b, *e; - int count = 0; - - for (t = dmt->head; t; t = t->next) { - len += sizeof(struct dm_target_spec_v1); - len += strlen(t->params) + 1 + ALIGNMENT_V1; - count++; - } - - if (count && dmt->newname) { - log_error("targets and newname are incompatible"); - return NULL; - } - - if (dmt->newname) - len += strlen(dmt->newname) + 1; - - /* - * Give len a minimum size so that we have space to store - * dependencies or status information. - */ - if (len < min_size) - len = min_size; - - if (!(dmi = dm_malloc(len))) - return NULL; - - memset(dmi, 0, len); - - version = &_cmd_data_v1[dmt->type].version; - - dmi->version[0] = (*version)[0]; - dmi->version[1] = (*version)[1]; - dmi->version[2] = (*version)[2]; - - dmi->data_size = len; - dmi->data_start = sizeof(struct dm_ioctl_v1); - - if (dmt->dev_name) - strncpy(dmi->name, dmt->dev_name, sizeof(dmi->name)); - - if (dmt->type == DM_DEVICE_SUSPEND) - dmi->flags |= DM_SUSPEND_FLAG; - if (dmt->read_only) - dmi->flags |= DM_READONLY_FLAG; - - if (dmt->minor >= 0) { - if (dmt->major <= 0) { - log_error("Missing major number for persistent device"); - return NULL; - } - dmi->flags |= DM_PERSISTENT_DEV_FLAG; - dmi->dev = MKDEV(dmt->major, dmt->minor); - } - - if (dmt->uuid) - strncpy(dmi->uuid, dmt->uuid, sizeof(dmi->uuid)); - - dmi->target_count = count; - - b = (void *) (dmi + 1); - e = (void *) ((char *) dmi + len); - - for (t = dmt->head; t; t = t->next) - if (!(b = _add_target_v1(t, b, e))) { - log_error("Ran out of memory building ioctl parameter"); - goto bad; - } - - if (dmt->newname) - strcpy(b, dmt->newname); - - return dmi; - - bad: - _dm_zfree_dmi_v1(dmi); - return NULL; -} - -static int _dm_names_v1(struct dm_ioctl_v1 *dmi) -{ - const char *dev_dir = dm_dir(); - int r = 1, len; - const char *name; - struct dirent *dirent; - DIR *d; - struct dm_names *names, *old_names = NULL; - void *end = (void *) dmi + dmi->data_size; - struct stat buf; - char path[PATH_MAX]; - - log_warn("WARNING: Device list may be incomplete with interface " - "version 1."); - log_warn("Please upgrade your kernel device-mapper driver."); - - if (!(d = opendir(dev_dir))) { - log_sys_error("opendir", dev_dir); - return 0; - } - - names = (struct dm_names *) ((void *) dmi + dmi->data_start); - - names->dev = 0; /* Flags no data */ - - while ((dirent = readdir(d))) { - name = dirent->d_name; - - if (name[0] == '.' || !strcmp(name, "control")) - continue; - - if (old_names) - old_names->next = (uint32_t) ((void *) names - - (void *) old_names); - snprintf(path, sizeof(path), "%s/%s", dev_dir, name); - if (stat(path, &buf)) { - log_sys_error("stat", path); - continue; - } - if (!S_ISBLK(buf.st_mode)) - continue; - names->dev = (uint64_t) buf.st_rdev; - names->next = 0; - len = strlen(name); - if (((void *) (names + 1) + len + 1) >= end) { - log_error("Insufficient buffer space for device list"); - r = 0; - break; - } - - strcpy(names->name, name); - - old_names = names; - names = _align((void *) ++names + len + 1, ALIGNMENT); - } - - if (closedir(d)) - log_sys_error("closedir", dev_dir); - - return r; -} - -static int _dm_task_run_v1(struct dm_task *dmt) -{ - struct dm_ioctl_v1 *dmi; - unsigned int command; - - dmi = _flatten_v1(dmt); - if (!dmi) { - log_error("Couldn't create ioctl argument."); - return 0; - } - - if (!_open_control()) - return 0; - - if ((unsigned) dmt->type >= - (sizeof(_cmd_data_v1) / sizeof(*_cmd_data_v1))) { - log_error(INTERNAL_ERROR "unknown device-mapper task %d", - dmt->type); - goto bad; - } - - command = _cmd_data_v1[dmt->type].cmd; - - if (dmt->type == DM_DEVICE_TABLE) - dmi->flags |= DM_STATUS_TABLE_FLAG; - - if (dmt->new_uuid) { - log_error("Changing UUID is not supported by kernel."); - goto bad; - } - - log_debug("dm %s %s %s%s%s [%u]", _cmd_data_v1[dmt->type].name, - dmi->name, dmi->uuid, dmt->newname ? " " : "", - dmt->newname ? dmt->newname : "", - dmi->data_size); - if (dmt->type == DM_DEVICE_LIST) { - if (!_dm_names_v1(dmi)) - goto bad; - } -#ifdef DM_IOCTLS - else if (ioctl(_control_fd, command, dmi) < 0) { - if (_log_suppress) - log_verbose("device-mapper: %s ioctl failed: %s", - _cmd_data_v1[dmt->type].name, - strerror(errno)); - else - log_error("device-mapper: %s ioctl failed: %s", - _cmd_data_v1[dmt->type].name, - strerror(errno)); - goto bad; - } -#else /* Userspace alternative for testing */ -#endif - - if (dmi->flags & DM_BUFFER_FULL_FLAG) - /* FIXME Increase buffer size and retry operation (if query) */ - log_error("WARNING: libdevmapper buffer too small for data"); - - switch (dmt->type) { - case DM_DEVICE_CREATE: - add_dev_node(dmt->dev_name, MAJOR(dmi->dev), MINOR(dmi->dev), - dmt->uid, dmt->gid, dmt->mode, 0); - break; - - case DM_DEVICE_REMOVE: - rm_dev_node(dmt->dev_name, 0); - break; - - case DM_DEVICE_RENAME: - rename_dev_node(dmt->dev_name, dmt->newname, 0); - break; - - case DM_DEVICE_MKNODES: - if (dmi->flags & DM_EXISTS_FLAG) - add_dev_node(dmt->dev_name, MAJOR(dmi->dev), - MINOR(dmi->dev), dmt->uid, - dmt->gid, dmt->mode, 0); - else - rm_dev_node(dmt->dev_name, 0); - break; - - case DM_DEVICE_STATUS: - case DM_DEVICE_TABLE: - if (!_unmarshal_status_v1(dmt, dmi)) - goto bad; - break; - - case DM_DEVICE_SUSPEND: - case DM_DEVICE_RESUME: - dmt->type = DM_DEVICE_INFO; - if (!dm_task_run(dmt)) - goto bad; - _dm_zfree_dmi_v1(dmi); /* We'll use what info returned */ - return 1; - } - - dmt->dmi.v1 = dmi; - return 1; - - bad: - _dm_zfree_dmi_v1(dmi); - return 0; -} - -#endif - -/* * Protocol Version 4 functions. */ @@ -912,20 +467,22 @@ int dm_task_get_driver_version(struct dm_task *dmt, char *version, size_t size) { unsigned *v; -#ifdef DM_COMPAT - if (_dm_version == 1) - return _dm_task_get_driver_version_v1(dmt, version, size); -#endif - if (!dmt->dmi.v4) { - version[0] = '\0'; + if (version) + version[0] = '\0'; return 0; } v = dmt->dmi.v4->version; - snprintf(version, size, "%u.%u.%u", v[0], v[1], v[2]); _dm_version_minor = v[1]; _dm_version_patchlevel = v[2]; + if (version && + (snprintf(version, size, "%u.%u.%u", v[0], v[1], v[2]) < 0)) { + log_error("Buffer for version is to short."); + if (size > 0) + version[0] = '\0'; + return 0; + } return 1; } @@ -945,7 +502,8 @@ static int _check_version(char *version, size_t size, int log_suppress) _log_suppress = 1; r = dm_task_run(task); - dm_task_get_driver_version(task, version, size); + if (!dm_task_get_driver_version(task, version, size)) + stack; dm_task_destroy(task); _log_suppress = 0; @@ -970,7 +528,7 @@ int dm_check_version(void) return 1; if (!_dm_compat) - goto bad; + goto_bad; log_verbose("device-mapper ioctl protocol version %u failed. " "Trying protocol version 1.", _dm_version); @@ -995,8 +553,25 @@ int dm_check_version(void) int dm_cookie_supported(void) { return (dm_check_version() && - _dm_version >= 4 && - _dm_version_minor >= 15); + _dm_version >= 4 && + _dm_version_minor >= 15); +} + +static int dm_inactive_supported(void) +{ + int inactive_supported = 0; + + if (dm_check_version() && _dm_version >= 4) { + if (_dm_version_minor >= 16) + inactive_supported = 1; /* upstream */ + else if (_dm_version_minor == 11 && + (_dm_version_patchlevel >= 6 && + _dm_version_patchlevel <= 40)) { + inactive_supported = 1; /* RHEL 5.7 */ + } + } + + return inactive_supported; } void *dm_get_next_target(struct dm_task *dmt, void *next, @@ -1008,8 +583,13 @@ void *dm_get_next_target(struct dm_task *dmt, void *next, if (!t) t = dmt->head; - if (!t) + if (!t) { + *start = 0; + *length = 0; + *target_type = 0; + *params = 0; return NULL; + } *start = t->start; *length = t->length; @@ -1047,11 +627,6 @@ int dm_format_dev(char *buf, int bufsize, uint32_t dev_major, { int r; -#ifdef DM_COMPAT - if (_dm_version == 1) - return _dm_format_dev_v1(buf, bufsize, dev_major, dev_minor); -#endif - if (bufsize < 8) return 0; @@ -1064,11 +639,6 @@ int dm_format_dev(char *buf, int bufsize, uint32_t dev_major, int dm_task_get_info(struct dm_task *dmt, struct dm_info *info) { -#ifdef DM_COMPAT - if (_dm_version == 1) - return _dm_task_get_info_v1(dmt, info); -#endif - if (!dmt->dmi.v4) return 0; @@ -1093,70 +663,33 @@ int dm_task_get_info(struct dm_task *dmt, struct dm_info *info) } uint32_t dm_task_get_read_ahead(const struct dm_task *dmt, uint32_t *read_ahead) -{ +{ const char *dev_name; *read_ahead = 0; -#ifdef DM_COMPAT - /* Not supporting this */ - if (_dm_version == 1) - return 1; -#endif - - if (!dmt->dmi.v4 || !(dmt->dmi.v4->flags & DM_EXISTS_FLAG)) + if (!dmt->dmi.v4 || !(dmt->dmi.v4->flags & DM_EXISTS_FLAG)) return 0; if (*dmt->dmi.v4->name) dev_name = dmt->dmi.v4->name; - else if (dmt->dev_name) - dev_name = dmt->dev_name; - else { + else if (!(dev_name = DEV_NAME(dmt))) { log_error("Get read ahead request failed: device name unrecorded."); return 0; } - return get_dev_node_read_ahead(dev_name, read_ahead); -} - -const char *dm_task_get_name(const struct dm_task *dmt) -{ -#ifdef DM_COMPAT - if (_dm_version == 1) - return _dm_task_get_name_v1(dmt); -#endif - - return (dmt->dmi.v4->name); -} - -const char *dm_task_get_uuid(const struct dm_task *dmt) -{ -#ifdef DM_COMPAT - if (_dm_version == 1) - return _dm_task_get_uuid_v1(dmt); -#endif - - return (dmt->dmi.v4->uuid); + return get_dev_node_read_ahead(dev_name, MAJOR(dmt->dmi.v4->dev), + MINOR(dmt->dmi.v4->dev), read_ahead); } struct dm_deps *dm_task_get_deps(struct dm_task *dmt) { -#ifdef DM_COMPAT - if (_dm_version == 1) - return _dm_task_get_deps_v1(dmt); -#endif - return (struct dm_deps *) (((char *) dmt->dmi.v4) + dmt->dmi.v4->data_start); } struct dm_names *dm_task_get_names(struct dm_task *dmt) { -#ifdef DM_COMPAT - if (_dm_version == 1) - return _dm_task_get_names_v1(dmt); -#endif - return (struct dm_names *) (((char *) dmt->dmi.v4) + dmt->dmi.v4->data_start); } @@ -1188,39 +721,52 @@ int dm_task_suppress_identical_reload(struct dm_task *dmt) return 1; } -int dm_task_set_newuuid(struct dm_task *dmt, const char *newuuid) +int dm_task_set_add_node(struct dm_task *dmt, dm_add_node_t add_node) { - if (strlen(newuuid) >= DM_UUID_LEN) { - log_error("Uuid \"%s\" too long", newuuid); + switch (add_node) { + case DM_ADD_NODE_ON_RESUME: + case DM_ADD_NODE_ON_CREATE: + dmt->add_node = add_node; + return 1; + default: + log_error("Unknown add node parameter"); return 0; } +} - if (!(dmt->newname = dm_strdup(newuuid))) { - log_error("dm_task_set_newuuid: strdup(%s) failed", newuuid); +int dm_task_set_newuuid(struct dm_task *dmt, const char *newuuid) +{ + dm_string_mangling_t mangling_mode = dm_get_name_mangling_mode(); + char mangled_uuid[DM_UUID_LEN]; + int r = 0; + + if (strlen(newuuid) >= DM_UUID_LEN) { + log_error("Uuid \"%s\" too long", newuuid); return 0; } - dmt->new_uuid = 1; - return 1; -} + if (!check_multiple_mangled_string_allowed(newuuid, "new UUID", mangling_mode)) + return_0; -int dm_task_set_newname(struct dm_task *dmt, const char *newname) -{ - if (strchr(newname, '/')) { - log_error("Name \"%s\" invalid. It contains \"/\".", newname); + if (mangling_mode != DM_STRING_MANGLING_NONE && + (r = mangle_string(newuuid, "new UUID", strlen(newuuid), mangled_uuid, + sizeof(mangled_uuid), mangling_mode)) < 0) { + log_error("Failed to mangle new device UUID \"%s\"", newuuid); return 0; } - if (strlen(newname) >= DM_NAME_LEN) { - log_error("Name \"%s\" too long", newname); - return 0; + if (r) { + log_debug("New device uuid mangled [%s]: %s --> %s", + mangling_mode == DM_STRING_MANGLING_AUTO ? "auto" : "hex", + newuuid, mangled_uuid); + newuuid = mangled_uuid; } - if (!(dmt->newname = dm_strdup(newname))) { - log_error("dm_task_set_newname: strdup(%s) failed", newname); + if (!(dmt->newname = dm_strdup(newuuid))) { + log_error("dm_task_set_newuuid: strdup(%s) failed", newuuid); return 0; } - dmt->new_uuid = 0; + dmt->new_uuid = 1; return 1; } @@ -1242,16 +788,11 @@ int dm_task_set_sector(struct dm_task *dmt, uint64_t sector) return 1; } -int dm_task_set_geometry(struct dm_task *dmt, const char *cylinders, const char *heads, const char *sectors, const char *start) +int dm_task_set_geometry(struct dm_task *dmt, const char *cylinders, const char *heads, + const char *sectors, const char *start) { - size_t len = strlen(cylinders) + 1 + strlen(heads) + 1 + strlen(sectors) + 1 + strlen(start) + 1; - - if (!(dmt->geometry = dm_malloc(len))) { - log_error("dm_task_set_geometry: dm_malloc failed"); - return 0; - } - - if (sprintf(dmt->geometry, "%s %s %s %s", cylinders, heads, sectors, start) < 0) { + if (dm_asprintf(&(dmt->geometry), "%s %s %s %s", + cylinders, heads, sectors, start) < 0) { log_error("dm_task_set_geometry: sprintf failed"); return 0; } @@ -1280,6 +821,20 @@ int dm_task_skip_lockfs(struct dm_task *dmt) return 1; } +int dm_task_secure_data(struct dm_task *dmt) +{ + dmt->secure_data = 1; + + return 1; +} + +int dm_task_retry_remove(struct dm_task *dmt) +{ + dmt->retry_remove = 1; + + return 1; +} + int dm_task_query_inactive_table(struct dm_task *dmt) { dmt->query_inactive_table = 1; @@ -1297,16 +852,19 @@ int dm_task_set_event_nr(struct dm_task *dmt, uint32_t event_nr) struct target *create_target(uint64_t start, uint64_t len, const char *type, const char *params) { - struct target *t = dm_malloc(sizeof(*t)); + struct target *t; - if (!t) { + if (strlen(type) >= DM_MAX_TYPE_NAME) { + log_error("Target type name %s is too long.", type); + return NULL; + } + + if (!(t = dm_zalloc(sizeof(*t)))) { log_error("create_target: malloc(%" PRIsize_t ") failed", sizeof(*t)); return NULL; } - memset(t, 0, sizeof(*t)); - if (!(t->params = dm_strdup(params))) { log_error("create_target: strdup(params) failed"); goto bad; @@ -1328,29 +886,53 @@ struct target *create_target(uint64_t start, uint64_t len, const char *type, return NULL; } -static void *_add_target(struct target *t, void *out, void *end) +static char *_add_target(struct target *t, char *out, char *end) { - void *out_sp = out; + char *out_sp = out; struct dm_target_spec sp; size_t sp_size = sizeof(struct dm_target_spec); + unsigned int backslash_count = 0; int len; + char *pt; - out += sp_size; - if (out >= end) - return_NULL; + if (strlen(t->type) >= sizeof(sp.target_type)) { + log_error("Target type name %s is too long.", t->type); + return NULL; + } sp.status = 0; sp.sector_start = t->start; sp.length = t->length; - strncpy(sp.target_type, t->type, sizeof(sp.target_type)); + strncpy(sp.target_type, t->type, sizeof(sp.target_type) - 1); + sp.target_type[sizeof(sp.target_type) - 1] = '\0'; - len = strlen(t->params); + out += sp_size; + pt = t->params; - if ((out + len + 1) >= end) - return_NULL; + while (*pt) + if (*pt++ == '\\') + backslash_count++; + len = strlen(t->params) + backslash_count; - strcpy((char *) out, t->params); - out += len + 1; + if ((out >= end) || (out + len + 1) >= end) { + log_error("Ran out of memory building ioctl parameter"); + return NULL; + } + + if (backslash_count) { + /* replace "\" with "\\" */ + pt = t->params; + do { + if (*pt == '\\') + *out++ = '\\'; + *out++ = *pt++; + } while (*pt); + *out++ = '\0'; + } + else { + strcpy(out, t->params); + out += len + 1; + } /* align next block */ out = _align(out, ALIGNMENT); @@ -1381,7 +963,7 @@ static int _lookup_dev_name(uint64_t dev, char *buf, size_t len) goto out; do { - names = (void *) names + next; + names = (struct dm_names *)((char *) names + next); if (names->dev == dev) { strncpy(buf, names->name, len); r = 1; @@ -1404,7 +986,7 @@ static struct dm_ioctl *_flatten(struct dm_task *dmt, unsigned repeat_count) struct target *t; struct dm_target_msg *tmsg; size_t len = sizeof(struct dm_ioctl); - void *b, *e; + char *b, *e; int count = 0; for (t = dmt->head; t; t = t->next) { @@ -1497,11 +1079,11 @@ static struct dm_ioctl *_flatten(struct dm_task *dmt, unsigned repeat_count) } dmi->flags |= DM_PERSISTENT_DEV_FLAG; - dmi->dev = MKDEV(dmt->major, dmt->minor); + dmi->dev = MKDEV((dev_t)dmt->major, dmt->minor); } /* Does driver support device number referencing? */ - if (_dm_version_minor < 3 && !dmt->dev_name && !dmt->uuid && dmi->dev) { + if (_dm_version_minor < 3 && !DEV_NAME(dmt) && !DEV_UUID(dmt) && dmi->dev) { if (!_lookup_dev_name(dmi->dev, dmi->name, sizeof(dmi->name))) { log_error("Unable to find name for device (%" PRIu32 ":%" PRIu32 ")", dmt->major, dmt->minor); @@ -1513,12 +1095,12 @@ static struct dm_ioctl *_flatten(struct dm_task *dmt, unsigned repeat_count) } /* FIXME Until resume ioctl supplies name, use dev_name for readahead */ - if (dmt->dev_name && (dmt->type != DM_DEVICE_RESUME || dmt->minor < 0 || + if (DEV_NAME(dmt) && (dmt->type != DM_DEVICE_RESUME || dmt->minor < 0 || dmt->major < 0)) - strncpy(dmi->name, dmt->dev_name, sizeof(dmi->name)); + strncpy(dmi->name, DEV_NAME(dmt), sizeof(dmi->name)); - if (dmt->uuid) - strncpy(dmi->uuid, dmt->uuid, sizeof(dmi->uuid)); + if (DEV_UUID(dmt)) + strncpy(dmi->uuid, DEV_UUID(dmt), sizeof(dmi->uuid)); if (dmt->type == DM_DEVICE_SUSPEND) dmi->flags |= DM_SUSPEND_FLAG; @@ -1528,8 +1110,14 @@ static struct dm_ioctl *_flatten(struct dm_task *dmt, unsigned repeat_count) dmi->flags |= DM_READONLY_FLAG; if (dmt->skip_lockfs) dmi->flags |= DM_SKIP_LOCKFS_FLAG; + if (dmt->secure_data) { + if (_dm_version_minor < 20) + log_verbose("Secure data flag unsupported by kernel. " + "Buffers will not be wiped after use."); + dmi->flags |= DM_SECURE_DATA_FLAG; + } if (dmt->query_inactive_table) { - if (_dm_version_minor < 16) + if (!dm_inactive_supported()) log_warn("WARNING: Inactive table query unsupported " "by kernel. It will use live table."); dmi->flags |= DM_QUERY_INACTIVE_TABLE_FLAG; @@ -1546,14 +1134,12 @@ static struct dm_ioctl *_flatten(struct dm_task *dmt, unsigned repeat_count) dmi->target_count = count; dmi->event_nr = dmt->event_nr; - b = (void *) (dmi + 1); - e = (void *) ((char *) dmi + len); + b = (char *) (dmi + 1); + e = (char *) dmi + len; for (t = dmt->head; t; t = t->next) - if (!(b = _add_target(t, b, e))) { - log_error("Ran out of memory building ioctl parameter"); - goto bad; - } + if (!(b = _add_target(t, b, e))) + goto_bad; if (dmt->newname) strcpy(b, dmt->newname); @@ -1633,7 +1219,7 @@ static int _process_all_v4(struct dm_task *dmt) goto out; do { - names = (void *) names + next; + names = (struct dm_names *)((char *) names + next); if (!dm_task_set_name(dmt, names->name)) { r = 0; goto out; @@ -1691,23 +1277,16 @@ static int _create_and_load_v4(struct dm_task *dmt) /* Use new task struct to create the device */ if (!(task = dm_task_create(DM_DEVICE_CREATE))) { - log_error("Failed to create device-mapper task struct"); _udev_complete(dmt); - return 0; + return_0; } /* Copy across relevant fields */ - if (dmt->dev_name && !dm_task_set_name(task, dmt->dev_name)) { - dm_task_destroy(task); - _udev_complete(dmt); - return 0; - } + if (dmt->dev_name && !dm_task_set_name(task, dmt->dev_name)) + goto_bad; - if (dmt->uuid && !dm_task_set_uuid(task, dmt->uuid)) { - dm_task_destroy(task); - _udev_complete(dmt); - return 0; - } + if (dmt->uuid && !dm_task_set_uuid(task, dmt->uuid)) + goto_bad; task->major = dmt->major; task->minor = dmt->minor; @@ -1717,40 +1296,41 @@ static int _create_and_load_v4(struct dm_task *dmt) /* FIXME: Just for udev_check in dm_task_run. Can we avoid this? */ task->event_nr = dmt->event_nr & DM_UDEV_FLAGS_MASK; task->cookie_set = dmt->cookie_set; + task->add_node = dmt->add_node; + + if (!dm_task_run(task)) + goto_bad; - r = dm_task_run(task); dm_task_destroy(task); - if (!r) { - _udev_complete(dmt); - return 0; - } /* Next load the table */ if (!(task = dm_task_create(DM_DEVICE_RELOAD))) { - log_error("Failed to create device-mapper task struct"); + stack; _udev_complete(dmt); - r = 0; goto revert; } /* Copy across relevant fields */ if (dmt->dev_name && !dm_task_set_name(task, dmt->dev_name)) { + stack; dm_task_destroy(task); _udev_complete(dmt); - r = 0; goto revert; } task->read_only = dmt->read_only; task->head = dmt->head; task->tail = dmt->tail; + task->secure_data = dmt->secure_data; r = dm_task_run(task); task->head = NULL; task->tail = NULL; dm_task_destroy(task); + if (!r) { + stack; _udev_complete(dmt); goto revert; } @@ -1759,16 +1339,18 @@ static int _create_and_load_v4(struct dm_task *dmt) dmt->type = DM_DEVICE_RESUME; dm_free(dmt->uuid); dmt->uuid = NULL; + dm_free(dmt->mangled_uuid); + dmt->mangled_uuid = NULL; - r = dm_task_run(dmt); - - if (r) - return r; + if (dm_task_run(dmt)) + return 1; revert: - dmt->type = DM_DEVICE_REMOVE; + dmt->type = DM_DEVICE_REMOVE; dm_free(dmt->uuid); dmt->uuid = NULL; + dm_free(dmt->mangled_uuid); + dmt->mangled_uuid = NULL; /* * Also udev-synchronize "remove" dm task that is a part of this revert! @@ -1780,13 +1362,19 @@ static int _create_and_load_v4(struct dm_task *dmt) if (!dm_task_set_cookie(dmt, &cookie, (dmt->event_nr & DM_UDEV_FLAGS_MASK) >> DM_UDEV_FLAGS_SHIFT)) - stack; /* keep going */ + stack; /* keep going */ } if (!dm_task_run(dmt)) log_error("Failed to revert device creation."); - return r; + return 0; + + bad: + dm_task_destroy(task); + _udev_complete(dmt); + + return 0; } uint64_t dm_task_get_existing_table_size(struct dm_task *dmt) @@ -1798,6 +1386,7 @@ static int _reload_with_suppression_v4(struct dm_task *dmt) { struct dm_task *task; struct target *t1, *t2; + size_t len; int r; /* New task to get existing table information */ @@ -1833,15 +1422,16 @@ static int _reload_with_suppression_v4(struct dm_task *dmt) t2 = t2->next; dmt->existing_table_size = t2 ? t2->start + t2->length : 0; - if ((task->dmi.v4->flags & DM_READONLY_FLAG) ? 1 : 0 != dmt->read_only) + if (((task->dmi.v4->flags & DM_READONLY_FLAG) ? 1 : 0) != dmt->read_only) goto no_match; t1 = dmt->head; t2 = task->head; while (t1 && t2) { - while (t2->params[strlen(t2->params) - 1] == ' ') - t2->params[strlen(t2->params) - 1] = '\0'; + len = strlen(t2->params); + while (len-- > 0 && t2->params[len] == ' ') + t2->params[len] = '\0'; if ((t1->start != t2->start) || (t1->length != t2->length) || (strcmp(t1->type, t2->type)) || @@ -1868,6 +1458,107 @@ no_match: return r; } +static int _check_children_not_suspended_v4(struct dm_task *dmt, uint64_t device) +{ + struct dm_task *task; + struct dm_info info; + struct dm_deps *deps; + int r = 0; + uint32_t i; + + /* Find dependencies */ + if (!(task = dm_task_create(DM_DEVICE_DEPS))) + return 0; + + /* Copy across or set relevant fields */ + if (device) { + task->major = MAJOR(device); + task->minor = MINOR(device); + } else { + if (dmt->dev_name && !dm_task_set_name(task, dmt->dev_name)) + goto out; + + if (dmt->uuid && !dm_task_set_uuid(task, dmt->uuid)) + goto out; + + task->major = dmt->major; + task->minor = dmt->minor; + } + + task->uid = dmt->uid; + task->gid = dmt->gid; + task->mode = dmt->mode; + /* FIXME: Just for udev_check in dm_task_run. Can we avoid this? */ + task->event_nr = dmt->event_nr & DM_UDEV_FLAGS_MASK; + task->cookie_set = dmt->cookie_set; + task->add_node = dmt->add_node; + + if (!(r = dm_task_run(task))) + goto out; + + if (!dm_task_get_info(task, &info) || !info.exists) + goto out; + + /* + * Warn if any of the devices this device depends upon are already + * suspended: I/O could become trapped between the two devices. + */ + if (info.suspended) { + if (!device) + log_debug("Attempting to suspend a device that is already suspended " + "(%u:%u)", info.major, info.minor); + else + log_error(INTERNAL_ERROR "Attempt to suspend device %s%s%s%.0d%s%.0d%s%s" + "that uses already-suspended device (%u:%u)", + DEV_NAME(dmt) ? : "", DEV_UUID(dmt) ? : "", + dmt->major > 0 ? "(" : "", + dmt->major > 0 ? dmt->major : 0, + dmt->major > 0 ? ":" : "", + dmt->minor > 0 ? dmt->minor : 0, + dmt->major > 0 && dmt->minor == 0 ? "0" : "", + dmt->major > 0 ? ") " : "", + info.major, info.minor); + + /* No need for further recursion */ + r = 1; + goto out; + } + + if (!(deps = dm_task_get_deps(task))) + goto out; + + for (i = 0; i < deps->count; i++) { + /* Only recurse with dm devices */ + if (MAJOR(deps->device[i]) != _dm_device_major) + continue; + + if (!_check_children_not_suspended_v4(task, deps->device[i])) + goto out; + } + + r = 1; + +out: + dm_task_destroy(task); + + return r; +} + +static int _suspend_with_validation_v4(struct dm_task *dmt) +{ + /* Avoid recursion */ + dmt->enable_checks = 0; + + /* + * Ensure we can't leave any I/O trapped between suspended devices. + */ + if (!_check_children_not_suspended_v4(dmt, 0)) + return 0; + + /* Finally, perform the original suspend. */ + return dm_task_run(dmt); +} + static const char *_sanitise_message(char *message) { const char *sanitised_message = message ?: ""; @@ -1880,13 +1571,76 @@ static const char *_sanitise_message(char *message) return sanitised_message; } +static int _do_dm_ioctl_unmangle_string(char *str, const char *str_name, + char *buf, size_t buf_size, + dm_string_mangling_t mode) +{ + int r; + + if (mode == DM_STRING_MANGLING_NONE) + return 1; + + if (!check_multiple_mangled_string_allowed(str, str_name, mode)) + return_0; + + if ((r = unmangle_string(str, str_name, strlen(str), buf, buf_size, mode)) < 0) { + log_debug("_do_dm_ioctl_unmangle_string: failed to " + "unmangle %s \"%s\"", str_name, str); + return 0; + } else if (r) + memcpy(str, buf, strlen(buf) + 1); + + return 1; +} + +static int _dm_ioctl_unmangle_names(int type, struct dm_ioctl *dmi) +{ + char buf[DM_NAME_LEN]; + struct dm_names *names; + unsigned next = 0; + char *name; + int r = 1; + + if ((name = dmi->name)) + r = _do_dm_ioctl_unmangle_string(name, "name", buf, sizeof(buf), + dm_get_name_mangling_mode()); + + if (type == DM_DEVICE_LIST && + ((names = ((struct dm_names *) ((char *)dmi + dmi->data_start)))) && + names->dev) { + do { + names = (struct dm_names *)((char *) names + next); + r = _do_dm_ioctl_unmangle_string(names->name, "name", + buf, sizeof(buf), + dm_get_name_mangling_mode()); + next = names->next; + } while (next); + } + + return r; +} + +static int _dm_ioctl_unmangle_uuids(int type, struct dm_ioctl *dmi) +{ + char buf[DM_UUID_LEN]; + char *uuid = dmi->uuid; + + if (uuid) + return _do_dm_ioctl_unmangle_string(uuid, "UUID", buf, sizeof(buf), + dm_get_name_mangling_mode()); + + return 1; +} + static struct dm_ioctl *_do_dm_ioctl(struct dm_task *dmt, unsigned command, - unsigned repeat_count) + unsigned buffer_repeat_count, + unsigned retry_repeat_count, + int *retryable) { struct dm_ioctl *dmi; int ioctl_with_uevent; - dmi = _flatten(dmt, repeat_count); + dmi = _flatten(dmt, buffer_repeat_count); if (!dmi) { log_error("Couldn't create ioctl argument."); return NULL; @@ -1947,7 +1701,7 @@ static struct dm_ioctl *_do_dm_ioctl(struct dm_task *dmt, unsigned command, } log_debug("dm %s %s%s %s%s%s %s%.0d%s%.0d%s" - "%s%c%c%s%s %.0" PRIu64 " %s [%u]", + "%s%c%c%s%s%s%s%s%s %.0" PRIu64 " %s [%u] (*%u)", _cmd_data_v4[dmt->type].name, dmt->new_uuid ? "UUID " : "", dmi->name, dmi->uuid, dmt->newname ? " " : "", @@ -1960,12 +1714,17 @@ static struct dm_ioctl *_do_dm_ioctl(struct dm_task *dmt, unsigned command, dmt->major > 0 ? ") " : "", dmt->no_open_count ? 'N' : 'O', dmt->no_flush ? 'N' : 'F', + dmt->read_only ? "R" : "", dmt->skip_lockfs ? "S " : "", + dmt->retry_remove ? "T " : "", + dmt->secure_data ? "W " : "", dmt->query_inactive_table ? "I " : "", + dmt->enable_checks ? "C" : "", dmt->sector, _sanitise_message(dmt->message), - dmi->data_size); + dmi->data_size, retry_repeat_count); #ifdef DM_IOCTLS - if (ioctl(_control_fd, command, dmi) < 0) { + if (ioctl(_control_fd, command, dmi) < 0 && + dmt->expected_errno != errno) { if (errno == ENXIO && ((dmt->type == DM_DEVICE_INFO) || (dmt->type == DM_DEVICE_MKNODES) || (dmt->type == DM_DEVICE_STATUS))) @@ -1977,21 +1736,43 @@ static struct dm_ioctl *_do_dm_ioctl(struct dm_task *dmt, unsigned command, _cmd_data_v4[dmt->type].name, strerror(errno)); else - log_error("device-mapper: %s ioctl " + log_error("device-mapper: %s ioctl on %s " "failed: %s", - _cmd_data_v4[dmt->type].name, - strerror(errno)); - _dm_zfree_dmi(dmi); - return NULL; + _cmd_data_v4[dmt->type].name, + dmi->name, strerror(errno)); + + /* + * It's sometimes worth retrying after EBUSY in case + * it's a transient failure caused by an asynchronous + * process quickly scanning the device. + */ + *retryable = errno == EBUSY; + + goto error; } } - if (ioctl_with_uevent && !_check_uevent_generated(dmi)) + if (ioctl_with_uevent && dm_udev_get_sync_support() && + !_check_uevent_generated(dmi)) { + log_debug("Uevent not generated! Calling udev_complete " + "internally to avoid process lock-up."); _udev_complete(dmt); + } + + if (!_dm_ioctl_unmangle_names(dmt->type, dmi)) + goto error; + + if (dmt->type != DM_DEVICE_REMOVE && + !_dm_ioctl_unmangle_uuids(dmt->type, dmi)) + goto error; #else /* Userspace alternative for testing */ #endif return dmi; + +error: + _dm_zfree_dmi(dmi); + return NULL; } void dm_task_update_nodes(void) @@ -1999,17 +1780,20 @@ void dm_task_update_nodes(void) update_devs(); } +#define DM_IOCTL_RETRIES 25 +#define DM_RETRY_USLEEP_DELAY 200000 + int dm_task_run(struct dm_task *dmt) { struct dm_ioctl *dmi; unsigned command; int check_udev; - int udev_only; - -#ifdef DM_COMPAT - if (_dm_version == 1) - return _dm_task_run_v1(dmt); -#endif + int rely_on_udev; + int suspended_counter; + unsigned ioctl_retry = 1; + int retryable = 0; + const char *dev_name = DEV_NAME(dmt); + const char *dev_uuid = DEV_UUID(dmt); if ((unsigned) dmt->type >= (sizeof(_cmd_data_v4) / sizeof(*_cmd_data_v4))) { @@ -2024,21 +1808,53 @@ int dm_task_run(struct dm_task *dmt) if (dmt->type == DM_DEVICE_CREATE && dmt->head) return _create_and_load_v4(dmt); - if (dmt->type == DM_DEVICE_MKNODES && !dmt->dev_name && - !dmt->uuid && dmt->major <= 0) + if (dmt->type == DM_DEVICE_MKNODES && !dev_name && + !dev_uuid && dmt->major <= 0) return _mknodes_v4(dmt); if ((dmt->type == DM_DEVICE_RELOAD) && dmt->suppress_identical_reload) return _reload_with_suppression_v4(dmt); + if ((dmt->type == DM_DEVICE_SUSPEND) && dmt->enable_checks) + return _suspend_with_validation_v4(dmt); + if (!_open_control()) { _udev_complete(dmt); - return 0; - } + return_0; + } + + if ((suspended_counter = dm_get_suspended_counter()) && + dmt->type == DM_DEVICE_RELOAD) + log_error(INTERNAL_ERROR "Performing unsafe table load while %d device(s) " + "are known to be suspended: " + "%s%s%s %s%.0d%s%.0d%s%s", + suspended_counter, + dev_name ? : "", + dev_uuid ? " UUID " : "", + dev_uuid ? : "", + dmt->major > 0 ? "(" : "", + dmt->major > 0 ? dmt->major : 0, + dmt->major > 0 ? ":" : "", + dmt->minor > 0 ? dmt->minor : 0, + dmt->major > 0 && dmt->minor == 0 ? "0" : "", + dmt->major > 0 ? ") " : ""); /* FIXME Detect and warn if cookie set but should not be. */ repeat_ioctl: - if (!(dmi = _do_dm_ioctl(dmt, command, _ioctl_buffer_double_factor))) { + if (!(dmi = _do_dm_ioctl(dmt, command, _ioctl_buffer_double_factor, + ioctl_retry, &retryable))) { + /* + * Async udev rules that scan devices commonly cause transient + * failures. Normally you'd expect the user to have made sure + * nothing was using the device before issuing REMOVE, so it's + * worth retrying in case the failure is indeed transient. + */ + if (retryable && dmt->type == DM_DEVICE_REMOVE && + dmt->retry_remove && ++ioctl_retry <= DM_IOCTL_RETRIES) { + usleep(DM_RETRY_USLEEP_DELAY); + goto repeat_ioctl; + } + _udev_complete(dmt); return 0; } @@ -2059,46 +1875,56 @@ repeat_ioctl: } } + /* + * Are we expecting a udev operation to occur that we need to check for? + */ check_udev = dmt->cookie_set && !(dmt->event_nr >> DM_UDEV_FLAGS_SHIFT & DM_UDEV_DISABLE_DM_RULES_FLAG); - udev_only = dmt->cookie_set ? (dmt->event_nr >> DM_UDEV_FLAGS_SHIFT & - DM_UDEV_DISABLE_LIBRARY_FALLBACK) : 0; + rely_on_udev = dmt->cookie_set ? (dmt->event_nr >> DM_UDEV_FLAGS_SHIFT & + DM_UDEV_DISABLE_LIBRARY_FALLBACK) : 0; switch (dmt->type) { case DM_DEVICE_CREATE: - if (dmt->dev_name && *dmt->dev_name && !udev_only) - add_dev_node(dmt->dev_name, MAJOR(dmi->dev), + if ((dmt->add_node == DM_ADD_NODE_ON_CREATE) && + dev_name && *dev_name && !rely_on_udev) + add_dev_node(dev_name, MAJOR(dmi->dev), MINOR(dmi->dev), dmt->uid, dmt->gid, - dmt->mode, check_udev); + dmt->mode, check_udev, rely_on_udev); break; case DM_DEVICE_REMOVE: /* FIXME Kernel needs to fill in dmi->name */ - if (dmt->dev_name && !udev_only) - rm_dev_node(dmt->dev_name, check_udev); + if (dev_name && !rely_on_udev) + rm_dev_node(dev_name, check_udev, rely_on_udev); break; case DM_DEVICE_RENAME: /* FIXME Kernel needs to fill in dmi->name */ - if (!dmt->new_uuid && dmt->dev_name && !udev_only) - rename_dev_node(dmt->dev_name, dmt->newname, - check_udev); + if (!dmt->new_uuid && dev_name) + rename_dev_node(dev_name, dmt->newname, + check_udev, rely_on_udev); break; case DM_DEVICE_RESUME: + if ((dmt->add_node == DM_ADD_NODE_ON_RESUME) && + dev_name && *dev_name) + add_dev_node(dev_name, MAJOR(dmi->dev), + MINOR(dmi->dev), dmt->uid, dmt->gid, + dmt->mode, check_udev, rely_on_udev); /* FIXME Kernel needs to fill in dmi->name */ - set_dev_node_read_ahead(dmt->dev_name, dmt->read_ahead, - dmt->read_ahead_flags); + set_dev_node_read_ahead(dev_name, + MAJOR(dmi->dev), MINOR(dmi->dev), + dmt->read_ahead, dmt->read_ahead_flags); break; case DM_DEVICE_MKNODES: if (dmi->flags & DM_EXISTS_FLAG) add_dev_node(dmi->name, MAJOR(dmi->dev), MINOR(dmi->dev), dmt->uid, - dmt->gid, dmt->mode, 0); - else if (dmt->dev_name) - rm_dev_node(dmt->dev_name, 0); + dmt->gid, dmt->mode, 0, rely_on_udev); + else if (dev_name) + rm_dev_node(dev_name, 0, rely_on_udev); break; case DM_DEVICE_STATUS: @@ -2129,6 +1955,15 @@ void dm_pools_check_leaks(void); void dm_lib_exit(void) { + int suspended_counter; + static unsigned _exited = 0; + + if (_exited++) + return; + + if ((suspended_counter = dm_get_suspended_counter())) + log_error("libdevmapper exiting with %d device(s) still suspended.", suspended_counter); + dm_lib_release(); selinux_release(); if (_dm_bitset) diff --git a/libdm/ioctl/libdm-targets.h b/libdm/ioctl/libdm-targets.h index d8cee45..8fc8738 100644 --- a/libdm/ioctl/libdm-targets.h +++ b/libdm/ioctl/libdm-targets.h @@ -20,7 +20,6 @@ #include <sys/types.h> struct dm_ioctl; -struct dm_ioctl_v1; struct target { uint64_t start; @@ -34,6 +33,7 @@ struct target { struct dm_task { int type; char *dev_name; + char *mangled_dev_name; struct target *head, *tail; @@ -49,7 +49,6 @@ struct dm_task { uint32_t read_ahead_flags; union { struct dm_ioctl *v4; - struct dm_ioctl_v1 *v1; } dmi; char *newname; char *message; @@ -60,11 +59,17 @@ struct dm_task { int skip_lockfs; int query_inactive_table; int suppress_identical_reload; + dm_add_node_t add_node; uint64_t existing_table_size; int cookie_set; int new_uuid; + int secure_data; + int retry_remove; + int enable_checks; + int expected_errno; char *uuid; + char *mangled_uuid; }; struct cmd_data { diff --git a/libdm/libdevmapper.h b/libdm/libdevmapper.h index 4aa9991..c853ab4 100644 --- a/libdm/libdevmapper.h +++ b/libdm/libdevmapper.h @@ -1,6 +1,6 @@ /* * Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved. - * Copyright (C) 2004-2010 Red Hat, Inc. All rights reserved. + * Copyright (C) 2004-2011 Red Hat, Inc. All rights reserved. * * This file is part of the device-mapper userspace tools. * @@ -19,6 +19,7 @@ #include <inttypes.h> #include <stdarg.h> #include <sys/types.h> +#include <sys/stat.h> #ifdef linux # include <linux/types.h> @@ -74,6 +75,11 @@ void dm_log_init(dm_log_fn fn); */ int dm_log_is_non_default(void); +/* + * Number of devices currently in suspended state (via the library). + */ +int dm_get_suspended_counter(void); + enum { DM_DEVICE_CREATE, DM_DEVICE_RELOAD, @@ -158,13 +164,33 @@ struct dm_versions { int dm_get_library_version(char *version, size_t size); int dm_task_get_driver_version(struct dm_task *dmt, char *version, size_t size); int dm_task_get_info(struct dm_task *dmt, struct dm_info *dmi); -const char *dm_task_get_name(const struct dm_task *dmt); + +/* + * This function returns dm device's UUID based on the value + * of the mangling mode set during preceding dm_task_run call: + * - unmangled UUID for DM_STRING_MANGLING_{AUTO, HEX}, + * - UUID without any changes for DM_STRING_MANGLING_NONE. + * + * To get mangled or unmangled form of the UUID directly, use + * dm_task_get_uuid_mangled or dm_task_get_uuid_unmangled function. + */ const char *dm_task_get_uuid(const struct dm_task *dmt); struct dm_deps *dm_task_get_deps(struct dm_task *dmt); -struct dm_names *dm_task_get_names(struct dm_task *dmt); struct dm_versions *dm_task_get_versions(struct dm_task *dmt); +/* + * These functions return device-mapper names based on the value + * of the mangling mode set during preceding dm_task_run call: + * - unmangled name for DM_STRING_MANGLING_{AUTO, HEX}, + * - name without any changes for DM_STRING_MANGLING_NONE. + * + * To get mangled or unmangled form of the name directly, use + * dm_task_get_name_mangled or dm_task_get_name_unmangled function. + */ +const char *dm_task_get_name(const struct dm_task *dmt); +struct dm_names *dm_task_get_names(struct dm_task *dmt); + int dm_task_set_ro(struct dm_task *dmt); int dm_task_set_newname(struct dm_task *dmt, const char *newname); int dm_task_set_newuuid(struct dm_task *dmt, const char *newuuid); @@ -184,6 +210,19 @@ int dm_task_no_open_count(struct dm_task *dmt); int dm_task_skip_lockfs(struct dm_task *dmt); int dm_task_query_inactive_table(struct dm_task *dmt); int dm_task_suppress_identical_reload(struct dm_task *dmt); +int dm_task_secure_data(struct dm_task *dmt); +int dm_task_retry_remove(struct dm_task *dmt); + +/* + * Enable checks for common mistakes such as issuing ioctls in an unsafe order. + */ +int dm_task_enable_checks(struct dm_task *dmt); + +typedef enum { + DM_ADD_NODE_ON_RESUME, /* add /dev/mapper node with dmsetup resume */ + DM_ADD_NODE_ON_CREATE /* add /dev/mapper node with dmsetup create */ +} dm_add_node_t; +int dm_task_set_add_node(struct dm_task *dmt, dm_add_node_t add_node); /* * Control read_ahead. @@ -219,6 +258,30 @@ void *dm_get_next_target(struct dm_task *dmt, void *next, uint64_t *start, uint64_t *length, char **target_type, char **params); +/* Parse params from STATUS call for thin_pool target */ +struct dm_pool; + +struct dm_status_thin_pool { + uint64_t transaction_id; + uint64_t used_metadata_blocks; + uint64_t total_metadata_blocks; + uint64_t used_data_blocks; + uint64_t total_data_blocks; + uint64_t held_metadata_root; +}; + +int dm_get_status_thin_pool(struct dm_pool *mem, const char *params, + struct dm_status_thin_pool **status); + +/* Parse params from STATUS call for thin target */ +struct dm_status_thin { + uint64_t mapped_sectors; + uint64_t highest_mapped_sector; +}; + +int dm_get_status_thin(struct dm_pool *mem, const char *params, + struct dm_status_thin **status); + /* * Call this to actually run the ioctl. */ @@ -231,17 +294,96 @@ int dm_task_run(struct dm_task *dmt); void dm_task_update_nodes(void); /* + * Mangling support + * + * Character whitelist: 0-9, A-Z, a-z, #+-.:=@_ + * HEX mangling format: \xNN, NN being the hex value of the character. + * (whitelist and format supported by udev) +*/ +typedef enum { + DM_STRING_MANGLING_NONE, /* do not mangle at all */ + DM_STRING_MANGLING_AUTO, /* mangle only if not already mangled with hex, error when mixed */ + DM_STRING_MANGLING_HEX /* always mangle with hex encoding, no matter what the input is */ +} dm_string_mangling_t; + +/* + * Set/get mangling mode used for device-mapper names and uuids. + */ +int dm_set_name_mangling_mode(dm_string_mangling_t name_mangling); +dm_string_mangling_t dm_get_name_mangling_mode(void); + +/* + * Get mangled/unmangled form of the device-mapper name or uuid + * irrespective of the global setting (set by dm_set_name_mangling_mode). + * The name or uuid returned needs to be freed after use by calling dm_free! + */ +char *dm_task_get_name_mangled(const struct dm_task *dmt); +char *dm_task_get_name_unmangled(const struct dm_task *dmt); +char *dm_task_get_uuid_mangled(const struct dm_task *dmt); +char *dm_task_get_uuid_unmangled(const struct dm_task *dmt); + +/* * Configure the device-mapper directory */ int dm_set_dev_dir(const char *dir); const char *dm_dir(void); /* + * Configure sysfs directory, /sys by default + */ +int dm_set_sysfs_dir(const char *dir); +const char *dm_sysfs_dir(void); + +/* + * Configure default UUID prefix string. + * Conventionally this is a short capitalised prefix indicating the subsystem + * that is managing the devices, e.g. "LVM-" or "MPATH-". + * To support stacks of devices from different subsystems, recursive functions + * stop recursing if they reach a device with a different prefix. + */ +int dm_set_uuid_prefix(const char *uuid_prefix); +const char *dm_uuid_prefix(void); + +/* * Determine whether a major number belongs to device-mapper or not. */ int dm_is_dm_major(uint32_t major); /* + * Get associated device name for given major and minor number by reading + * the sysfs content. If this is a dm device, get associated dm name, the one + * that appears in /dev/mapper. DM names could be resolved this way only if + * kernel used >= 2.6.29, kernel name is found otherwise (e.g. dm-0). + * If prefer_kernel_name is set, the kernel name is always preferred over + * device-mapper name for dm devices no matter what the kernel version is. + * For non-dm devices, we always get associated kernel name, e.g sda, md0 etc. + * Returns 0 on error or if sysfs is not used (or configured incorrectly), + * otherwise returns 1 and the supplied buffer holds the device name. + */ +int dm_device_get_name(uint32_t major, uint32_t minor, + int prefer_kernel_name, + char *buf, size_t buf_size); + +/* + * Determine whether a device has any holders (devices + * using this device). If sysfs is not used (or configured + * incorrectly), returns 0. + */ +int dm_device_has_holders(uint32_t major, uint32_t minor); + +/* + * Determine whether a device contains mounted filesystem. + * If sysfs is not used (or configured incorrectly), returns 0. + */ +int dm_device_has_mounted_fs(uint32_t major, uint32_t minor); + + +/* + * Initialise library + */ +void dm_lib_init(void) __attribute__((constructor)); + +/* * Release library resources */ void dm_lib_release(void); @@ -384,6 +526,11 @@ void dm_tree_skip_lockfs(struct dm_tree_node *dnode); void dm_tree_use_no_flush_suspend(struct dm_tree_node *dnode); /* + * Retry removal of each device if not successful. + */ +void dm_tree_retry_remove(struct dm_tree_node *dnode); + +/* * Is the uuid prefix present in the tree? * Only returns 0 if every node was checked successfully. * Returns 1 if the tree walk has to be aborted. @@ -450,6 +597,14 @@ int dm_tree_node_add_mirror_target_log(struct dm_tree_node *node, unsigned area_count, uint32_t flags); +int dm_tree_node_add_raid_target(struct dm_tree_node *node, + uint64_t size, + const char *raid_type, + uint32_t region_size, + uint32_t stripe_size, + uint64_t rebuilds, + uint64_t flags); + /* * Replicator operation mode * Note: API for Replicator is not yet stable @@ -484,6 +639,63 @@ int dm_tree_node_add_replicator_dev_target(struct dm_tree_node *node, uint32_t slog_region_size); /* End of Replicator API */ +/* + * FIXME: Defines bellow are based on kernel's dm-thin.c defines + * DATA_DEV_BLOCK_SIZE_MIN_SECTORS (64 * 1024 >> SECTOR_SHIFT) + * DATA_DEV_BLOCK_SIZE_MAX_SECTORS (1024 * 1024 * 1024 >> SECTOR_SHIFT) + */ +#define DM_THIN_MIN_DATA_BLOCK_SIZE (UINT32_C(128)) +#define DM_THIN_MAX_DATA_BLOCK_SIZE (UINT32_C(2097152)) + +int dm_tree_node_add_thin_pool_target(struct dm_tree_node *node, + uint64_t size, + uint64_t transaction_id, + const char *metadata_uuid, + const char *pool_uuid, + uint32_t data_block_size, + uint64_t low_water_mark, + unsigned skip_block_zeroing); + +/* Supported messages for thin provision target */ +typedef enum { + DM_THIN_MESSAGE_CREATE_SNAP, /* device_id, origin_id */ + DM_THIN_MESSAGE_CREATE_THIN, /* device_id */ + DM_THIN_MESSAGE_DELETE, /* device_id */ + DM_THIN_MESSAGE_SET_TRANSACTION_ID, /* current_id, new_id */ + DM_THIN_MESSAGE_RESERVE_METADATA_SNAP, /* target version >= 1.1 */ + DM_THIN_MESSAGE_RELEASE_METADATA_SNAP, /* target version >= 1.1 */ +} dm_thin_message_t; + +int dm_tree_node_add_thin_pool_message(struct dm_tree_node *node, + dm_thin_message_t type, + uint64_t id1, uint64_t id2); + +/* + * Set thin pool discard features + * ignore - Disable support for discards + * no_passdown - Don't pass discards down to underlying data device, + * just remove the mapping + * Feature is available since version 1.1 of the thin target. + */ +int dm_tree_node_set_thin_pool_discard(struct dm_tree_node *node, + unsigned ignore, + unsigned no_passdown); + +/* + * FIXME: Defines bellow are based on kernel's dm-thin.c defines + * MAX_DEV_ID ((1 << 24) - 1) + */ +#define DM_THIN_MAX_DEVICE_ID (UINT32_C((1 << 24) - 1)) +int dm_tree_node_add_thin_target(struct dm_tree_node *node, + uint64_t size, + const char *pool_uuid, + uint32_t device_id); + +int dm_tree_node_set_thin_external_origin(struct dm_tree_node *node, + const char *external_uuid); + +void dm_tree_node_set_udev_flags(struct dm_tree_node *node, uint16_t udev_flags); + void dm_tree_node_set_presuspend_node(struct dm_tree_node *node, struct dm_tree_node *presuspend_node); @@ -493,12 +705,31 @@ int dm_tree_node_add_target_area(struct dm_tree_node *node, uint64_t offset); /* + * Only for temporarily-missing raid devices where changes are tracked. + */ +int dm_tree_node_add_null_area(struct dm_tree_node *node, uint64_t offset); + +/* * Set readahead (in sectors) after loading the node. */ void dm_tree_node_set_read_ahead(struct dm_tree_node *dnode, uint32_t read_ahead, uint32_t read_ahead_flags); +/* + * Set node callback hook before de/activation. + * Callback is called before 'activation' of node for activation tree, + * or 'deactivation' of node for deactivation tree. + */ +typedef enum { + DM_NODE_CALLBACK_PRELOADED, /* Node has preload deps */ + DM_NODE_CALLBACK_DEACTIVATED, /* Node is deactivated */ +} dm_node_callback_t; +typedef int (*dm_node_callback_fn) (struct dm_tree_node *node, + dm_node_callback_t type, void *cb_data); +void dm_tree_node_set_callback(struct dm_tree_node *node, + dm_node_callback_fn cb, void *cb_data); + void dm_tree_set_cookie(struct dm_tree_node *node, uint32_t cookie); uint32_t dm_tree_get_cookie(struct dm_tree_node *node); @@ -510,13 +741,19 @@ uint32_t dm_tree_get_cookie(struct dm_tree_node *node); * Memory management *******************/ -void *dm_malloc_aux(size_t s, const char *file, int line); -void *dm_malloc_aux_debug(size_t s, const char *file, int line); -void *dm_zalloc_aux(size_t s, const char *file, int line); -void *dm_zalloc_aux_debug(size_t s, const char *file, int line); -char *dm_strdup_aux(const char *str, const char *file, int line); +void *dm_malloc_aux(size_t s, const char *file, int line) + __attribute__((malloc)) __attribute__((__warn_unused_result__)); +void *dm_malloc_aux_debug(size_t s, const char *file, int line) + __attribute__((__warn_unused_result__)); +void *dm_zalloc_aux(size_t s, const char *file, int line) + __attribute__((malloc)) __attribute__((__warn_unused_result__)); +void *dm_zalloc_aux_debug(size_t s, const char *file, int line) + __attribute__((__warn_unused_result__)); +char *dm_strdup_aux(const char *str, const char *file, int line) + __attribute__((malloc)) __attribute__((__warn_unused_result__)); void dm_free_aux(void *p); -void *dm_realloc_aux(void *p, unsigned int s, const char *file, int line); +void *dm_realloc_aux(void *p, unsigned int s, const char *file, int line) + __attribute__((__warn_unused_result__)); int dm_dump_memory_debug(void); void dm_bounds_check_debug(void); @@ -579,16 +816,35 @@ void dm_bounds_check_debug(void); struct dm_pool; /* constructor and destructor */ -struct dm_pool *dm_pool_create(const char *name, size_t chunk_hint); +struct dm_pool *dm_pool_create(const char *name, size_t chunk_hint) + __attribute__((__warn_unused_result__)); void dm_pool_destroy(struct dm_pool *p); /* simple allocation/free routines */ -void *dm_pool_alloc(struct dm_pool *p, size_t s); -void *dm_pool_alloc_aligned(struct dm_pool *p, size_t s, unsigned alignment); +void *dm_pool_alloc(struct dm_pool *p, size_t s) + __attribute__((__warn_unused_result__)); +void *dm_pool_alloc_aligned(struct dm_pool *p, size_t s, unsigned alignment) + __attribute__((__warn_unused_result__)); void dm_pool_empty(struct dm_pool *p); void dm_pool_free(struct dm_pool *p, void *ptr); /* + * To aid debugging, a pool can be locked. Any modifications made + * to the content of the pool while it is locked can be detected. + * Default compilation is using a crc checksum to notice modifications. + * The pool locking is using the mprotect with the compilation flag + * DEBUG_ENFORCE_POOL_LOCKING to enforce the memory protection. + */ +/* query pool lock status */ +int dm_pool_locked(struct dm_pool *p); +/* mark pool as locked */ +int dm_pool_lock(struct dm_pool *p, int crc) + __attribute__((__warn_unused_result__)); +/* mark pool as unlocked */ +int dm_pool_unlock(struct dm_pool *p, int crc) + __attribute__((__warn_unused_result__)); + +/* * Object building routines: * * These allow you to 'grow' an object, useful for @@ -639,9 +895,12 @@ void *dm_pool_end_object(struct dm_pool *p); void dm_pool_abandon_object(struct dm_pool *p); /* utilities */ -char *dm_pool_strdup(struct dm_pool *p, const char *str); -char *dm_pool_strndup(struct dm_pool *p, const char *str, size_t n); -void *dm_pool_zalloc(struct dm_pool *p, size_t s); +char *dm_pool_strdup(struct dm_pool *p, const char *str) + __attribute__((__warn_unused_result__)); +char *dm_pool_strndup(struct dm_pool *p, const char *str, size_t n) + __attribute__((__warn_unused_result__)); +void *dm_pool_zalloc(struct dm_pool *p, size_t s) + __attribute__((__warn_unused_result__)); /****************** * bitset functions @@ -699,7 +958,8 @@ struct dm_hash_node; typedef void (*dm_hash_iterate_fn) (void *data); -struct dm_hash_table *dm_hash_create(unsigned size_hint); +struct dm_hash_table *dm_hash_create(unsigned size_hint) + __attribute__((__warn_unused_result__)); void dm_hash_destroy(struct dm_hash_table *t); void dm_hash_wipe(struct dm_hash_table *t); @@ -707,10 +967,10 @@ void *dm_hash_lookup(struct dm_hash_table *t, const char *key); int dm_hash_insert(struct dm_hash_table *t, const char *key, void *data); void dm_hash_remove(struct dm_hash_table *t, const char *key); -void *dm_hash_lookup_binary(struct dm_hash_table *t, const char *key, uint32_t len); -int dm_hash_insert_binary(struct dm_hash_table *t, const char *key, uint32_t len, +void *dm_hash_lookup_binary(struct dm_hash_table *t, const void *key, uint32_t len); +int dm_hash_insert_binary(struct dm_hash_table *t, const void *key, uint32_t len, void *data); -void dm_hash_remove_binary(struct dm_hash_table *t, const char *key, uint32_t len); +void dm_hash_remove_binary(struct dm_hash_table *t, const void *key, uint32_t len); unsigned dm_hash_get_num_entries(struct dm_hash_table *t); void dm_hash_iter(struct dm_hash_table *t, dm_hash_iterate_fn f); @@ -770,7 +1030,7 @@ void dm_list_del(struct dm_list *elem); void dm_list_move(struct dm_list *head, struct dm_list *elem); /* - * Join 'head1' to the of 'head'. + * Join 'head1' to the end of 'head'. */ void dm_list_splice(struct dm_list *head, struct dm_list *head1); @@ -968,6 +1228,52 @@ int dm_snprintf(char *buf, size_t bufsize, const char *format, ...) */ const char *dm_basename(const char *path); +/* + * Returns number of occurrences of 'c' in 'str' of length 'size'. + */ +unsigned dm_count_chars(const char *str, size_t len, const int c); + +/* + * Length of string after escaping double quotes and backslashes. + */ +size_t dm_escaped_len(const char *str); + +/* + * <vg>-<lv>-<layer> or if !layer just <vg>-<lv>. + */ +char *dm_build_dm_name(struct dm_pool *mem, const char *vgname, + const char *lvname, const char *layer); +char *dm_build_dm_uuid(struct dm_pool *mem, const char *prefix, const char *lvid, const char *layer); + +/* + * Copies a string, quoting double quotes with backslashes. + */ +char *dm_escape_double_quotes(char *out, const char *src); + +/* + * Undo quoting in situ. + */ +void dm_unescape_double_quotes(char *src); + +/* + * Unescape colons and "at" signs in situ and save the substrings + * starting at the position of the first unescaped colon and the + * first unescaped "at" sign. This is normally used to unescape + * device names used as PVs. + */ +void dm_unescape_colons_and_at_signs(char *src, + char **substr_first_unquoted_colon, + char **substr_first_unquoted_at_sign); + +/* + * Replacement for strncpy() function. + * + * Copies no more than n bytes from string pointed by src to the buffer + * pointed by dest and ensure string is finished with '\0'. + * Returns 0 if the whole string does not fit. + */ +int dm_strncpy(char *dest, const char *src, size_t n); + /************************** * file/stream manipulation **************************/ @@ -978,6 +1284,8 @@ const char *dm_basename(const char *path); */ int dm_create_dir(const char *dir); +int dm_is_empty_dir(const char *dir); + /* * Close a stream, with nicer error checking than fclose's. * Derived from gnulib's close-stream.c. @@ -995,6 +1303,7 @@ int dm_fclose(FILE *stream); */ int dm_asprintf(char **buf, const char *format, ...) __attribute__ ((format(printf, 2, 3))); +int dm_vasprintf(char **buf, const char *format, va_list ap); /* * create lockfile (pidfile) - create and lock a lock file @@ -1117,7 +1426,7 @@ int dm_report_set_output_field_name_prefix(struct dm_report *rh, * They take care of allocating copies of the data. */ int dm_report_field_string(struct dm_report *rh, struct dm_report_field *field, - const char **data); + const char *const *data); int dm_report_field_int32(struct dm_report *rh, struct dm_report_field *field, const int32_t *data); int dm_report_field_uint32(struct dm_report *rh, struct dm_report_field *field, @@ -1135,7 +1444,112 @@ int dm_report_field_uint64(struct dm_report *rh, struct dm_report_field *field, void dm_report_field_set_value(struct dm_report_field *field, const void *value, const void *sortvalue); +/************************* + * config file parse/print + *************************/ +typedef enum { + DM_CFG_INT, + DM_CFG_FLOAT, + DM_CFG_STRING, + DM_CFG_EMPTY_ARRAY +} dm_config_value_type_t; + +struct dm_config_value { + dm_config_value_type_t type; + + union { + int64_t i; + float f; + double d; /* Unused. */ + const char *str; + } v; + + struct dm_config_value *next; /* For arrays */ +}; + +struct dm_config_node { + const char *key; + struct dm_config_node *parent, *sib, *child; + struct dm_config_value *v; +}; + +struct dm_config_tree { + struct dm_config_node *root; + struct dm_config_tree *cascade; + struct dm_pool *mem; + void *custom; +}; + +struct dm_config_tree *dm_config_create(void); +struct dm_config_tree *dm_config_from_string(const char *config_settings); +int dm_config_parse(struct dm_config_tree *cft, const char *start, const char *end); + +void *dm_config_get_custom(struct dm_config_tree *cft); +void dm_config_set_custom(struct dm_config_tree *cft, void *custom); + +/* + * When searching, first_cft is checked before second_cft. + */ +struct dm_config_tree *dm_config_insert_cascaded_tree(struct dm_config_tree *first_cft, struct dm_config_tree *second_cft); + +/* + * If there's a cascaded dm_config_tree, remove the top layer + * and return the layer below. Otherwise return NULL. + */ +struct dm_config_tree *dm_config_remove_cascaded_tree(struct dm_config_tree *cft); + +void dm_config_destroy(struct dm_config_tree *cft); + +typedef int (*dm_putline_fn)(const char *line, void *baton); +/* Write the node and any subsequent siblings it has. */ +int dm_config_write_node(const struct dm_config_node *cn, dm_putline_fn putline, void *baton); +/* Write given node only without subsequent siblings. */ +int dm_config_write_one_node(const struct dm_config_node *cn, dm_putline_fn putline, void *baton); + +struct dm_config_node *dm_config_find_node(const struct dm_config_node *cn, const char *path); +int dm_config_has_node(const struct dm_config_node *cn, const char *path); +const char *dm_config_find_str(const struct dm_config_node *cn, const char *path, const char *fail); +const char *dm_config_find_str_allow_empty(const struct dm_config_node *cn, const char *path, const char *fail); +int dm_config_find_int(const struct dm_config_node *cn, const char *path, int fail); +int64_t dm_config_find_int64(const struct dm_config_node *cn, const char *path, int64_t fail); +float dm_config_find_float(const struct dm_config_node *cn, const char *path, float fail); + +const struct dm_config_node *dm_config_tree_find_node(const struct dm_config_tree *cft, const char *path); +const char *dm_config_tree_find_str(const struct dm_config_tree *cft, const char *path, const char *fail); +const char *dm_config_tree_find_str_allow_empty(const struct dm_config_tree *cft, const char *path, const char *fail); +int dm_config_tree_find_int(const struct dm_config_tree *cft, const char *path, int fail); +int64_t dm_config_tree_find_int64(const struct dm_config_tree *cft, const char *path, int64_t fail); +float dm_config_tree_find_float(const struct dm_config_tree *cft, const char *path, float fail); +int dm_config_tree_find_bool(const struct dm_config_tree *cft, const char *path, int fail); + +/* + * Understands (0, ~0), (y, n), (yes, no), (on, + * off), (true, false). + */ +int dm_config_find_bool(const struct dm_config_node *cn, const char *path, int fail); + +int dm_config_get_uint32(const struct dm_config_node *cn, const char *path, uint32_t *result); +int dm_config_get_uint64(const struct dm_config_node *cn, const char *path, uint64_t *result); +int dm_config_get_str(const struct dm_config_node *cn, const char *path, const char **result); +int dm_config_get_list(const struct dm_config_node *cn, const char *path, const struct dm_config_value **result); +int dm_config_get_section(const struct dm_config_node *cn, const char *path, const struct dm_config_node **result); + +unsigned dm_config_maybe_section(const char *str, unsigned len); + +const char *dm_config_parent_name(const struct dm_config_node *n); + +struct dm_config_node *dm_config_clone_node_with_mem(struct dm_pool *mem, const struct dm_config_node *node, int siblings); +struct dm_config_node *dm_config_create_node(struct dm_config_tree *cft, const char *key); +struct dm_config_value *dm_config_create_value(struct dm_config_tree *cft); +struct dm_config_node *dm_config_clone_node(struct dm_config_tree *cft, const struct dm_config_node *cn, int siblings); + +struct dm_pool *dm_config_memory(struct dm_config_tree *cft); + +/* Udev device directory. */ +#define DM_UDEV_DEV_DIR "/dev/" + /* Cookie prefixes. + * * The cookie value consists of a prefix (16 bits) and a base (16 bits). * We can use the prefix to store the flags. These flags are sent to * kernel within given dm task. When returned back to userspace in @@ -1143,6 +1557,7 @@ void dm_report_field_set_value(struct dm_report_field *field, const void *value, * of udev rules we use by decoding the cookie prefix. When doing the * notification, we replace the cookie prefix with DM_COOKIE_MAGIC, * so we notify the right semaphore. + * * It is still possible to use cookies for passing the flags to udev * rules even when udev_sync is disabled. The base part of the cookie * will be zero (there's no notification semaphore) and prefix will be @@ -1214,11 +1629,17 @@ void dm_udev_set_sync_support(int sync_with_udev); int dm_udev_get_sync_support(void); void dm_udev_set_checking(int checking); int dm_udev_get_checking(void); + +/* + * Default value to get new auto generated cookie created + */ +#define DM_COOKIE_AUTO_CREATE 0 int dm_udev_create_cookie(uint32_t *cookie); int dm_udev_complete(uint32_t cookie); int dm_udev_wait(uint32_t cookie); #define DM_DEV_DIR_UMASK 0022 +#define DM_CONTROL_NODE_UMASK 0177 #ifdef __cplusplus } diff --git a/libdm/libdm-common.c b/libdm/libdm-common.c index 6d8bcbd..b8533ed 100644 --- a/libdm/libdm-common.c +++ b/libdm/libdm-common.c @@ -1,6 +1,6 @@ /* * Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved. - * Copyright (C) 2004-2007 Red Hat, Inc. All rights reserved. + * Copyright (C) 2004-2012 Red Hat, Inc. All rights reserved. * * This file is part of the device-mapper userspace tools. * @@ -29,7 +29,6 @@ # include <sys/types.h> # include <sys/ipc.h> # include <sys/sem.h> -# define LIBUDEV_I_KNOW_THE_API_IS_SUBJECT_TO_CHANGE # include <libudev.h> #endif @@ -44,6 +43,8 @@ # include <selinux/label.h> #endif +#define DM_DEFAULT_NAME_MANGLING_MODE_ENV_VAR_NAME "DM_DEFAULT_NAME_MANGLING_MODE" + #define DEV_DIR "/dev/" #ifdef UDEV_SYNC_SUPPORT @@ -59,8 +60,15 @@ union semun #endif static char _dm_dir[PATH_MAX] = DEV_DIR DM_DIR; +static char _sysfs_dir[PATH_MAX] = "/sys/"; +static char _path0[PATH_MAX]; /* path buffer, safe 4kB on stack */ + +#define DM_MAX_UUID_PREFIX_LEN 15 +static char _default_uuid_prefix[DM_MAX_UUID_PREFIX_LEN + 1] = "LVM-"; static int _verbose = 0; +static int _suspended_dev_counter = 0; +static dm_string_mangling_t _name_mangling_mode = DEFAULT_DM_NAME_MANGLING; #ifdef HAVE_SELINUX_LABEL_H static struct selabel_handle *_selabel_handle = NULL; @@ -73,11 +81,28 @@ static int _sync_with_udev = 1; static int _udev_checking = 1; #endif +void dm_lib_init(void) +{ + const char *env; + + env = getenv(DM_DEFAULT_NAME_MANGLING_MODE_ENV_VAR_NAME); + if (env && *env) { + if (!strcasecmp(env, "none")) + _name_mangling_mode = DM_STRING_MANGLING_NONE; + else if (!strcasecmp(env, "auto")) + _name_mangling_mode = DM_STRING_MANGLING_AUTO; + else if (!strcasecmp(env, "hex")) + _name_mangling_mode = DM_STRING_MANGLING_HEX; + } else + _name_mangling_mode = DEFAULT_DM_NAME_MANGLING; +} + /* * Library users can provide their own logging * function. */ +__attribute__((format(printf, 5, 0))) static void _default_log_line(int level, const char *file __attribute__((unused)), int line __attribute__((unused)), int dm_errno, @@ -101,6 +126,7 @@ static void _default_log_line(int level, fprintf(use_stderr ? stderr : stdout, "\n"); } +__attribute__((format(printf, 5, 6))) static void _default_log_with_errno(int level, const char *file __attribute__((unused)), int line __attribute__((unused)), int dm_errno, @@ -113,6 +139,7 @@ static void _default_log_with_errno(int level, va_end(ap); } +__attribute__((format(printf, 4, 5))) static void _default_log(int level, const char *file, int line, const char *f, ...) { @@ -171,6 +198,40 @@ int dm_get_library_version(char *version, size_t size) return 1; } +void inc_suspended(void) +{ + _suspended_dev_counter++; + log_debug("Suspended device counter increased to %d", _suspended_dev_counter); +} + +void dec_suspended(void) +{ + if (!_suspended_dev_counter) { + log_error("Attempted to decrement suspended device counter below zero."); + return; + } + + _suspended_dev_counter--; + log_debug("Suspended device counter reduced to %d", _suspended_dev_counter); +} + +int dm_get_suspended_counter(void) +{ + return _suspended_dev_counter; +} + +int dm_set_name_mangling_mode(dm_string_mangling_t name_mangling_mode) +{ + _name_mangling_mode = name_mangling_mode; + + return 1; +} + +dm_string_mangling_t dm_get_name_mangling_mode(void) +{ + return _name_mangling_mode; +} + struct dm_task *dm_task_create(int type) { struct dm_task *dmt = dm_zalloc(sizeof(*dmt)); @@ -183,7 +244,7 @@ struct dm_task *dm_task_create(int type) if (!dm_check_version()) { dm_free(dmt); - return NULL; + return_NULL; } dmt->type = type; @@ -200,6 +261,7 @@ struct dm_task *dm_task_create(int type) dmt->cookie_set = 0; dmt->query_inactive_table = 0; dmt->new_uuid = 0; + dmt->secure_data = 0; return dmt; } @@ -207,18 +269,18 @@ struct dm_task *dm_task_create(int type) /* * Find the name associated with a given device number by scanning _dm_dir. */ -static char *_find_dm_name_of_device(dev_t st_rdev) +static int _find_dm_name_of_device(dev_t st_rdev, char *buf, size_t buf_len) { const char *name; char path[PATH_MAX]; struct dirent *dirent; DIR *d; - struct stat buf; - char *new_name = NULL; + struct stat st; + int r = 0; if (!(d = opendir(_dm_dir))) { log_sys_error("opendir", _dm_dir); - return NULL; + return 0; } while ((dirent = readdir(d))) { @@ -233,13 +295,12 @@ static char *_find_dm_name_of_device(dev_t st_rdev) continue; } - if (stat(path, &buf)) + if (stat(path, &st)) continue; - if (buf.st_rdev == st_rdev) { - if (!(new_name = dm_strdup(name))) - log_error("dm_task_set_name: strdup(%s) failed", - name); + if (st.st_rdev == st_rdev) { + strncpy(buf, name, buf_len); + r = 1; break; } } @@ -247,73 +308,444 @@ static char *_find_dm_name_of_device(dev_t st_rdev) if (closedir(d)) log_sys_error("closedir", _dm_dir); - return new_name; + return r; } -int dm_task_set_name(struct dm_task *dmt, const char *name) +static int _is_whitelisted_char(char c) { - char *pos; - char *new_name = NULL; - char path[PATH_MAX]; - struct stat st1, st2; - - dm_free(dmt->dev_name); - dmt->dev_name = NULL; - /* - * Path supplied for existing device? + * Actually, DM supports any character in a device name. + * This whitelist is just for proper integration with udev. */ - if ((pos = strrchr(name, '/'))) { - if (dmt->type == DM_DEVICE_CREATE) { - log_error("Name \"%s\" invalid. It contains \"/\".", name); - return 0; + if ((c >= '0' && c <= '9') || + (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || + strchr("#+-.:=@_", c) != NULL) + return 1; + + return 0; +} + +int check_multiple_mangled_string_allowed(const char *str, const char *str_name, + dm_string_mangling_t mode) +{ + if (mode == DM_STRING_MANGLING_AUTO && strstr(str, "\\x5cx")) { + log_error("The %s \"%s\" seems to be mangled more than once. " + "This is not allowed in auto mode.", str_name, str); + return 0; + } + + return 1; +} + +/* + * Mangle all characters in the input string which are not on a whitelist + * with '\xNN' format where NN is the hex value of the character. + */ +int mangle_string(const char *str, const char *str_name, size_t len, + char *buf, size_t buf_len, dm_string_mangling_t mode) +{ + int need_mangling = -1; /* -1 don't know yet, 0 no, 1 yes */ + size_t i, j; + + if (!str || !buf) + return -1; + + /* Is there anything to do at all? */ + if (!*str || !len) + return 0; + + if (buf_len < DM_NAME_LEN) { + log_error(INTERNAL_ERROR "mangle_string: supplied buffer too small"); + return -1; + } + + if (mode == DM_STRING_MANGLING_NONE) + mode = DM_STRING_MANGLING_AUTO; + + for (i = 0, j = 0; str[i]; i++) { + if (mode == DM_STRING_MANGLING_AUTO) { + /* + * Detect already mangled part of the string and keep it. + * Return error on mixture of mangled/not mangled! + */ + if (str[i] == '\\' && str[i+1] == 'x') { + if ((len - i < 4) || (need_mangling == 1)) + goto bad1; + if (buf_len - j < 4) + goto bad2; + + memcpy(&buf[j], &str[i], 4); + i+=3; j+=4; + + need_mangling = 0; + continue; + } } - if (stat(name, &st1)) { - log_error("Device %s not found", name); - return 0; + if (_is_whitelisted_char(str[i])) { + /* whitelisted, keep it. */ + if (buf_len - j < 1) + goto bad2; + buf[j] = str[i]; + j++; + } else { + /* + * Not on a whitelist, mangle it. + * Return error on mixture of mangled/not mangled + * unless a DM_STRING_MANGLING_HEX is used!. + */ + if ((mode != DM_STRING_MANGLING_HEX) && (need_mangling == 0)) + goto bad1; + if (buf_len - j < 4) + goto bad2; + + sprintf(&buf[j], "\\x%02x", (unsigned char) str[i]); + j+=4; + + need_mangling = 1; } + } - /* - * If supplied path points to same device as last component - * under /dev/mapper, use that name directly. Otherwise call - * _find_dm_name_of_device() to scan _dm_dir for a match. - */ - if (dm_snprintf(path, sizeof(path), "%s/%s", _dm_dir, - pos + 1) == -1) { - log_error("Couldn't create path for %s", pos + 1); - return 0; + if (buf_len - j < 1) + goto bad2; + buf[j] = '\0'; + + /* All chars in the string whitelisted? */ + if (need_mangling == -1) + need_mangling = 0; + + return need_mangling; + +bad1: + log_error("The %s \"%s\" contains mixed mangled and unmangled " + "characters or it's already mangled improperly.", str_name, str); + return -1; +bad2: + log_error("Mangled form of the %s too long for \"%s\".", str_name, str); + return -1; +} + +/* + * Try to unmangle supplied string. + * Return value: -1 on error, 0 when no unmangling needed, 1 when unmangling applied + */ +int unmangle_string(const char *str, const char *str_name, size_t len, + char *buf, size_t buf_len, dm_string_mangling_t mode) +{ + int strict = mode != DM_STRING_MANGLING_NONE; + char str_rest[DM_NAME_LEN]; + size_t i, j; + int code; + int r = 0; + + if (!str || !buf) + return -1; + + /* Is there anything to do at all? */ + if (!*str || !len) + return 0; + + if (buf_len < DM_NAME_LEN) { + log_error(INTERNAL_ERROR "unmangle_string: supplied buffer too small"); + return -1; + } + + for (i = 0, j = 0; str[i]; i++, j++) { + if (strict && !(_is_whitelisted_char(str[i]) || str[i]=='\\')) { + log_error("The %s \"%s\" should be mangled but " + "it contains blacklisted characters.", str_name, str); + j=0; r=-1; + goto out; } - if (!stat(path, &st2) && (st1.st_rdev == st2.st_rdev)) - name = pos + 1; - else if ((new_name = _find_dm_name_of_device(st1.st_rdev))) - name = new_name; - else { - log_error("Device %s not found", name); + if (str[i] == '\\' && str[i+1] == 'x') { + if (!sscanf(&str[i+2], "%2x%s", &code, str_rest)) { + log_debug("Hex encoding mismatch detected in %s \"%s\" " + "while trying to unmangle it.", str_name, str); + goto out; + } + buf[j] = (unsigned char) code; + + /* skip the encoded part we've just decoded! */ + i+= 3; + + /* unmangling applied */ + r = 1; + } else + buf[j] = str[i]; + } + +out: + buf[j] = '\0'; + return r; +} + +static int _dm_task_set_name(struct dm_task *dmt, const char *name, + dm_string_mangling_t mangling_mode) +{ + char mangled_name[DM_NAME_LEN]; + int r = 0; + + dm_free(dmt->dev_name); + dmt->dev_name = NULL; + dm_free(dmt->mangled_dev_name); + dmt->mangled_dev_name = NULL; + + if (strlen(name) >= DM_NAME_LEN) { + log_error("Name \"%s\" too long.", name); + return 0; + } + + if (!check_multiple_mangled_string_allowed(name, "name", mangling_mode)) + return_0; + + if (mangling_mode != DM_STRING_MANGLING_NONE && + (r = mangle_string(name, "name", strlen(name), mangled_name, + sizeof(mangled_name), mangling_mode)) < 0) { + log_error("Failed to mangle device name \"%s\".", name); + return 0; + } + + /* Store mangled_dev_name only if it differs from dev_name! */ + if (r) { + log_debug("Device name mangled [%s]: %s --> %s", + mangling_mode == DM_STRING_MANGLING_AUTO ? "auto" : "hex", + name, mangled_name); + if (!(dmt->mangled_dev_name = dm_strdup(mangled_name))) { + log_error("_dm_task_set_name: dm_strdup(%s) failed", mangled_name); return 0; } } - if (strlen(name) >= DM_NAME_LEN) { - log_error("Name \"%s\" too long", name); - dm_free(new_name); + if (!(dmt->dev_name = dm_strdup(name))) { + log_error("_dm_task_set_name: strdup(%s) failed", name); + return 0; + } + + return 1; +} + +static int _dm_task_set_name_from_path(struct dm_task *dmt, const char *path, + const char *name) +{ + char buf[PATH_MAX]; + struct stat st1, st2; + const char *final_name; + + if (dmt->type == DM_DEVICE_CREATE) { + log_error("Name \"%s\" invalid. It contains \"/\".", path); return 0; } - if (new_name) - dmt->dev_name = new_name; - else if (!(dmt->dev_name = dm_strdup(name))) { - log_error("dm_task_set_name: strdup(%s) failed", name); + if (stat(path, &st1)) { + log_error("Device %s not found", path); return 0; } + /* + * If supplied path points to same device as last component + * under /dev/mapper, use that name directly. Otherwise call + * _find_dm_name_of_device() to scan _dm_dir for a match. + */ + if (dm_snprintf(buf, sizeof(buf), "%s/%s", _dm_dir, name) == -1) { + log_error("Couldn't create path for %s", name); + return 0; + } + + if (!stat(buf, &st2) && (st1.st_rdev == st2.st_rdev)) + final_name = name; + else if (_find_dm_name_of_device(st1.st_rdev, buf, sizeof(buf))) + final_name = buf; + else { + log_error("Device %s not found", name); + return 0; + } + + /* This is an already existing path - do not mangle! */ + return _dm_task_set_name(dmt, final_name, DM_STRING_MANGLING_NONE); +} + +int dm_task_set_name(struct dm_task *dmt, const char *name) +{ + char *pos; + + /* Path supplied for existing device? */ + if ((pos = strrchr(name, '/'))) + return _dm_task_set_name_from_path(dmt, name, pos + 1); + + return _dm_task_set_name(dmt, name, dm_get_name_mangling_mode()); +} + +const char *dm_task_get_name(const struct dm_task *dmt) +{ + return (dmt->dmi.v4->name); +} + +static char *_task_get_string_mangled(const char *str, const char *str_name, + char *buf, size_t buf_size, + dm_string_mangling_t mode) +{ + char *rs; + int r; + + if ((r = mangle_string(str, str_name, strlen(str), buf, buf_size, mode)) < 0) + return NULL; + + if (!(rs = r ? dm_strdup(buf) : dm_strdup(str))) + log_error("_task_get_string_mangled: dm_strdup failed"); + + return rs; +} + +static char *_task_get_string_unmangled(const char *str, const char *str_name, + char *buf, size_t buf_size, + dm_string_mangling_t mode) +{ + char *rs; + int r = 0; + + /* + * Unless the mode used is 'none', the string + * is *already* unmangled on ioctl return! + */ + if (mode == DM_STRING_MANGLING_NONE && + (r = unmangle_string(str, str_name, strlen(str), buf, buf_size, mode)) < 0) + return NULL; + + if (!(rs = r ? dm_strdup(buf) : dm_strdup(str))) + log_error("_task_get_string_unmangled: dm_strdup failed"); + + return rs; +} + +char *dm_task_get_name_mangled(const struct dm_task *dmt) +{ + const char *s = dm_task_get_name(dmt); + char buf[DM_NAME_LEN]; + char *rs; + + if (!(rs = _task_get_string_mangled(s, "name", buf, sizeof(buf), dm_get_name_mangling_mode()))) + log_error("Failed to mangle device name \"%s\".", s); + + return rs; +} + +char *dm_task_get_name_unmangled(const struct dm_task *dmt) +{ + const char *s = dm_task_get_name(dmt); + char buf[DM_NAME_LEN]; + char *rs; + + if (!(rs = _task_get_string_unmangled(s, "name", buf, sizeof(buf), dm_get_name_mangling_mode()))) + log_error("Failed to unmangle device name \"%s\".", s); + + return rs; +} + +const char *dm_task_get_uuid(const struct dm_task *dmt) +{ + return (dmt->dmi.v4->uuid); +} + +char *dm_task_get_uuid_mangled(const struct dm_task *dmt) +{ + const char *s = dm_task_get_uuid(dmt); + char buf[DM_UUID_LEN]; + char *rs; + + if (!(rs = _task_get_string_mangled(s, "UUID", buf, sizeof(buf), dm_get_name_mangling_mode()))) + log_error("Failed to mangle device uuid \"%s\".", s); + + return rs; +} + +char *dm_task_get_uuid_unmangled(const struct dm_task *dmt) +{ + const char *s = dm_task_get_uuid(dmt); + char buf[DM_UUID_LEN]; + char *rs; + + if (!(rs = _task_get_string_unmangled(s, "UUID", buf, sizeof(buf), dm_get_name_mangling_mode()))) + log_error("Failed to unmangle device uuid \"%s\".", s); + + return rs; +} + +int dm_task_set_newname(struct dm_task *dmt, const char *newname) +{ + dm_string_mangling_t mangling_mode = dm_get_name_mangling_mode(); + char mangled_name[DM_NAME_LEN]; + int r = 0; + + if (strchr(newname, '/')) { + log_error("Name \"%s\" invalid. It contains \"/\".", newname); + return 0; + } + + if (strlen(newname) >= DM_NAME_LEN) { + log_error("Name \"%s\" too long", newname); + return 0; + } + + if (!check_multiple_mangled_string_allowed(newname, "new name", mangling_mode)) + return_0; + + if (mangling_mode != DM_STRING_MANGLING_NONE && + (r = mangle_string(newname, "new name", strlen(newname), mangled_name, + sizeof(mangled_name), mangling_mode)) < 0) { + log_error("Failed to mangle new device name \"%s\"", newname); + return 0; + } + + if (r) { + log_debug("New device name mangled [%s]: %s --> %s", + mangling_mode == DM_STRING_MANGLING_AUTO ? "auto" : "hex", + newname, mangled_name); + newname = mangled_name; + } + + if (!(dmt->newname = dm_strdup(newname))) { + log_error("dm_task_set_newname: strdup(%s) failed", newname); + return 0; + } + + dmt->new_uuid = 0; + return 1; } int dm_task_set_uuid(struct dm_task *dmt, const char *uuid) { + char mangled_uuid[DM_UUID_LEN]; + dm_string_mangling_t mangling_mode = dm_get_name_mangling_mode(); + int r = 0; + dm_free(dmt->uuid); + dmt->uuid = NULL; + dm_free(dmt->mangled_uuid); + dmt->mangled_uuid = NULL; + + if (!check_multiple_mangled_string_allowed(uuid, "UUID", mangling_mode)) + return_0; + + if (mangling_mode != DM_STRING_MANGLING_NONE && + (r = mangle_string(uuid, "UUID", strlen(uuid), mangled_uuid, + sizeof(mangled_uuid), mangling_mode)) < 0) { + log_error("Failed to mangle device uuid \"%s\".", uuid); + return 0; + } + + if (r) { + log_debug("Device uuid mangled [%s]: %s --> %s", + mangling_mode == DM_STRING_MANGLING_AUTO ? "auto" : "hex", + uuid, mangled_uuid); + + if (!(dmt->mangled_uuid = dm_strdup(mangled_uuid))) { + log_error("dm_task_set_uuid: dm_strdup(%s) failed", mangled_uuid); + return 0; + } + } if (!(dmt->uuid = dm_strdup(uuid))) { log_error("dm_task_set_uuid: strdup(%s) failed", uuid); @@ -369,13 +801,19 @@ int dm_task_set_mode(struct dm_task *dmt, mode_t mode) return 1; } +int dm_task_enable_checks(struct dm_task *dmt) +{ + dmt->enable_checks = 1; + + return 1; +} + int dm_task_add_target(struct dm_task *dmt, uint64_t start, uint64_t size, const char *ttype, const char *params) { struct target *t = create_target(start, size, ttype, params); - if (!t) - return 0; + return_0; if (!dmt->head) dmt->head = dmt->tail = t; @@ -399,12 +837,14 @@ static int _selabel_lookup(const char *path, mode_t mode, } if (selabel_lookup(_selabel_handle, scontext, path, mode)) { - log_error("selabel_lookup failed: %s", strerror(errno)); + log_debug("selabel_lookup failed for %s: %s", + path, strerror(errno)); return 0; } #else if (matchpathcon(path, mode, scontext)) { - log_error("matchpathcon failed: %s", strerror(errno)); + log_debug("matchpathcon failed for %s: %s", + path, strerror(errno)); return 0; } #endif @@ -473,12 +913,17 @@ void selinux_release(void) #endif } +static int _warn_if_op_needed(int warn_if_udev_failed) +{ + return warn_if_udev_failed && dm_udev_get_sync_support() && dm_udev_get_checking(); +} + static int _add_dev_node(const char *dev_name, uint32_t major, uint32_t minor, - uid_t uid, gid_t gid, mode_t mode, int check_udev) + uid_t uid, gid_t gid, mode_t mode, int warn_if_udev_failed) { char path[PATH_MAX]; struct stat info; - dev_t dev = MKDEV(major, minor); + dev_t dev = MKDEV((dev_t)major, minor); mode_t old_mask; _build_dev_path(path, sizeof(path), dev_name); @@ -499,15 +944,14 @@ static int _add_dev_node(const char *dev_name, uint32_t major, uint32_t minor, dev_name); return 0; } - } else if (dm_udev_get_sync_support() && dm_udev_get_checking() && - check_udev) + } else if (_warn_if_op_needed(warn_if_udev_failed)) log_warn("%s not set up by udev: Falling back to direct " "node creation.", path); (void) dm_prepare_selinux_context(path, S_IFBLK); old_mask = umask(0); if (mknod(path, S_IFBLK | mode, dev) < 0) { - log_error("Unable to make device node for '%s'", dev_name); + log_error("%s: mknod for %s failed: %s", path, dev_name, strerror(errno)); umask(old_mask); (void) dm_prepare_selinux_context(NULL, 0); return 0; @@ -525,7 +969,7 @@ static int _add_dev_node(const char *dev_name, uint32_t major, uint32_t minor, return 1; } -static int _rm_dev_node(const char *dev_name, int check_udev) +static int _rm_dev_node(const char *dev_name, int warn_if_udev_failed) { char path[PATH_MAX]; struct stat info; @@ -534,8 +978,7 @@ static int _rm_dev_node(const char *dev_name, int check_udev) if (stat(path, &info) < 0) return 1; - else if (dm_udev_get_sync_support() && dm_udev_get_checking() && - check_udev) + else if (_warn_if_op_needed(warn_if_udev_failed)) log_warn("Node %s was not removed by udev. " "Falling back to direct node removal.", path); @@ -550,7 +993,7 @@ static int _rm_dev_node(const char *dev_name, int check_udev) } static int _rename_dev_node(const char *old_name, const char *new_name, - int check_udev) + int warn_if_udev_failed) { char oldpath[PATH_MAX]; char newpath[PATH_MAX]; @@ -565,8 +1008,7 @@ static int _rename_dev_node(const char *old_name, const char *new_name, "is already present", newpath); return 0; } - else if (dm_udev_get_sync_support() && dm_udev_get_checking() && - check_udev) { + else if (_warn_if_op_needed(warn_if_udev_failed)) { if (stat(oldpath, &info) < 0 && errno == ENOENT) /* assume udev already deleted this */ @@ -590,8 +1032,7 @@ static int _rename_dev_node(const char *old_name, const char *new_name, return 0; } } - else if (dm_udev_get_sync_support() && dm_udev_get_checking() && - check_udev) + else if (_warn_if_op_needed(warn_if_udev_failed)) log_warn("The node %s should have been renamed to %s " "by udev but new node is not present. " "Falling back to direct node rename.", @@ -622,12 +1063,53 @@ static int _open_dev_node(const char *dev_name) return fd; } -int get_dev_node_read_ahead(const char *dev_name, uint32_t *read_ahead) +int get_dev_node_read_ahead(const char *dev_name, uint32_t major, uint32_t minor, + uint32_t *read_ahead) { + char buf[24]; + int len; int r = 1; int fd; long read_ahead_long; + /* + * If we know the device number, use sysfs if we can. + * Otherwise use BLKRAGET ioctl. + */ + if (*_sysfs_dir && major != 0) { + if (dm_snprintf(_path0, sizeof(_path0), "%sdev/block/%" PRIu32 + ":%" PRIu32 "/bdi/read_ahead_kb", _sysfs_dir, + major, minor) < 0) { + log_error("Failed to build sysfs_path."); + return 0; + } + + if ((fd = open(_path0, O_RDONLY, 0)) != -1) { + /* Reading from sysfs, expecting number\n */ + if ((len = read(fd, buf, sizeof(buf) - 1)) < 1) { + log_sys_error("read", _path0); + r = 0; + } else { + buf[len] = 0; /* kill \n and ensure \0 */ + *read_ahead = atoi(buf) * 2; + log_debug("%s (%d:%d): read ahead is %" PRIu32, + dev_name, major, minor, *read_ahead); + } + + if (close(fd)) + log_sys_debug("close", _path0); + + return r; + } + + log_sys_debug("open", _path0); + /* Fall back to use dev_name */ + } + + /* + * Open/close dev_name may block the process + * (i.e. overfilled thin pool volume) + */ if (!*dev_name) { log_error("Empty device name passed to BLKRAGET"); return 0; @@ -640,23 +1122,64 @@ int get_dev_node_read_ahead(const char *dev_name, uint32_t *read_ahead) log_sys_error("BLKRAGET", dev_name); *read_ahead = 0; r = 0; - } else { + } else { *read_ahead = (uint32_t) read_ahead_long; log_debug("%s: read ahead is %" PRIu32, dev_name, *read_ahead); } if (close(fd)) - stack; + log_sys_debug("close", dev_name); return r; } -static int _set_read_ahead(const char *dev_name, uint32_t read_ahead) +static int _set_read_ahead(const char *dev_name, uint32_t major, uint32_t minor, + uint32_t read_ahead) { + char buf[24]; + int len; int r = 1; int fd; long read_ahead_long = (long) read_ahead; + log_debug("%s (%d:%d): Setting read ahead to %" PRIu32, dev_name, + major, minor, read_ahead); + + /* + * If we know the device number, use sysfs if we can. + * Otherwise use BLKRASET ioctl. RA is set after resume. + */ + if (*_sysfs_dir && major != 0) { + if (dm_snprintf(_path0, sizeof(_path0), "%sdev/block/%" PRIu32 + ":%" PRIu32 "/bdi/read_ahead_kb", + _sysfs_dir, major, minor) < 0) { + log_error("Failed to build sysfs_path."); + return 0; + } + + /* Sysfs is kB based, round up to kB */ + if ((len = dm_snprintf(buf, sizeof(buf), "%" PRIu32, + (read_ahead + 1) / 2)) < 0) { + log_error("Failed to build size in kB."); + return 0; + } + + if ((fd = open(_path0, O_WRONLY, 0)) != -1) { + if (write(fd, buf, len) < len) { + log_sys_error("write", _path0); + r = 0; + } + + if (close(fd)) + log_sys_debug("close", _path0); + + return r; + } + + log_sys_debug("open", _path0); + /* Fall back to use dev_name */ + } + if (!*dev_name) { log_error("Empty device name passed to BLKRAGET"); return 0; @@ -665,21 +1188,20 @@ static int _set_read_ahead(const char *dev_name, uint32_t read_ahead) if ((fd = _open_dev_node(dev_name)) < 0) return_0; - log_debug("%s: Setting read ahead to %" PRIu32, dev_name, read_ahead); - if (ioctl(fd, BLKRASET, read_ahead_long)) { log_sys_error("BLKRASET", dev_name); r = 0; } if (close(fd)) - stack; + log_sys_debug("close", dev_name); return r; } -static int _set_dev_node_read_ahead(const char *dev_name, uint32_t read_ahead, - uint32_t read_ahead_flags) +static int _set_dev_node_read_ahead(const char *dev_name, + uint32_t major, uint32_t minor, + uint32_t read_ahead, uint32_t read_ahead_flags) { uint32_t current_read_ahead; @@ -690,7 +1212,7 @@ static int _set_dev_node_read_ahead(const char *dev_name, uint32_t read_ahead, read_ahead = 0; if (read_ahead_flags & DM_READ_AHEAD_MINIMUM_FLAG) { - if (!get_dev_node_read_ahead(dev_name, ¤t_read_ahead)) + if (!get_dev_node_read_ahead(dev_name, major, minor, ¤t_read_ahead)) return_0; if (current_read_ahead > read_ahead) { @@ -701,7 +1223,7 @@ static int _set_dev_node_read_ahead(const char *dev_name, uint32_t read_ahead, } } - return _set_read_ahead(dev_name, read_ahead); + return _set_read_ahead(dev_name, major, minor, read_ahead); } #else @@ -713,8 +1235,9 @@ int get_dev_node_read_ahead(const char *dev_name, uint32_t *read_ahead) return 1; } -static int _set_dev_node_read_ahead(const char *dev_name, uint32_t read_ahead, - uint32_t read_ahead_flags) +static int _set_dev_node_read_ahead(const char *dev_name, + uint32_t major, uint32_t minor, + uint32_t read_ahead, uint32_t read_ahead_flags) { return 1; } @@ -724,31 +1247,35 @@ typedef enum { NODE_ADD, NODE_DEL, NODE_RENAME, - NODE_READ_AHEAD + NODE_READ_AHEAD, + NUM_NODES } node_op_t; static int _do_node_op(node_op_t type, const char *dev_name, uint32_t major, uint32_t minor, uid_t uid, gid_t gid, mode_t mode, const char *old_name, uint32_t read_ahead, - uint32_t read_ahead_flags, int check_udev) + uint32_t read_ahead_flags, int warn_if_udev_failed) { switch (type) { case NODE_ADD: return _add_dev_node(dev_name, major, minor, uid, gid, - mode, check_udev); + mode, warn_if_udev_failed); case NODE_DEL: - return _rm_dev_node(dev_name, check_udev); + return _rm_dev_node(dev_name, warn_if_udev_failed); case NODE_RENAME: - return _rename_dev_node(old_name, dev_name, check_udev); + return _rename_dev_node(old_name, dev_name, warn_if_udev_failed); case NODE_READ_AHEAD: - return _set_dev_node_read_ahead(dev_name, read_ahead, - read_ahead_flags); + return _set_dev_node_read_ahead(dev_name, major, minor, + read_ahead, read_ahead_flags); + default: + ; /* NOTREACHED */ } return 1; } static DM_LIST_INIT(_node_ops); +static int _count_node_ops[NUM_NODES]; struct node_op_parms { struct dm_list list; @@ -762,7 +1289,8 @@ struct node_op_parms { uint32_t read_ahead; uint32_t read_ahead_flags; char *old_name; - int check_udev; + int warn_if_udev_failed; + unsigned rely_on_udev; char names[0]; }; @@ -773,10 +1301,56 @@ static void _store_str(char **pos, char **ptr, const char *str) *pos += strlen(*ptr) + 1; } +static void _del_node_op(struct node_op_parms *nop) +{ + _count_node_ops[nop->type]--; + dm_list_del(&nop->list); + dm_free(nop); + +} + +/* Check if there is other the type of node operation stacked */ +static int _other_node_ops(node_op_t type) +{ + unsigned i; + + for (i = 0; i < NUM_NODES; i++) + if (type != i && _count_node_ops[i]) + return 1; + return 0; +} + +static void _log_node_op(const char *action_str, struct node_op_parms *nop) +{ + const char *rely = nop->rely_on_udev ? " [trust_udev]" : "" ; + const char *verify = nop->warn_if_udev_failed ? " [verify_udev]" : ""; + + switch (nop->type) { + case NODE_ADD: + log_debug("%s: %s NODE_ADD (%" PRIu32 ",%" PRIu32 ") %u:%u 0%o%s%s", + nop->dev_name, action_str, nop->major, nop->minor, nop->uid, nop->gid, nop->mode, + rely, verify); + break; + case NODE_DEL: + log_debug("%s: %s NODE_DEL%s%s", nop->dev_name, action_str, rely, verify); + break; + case NODE_RENAME: + log_debug("%s: %s NODE_RENAME to %s%s%s", nop->old_name, action_str, nop->dev_name, rely, verify); + break; + case NODE_READ_AHEAD: + log_debug("%s: %s NODE_READ_AHEAD %" PRIu32 " (flags=%" PRIu32 ")%s%s", + nop->dev_name, action_str, nop->read_ahead, nop->read_ahead_flags, rely, verify); + break; + default: + ; /* NOTREACHED */ + } +} + static int _stack_node_op(node_op_t type, const char *dev_name, uint32_t major, uint32_t minor, uid_t uid, gid_t gid, mode_t mode, const char *old_name, uint32_t read_ahead, - uint32_t read_ahead_flags, int check_udev) + uint32_t read_ahead_flags, int warn_if_udev_failed, + unsigned rely_on_udev) { struct node_op_parms *nop; struct dm_list *noph, *nopht; @@ -784,16 +1358,55 @@ static int _stack_node_op(node_op_t type, const char *dev_name, uint32_t major, char *pos; /* - * Ignore any outstanding operations on the node if deleting it + * Note: warn_if_udev_failed must have valid content */ - if (type == NODE_DEL) { + if ((type == NODE_DEL) && _other_node_ops(type)) + /* + * Ignore any outstanding operations on the node if deleting it. + */ dm_list_iterate_safe(noph, nopht, &_node_ops) { nop = dm_list_item(noph, struct node_op_parms); if (!strcmp(dev_name, nop->dev_name)) { - dm_list_del(&nop->list); - dm_free(nop); + _log_node_op("Unstacking", nop); + _del_node_op(nop); + if (!_other_node_ops(type)) + break; /* no other non DEL ops */ + } + } + else if ((type == NODE_ADD) && _count_node_ops[NODE_DEL]) + /* + * Ignore previous DEL operation on added node. + * (No other operations for this device then DEL could be stacked here). + */ + dm_list_iterate_safe(noph, nopht, &_node_ops) { + nop = dm_list_item(noph, struct node_op_parms); + if ((nop->type == NODE_DEL) && + !strcmp(dev_name, nop->dev_name)) { + _log_node_op("Unstacking", nop); + _del_node_op(nop); + break; /* no other DEL ops */ } } + else if (type == NODE_RENAME) + /* + * Ignore any outstanding operations if renaming it. + * + * Currently RENAME operation happens through 'suspend -> resume'. + * On 'resume' device is added with read_ahead settings, so it is + * safe to remove any stacked ADD, RENAME, READ_AHEAD operation + * There cannot be any DEL operation on the renamed device. + */ + dm_list_iterate_safe(noph, nopht, &_node_ops) { + nop = dm_list_item(noph, struct node_op_parms); + if (!strcmp(old_name, nop->dev_name)) { + _log_node_op("Unstacking", nop); + _del_node_op(nop); + } + } + else if (type == NODE_READ_AHEAD) { + /* udev doesn't process readahead */ + rely_on_udev = 0; + warn_if_udev_failed = 0; } if (!(nop = dm_malloc(sizeof(*nop) + len))) { @@ -810,13 +1423,22 @@ static int _stack_node_op(node_op_t type, const char *dev_name, uint32_t major, nop->mode = mode; nop->read_ahead = read_ahead; nop->read_ahead_flags = read_ahead_flags; - nop->check_udev = check_udev; + nop->rely_on_udev = rely_on_udev; + + /* + * Clear warn_if_udev_failed if rely_on_udev is set. It doesn't get + * checked in this case - this just removes the flag from log messages. + */ + nop->warn_if_udev_failed = rely_on_udev ? 0 : warn_if_udev_failed; _store_str(&pos, &nop->dev_name, dev_name); _store_str(&pos, &nop->old_name, old_name); + _count_node_ops[type]++; dm_list_add(&_node_ops, &nop->list); + _log_node_op("Stacking", nop); + return 1; } @@ -827,52 +1449,46 @@ static void _pop_node_ops(void) dm_list_iterate_safe(noph, nopht, &_node_ops) { nop = dm_list_item(noph, struct node_op_parms); - _do_node_op(nop->type, nop->dev_name, nop->major, nop->minor, - nop->uid, nop->gid, nop->mode, nop->old_name, - nop->read_ahead, nop->read_ahead_flags, - nop->check_udev); - dm_list_del(&nop->list); - dm_free(nop); + if (!nop->rely_on_udev) { + _log_node_op("Processing", nop); + _do_node_op(nop->type, nop->dev_name, nop->major, nop->minor, + nop->uid, nop->gid, nop->mode, nop->old_name, + nop->read_ahead, nop->read_ahead_flags, + nop->warn_if_udev_failed); + } else + _log_node_op("Skipping", nop); + _del_node_op(nop); } } int add_dev_node(const char *dev_name, uint32_t major, uint32_t minor, - uid_t uid, gid_t gid, mode_t mode, int check_udev) + uid_t uid, gid_t gid, mode_t mode, int check_udev, unsigned rely_on_udev) { - log_debug("%s: Stacking NODE_ADD (%" PRIu32 ",%" PRIu32 ") %u:%u 0%o", - dev_name, major, minor, uid, gid, mode); - return _stack_node_op(NODE_ADD, dev_name, major, minor, uid, - gid, mode, "", 0, 0, check_udev); + gid, mode, "", 0, 0, check_udev, rely_on_udev); } -int rename_dev_node(const char *old_name, const char *new_name, int check_udev) +int rename_dev_node(const char *old_name, const char *new_name, int check_udev, unsigned rely_on_udev) { - log_debug("%s: Stacking NODE_RENAME to %s", old_name, new_name); - return _stack_node_op(NODE_RENAME, new_name, 0, 0, 0, - 0, 0, old_name, 0, 0, check_udev); + 0, 0, old_name, 0, 0, check_udev, rely_on_udev); } -int rm_dev_node(const char *dev_name, int check_udev) +int rm_dev_node(const char *dev_name, int check_udev, unsigned rely_on_udev) { - log_debug("%s: Stacking NODE_DEL (replaces other stacked ops)", dev_name); - return _stack_node_op(NODE_DEL, dev_name, 0, 0, 0, - 0, 0, "", 0, 0, check_udev); + 0, 0, "", 0, 0, check_udev, rely_on_udev); } -int set_dev_node_read_ahead(const char *dev_name, uint32_t read_ahead, - uint32_t read_ahead_flags) +int set_dev_node_read_ahead(const char *dev_name, + uint32_t major, uint32_t minor, + uint32_t read_ahead, uint32_t read_ahead_flags) { if (read_ahead == DM_READ_AHEAD_AUTO) return 1; - log_debug("%s: Stacking NODE_READ_AHEAD %" PRIu32 " (flags=%" PRIu32 - ")", dev_name, read_ahead, read_ahead_flags); - - return _stack_node_op(NODE_READ_AHEAD, dev_name, 0, 0, 0, 0, - 0, "", read_ahead, read_ahead_flags, 0); + return _stack_node_op(NODE_READ_AHEAD, dev_name, major, minor, 0, 0, + 0, "", read_ahead, read_ahead_flags, 0, 0); } void update_devs(void) @@ -880,33 +1496,282 @@ void update_devs(void) _pop_node_ops(); } -int dm_set_dev_dir(const char *dev_dir) +static int _canonicalize_and_set_dir(const char *src, const char *suffix, size_t max_len, char *dir) { size_t len; const char *slash; - if (*dev_dir != '/') { - log_debug("Invalid dev_dir value, %s: " - "not an absolute name.", dev_dir); + + if (*src != '/') { + log_debug("Invalid directory value, %s: " + "not an absolute name.", src); return 0; } - len = strlen(dev_dir); - slash = dev_dir[len-1] == '/' ? "" : "/"; + len = strlen(src); + slash = src[len-1] == '/' ? "" : "/"; - if (snprintf(_dm_dir, sizeof _dm_dir, "%s%s%s", dev_dir, slash, DM_DIR) - >= sizeof _dm_dir) { - log_debug("Invalid dev_dir value, %s: name too long.", dev_dir); + if (dm_snprintf(dir, max_len, "%s%s%s", src, slash, suffix ? suffix : "") < 0) { + log_debug("Invalid directory value, %s: name too long.", src); return 0; } return 1; } +int dm_set_dev_dir(const char *dev_dir) +{ + return _canonicalize_and_set_dir(dev_dir, DM_DIR, sizeof _dm_dir, _dm_dir); +} + const char *dm_dir(void) { return _dm_dir; } +int dm_set_sysfs_dir(const char *sysfs_dir) +{ + if (!sysfs_dir || !*sysfs_dir) { + _sysfs_dir[0] = '\0'; + return 1; + } + else + return _canonicalize_and_set_dir(sysfs_dir, NULL, sizeof _sysfs_dir, _sysfs_dir); +} + +const char *dm_sysfs_dir(void) +{ + return _sysfs_dir; +} + +/* + * Replace existing uuid_prefix provided it isn't too long. + */ +int dm_set_uuid_prefix(const char *uuid_prefix) +{ + if (!uuid_prefix) + return_0; + + if (strlen(uuid_prefix) > DM_MAX_UUID_PREFIX_LEN) { + log_error("New uuid prefix %s too long.", uuid_prefix); + return 0; + } + + strcpy(_default_uuid_prefix, uuid_prefix); + + return 1; +} + +const char *dm_uuid_prefix(void) +{ + return _default_uuid_prefix; +} + +static int _sysfs_get_dm_name(uint32_t major, uint32_t minor, char *buf, size_t buf_size) +{ + char *sysfs_path, *temp_buf = NULL; + FILE *fp = NULL; + int r = 0; + size_t len; + + if (!(sysfs_path = dm_malloc(PATH_MAX)) || + !(temp_buf = dm_malloc(PATH_MAX))) { + log_error("_sysfs_get_dm_name: failed to allocate temporary buffers"); + goto bad; + } + + if (dm_snprintf(sysfs_path, PATH_MAX, "%sdev/block/%" PRIu32 ":%" PRIu32 + "/dm/name", _sysfs_dir, major, minor) < 0) { + log_error("_sysfs_get_dm_name: dm_snprintf failed"); + goto bad; + } + + if (!(fp = fopen(sysfs_path, "r"))) { + if (errno != ENOENT) + log_sys_error("fopen", sysfs_path); + else + log_sys_debug("fopen", sysfs_path); + goto bad; + } + + if (!fgets(temp_buf, PATH_MAX, fp)) { + log_sys_error("fgets", sysfs_path); + goto bad; + } + + len = strlen(temp_buf); + + if (len > buf_size) { + log_error("_sysfs_get_dm_name: supplied buffer too small"); + goto bad; + } + + temp_buf[len ? len - 1 : 0] = '\0'; /* \n */ + strcpy(buf, temp_buf); + r = 1; +bad: + if (fp && fclose(fp)) + log_sys_error("fclose", sysfs_path); + + dm_free(temp_buf); + dm_free(sysfs_path); + + return r; +} + +static int _sysfs_get_kernel_name(uint32_t major, uint32_t minor, char *buf, size_t buf_size) +{ + char *name, *sysfs_path, *temp_buf = NULL; + ssize_t size; + size_t len; + int r = 0; + + if (!(sysfs_path = dm_malloc(PATH_MAX)) || + !(temp_buf = dm_malloc(PATH_MAX))) { + log_error("_sysfs_get_kernel_name: failed to allocate temporary buffers"); + goto bad; + } + + if (dm_snprintf(sysfs_path, PATH_MAX, "%sdev/block/%" PRIu32 ":%" PRIu32, + _sysfs_dir, major, minor) < 0) { + log_error("_sysfs_get_kernel_name: dm_snprintf failed"); + goto bad; + } + + if ((size = readlink(sysfs_path, temp_buf, PATH_MAX - 1)) < 0) { + if (errno != ENOENT) + log_sys_error("readlink", sysfs_path); + else + log_sys_debug("readlink", sysfs_path); + goto bad; + } + temp_buf[size] = '\0'; + + if (!(name = strrchr(temp_buf, '/'))) { + log_error("Could not locate device kernel name in sysfs path %s", temp_buf); + goto bad; + } + name += 1; + len = size - (name - temp_buf) + 1; + + if (len > buf_size) { + log_error("_sysfs_get_kernel_name: output buffer too small"); + goto bad; + } + + strcpy(buf, name); + r = 1; +bad: + dm_free(temp_buf); + dm_free(sysfs_path); + + return r; +} + +int dm_device_get_name(uint32_t major, uint32_t minor, int prefer_kernel_name, + char *buf, size_t buf_size) +{ + if (!*_sysfs_dir) + return 0; + + /* + * device-mapper devices and prefer_kernel_name = 0 + * get dm name by reading /sys/dev/block/major:minor/dm/name, + * fallback to _sysfs_get_kernel_name if not successful + */ + if (dm_is_dm_major(major) && !prefer_kernel_name) { + if (_sysfs_get_dm_name(major, minor, buf, buf_size)) + return 1; + else + stack; + } + + /* + * non-device-mapper devices or prefer_kernel_name = 1 + * get kernel name using readlink /sys/dev/block/major:minor -> .../dm-X + */ + return _sysfs_get_kernel_name(major, minor, buf, buf_size); +} + +int dm_device_has_holders(uint32_t major, uint32_t minor) +{ + char sysfs_path[PATH_MAX]; + struct stat st; + + if (!*_sysfs_dir) + return 0; + + if (dm_snprintf(sysfs_path, PATH_MAX, "%sdev/block/%" PRIu32 + ":%" PRIu32 "/holders", _sysfs_dir, major, minor) < 0) { + log_error("sysfs_path dm_snprintf failed"); + return 0; + } + + if (stat(sysfs_path, &st)) { + log_sys_error("stat", sysfs_path); + return 0; + } + + return !dm_is_empty_dir(sysfs_path); +} + +static int _mounted_fs_on_device(const char *kernel_dev_name) +{ + char sysfs_path[PATH_MAX]; + struct dirent *dirent; + DIR *d; + struct stat st; + int r = 0; + + if (dm_snprintf(sysfs_path, PATH_MAX, "%sfs", _sysfs_dir) < 0) { + log_error("sysfs_path dm_snprintf failed"); + return 0; + } + + if (!(d = opendir(sysfs_path))) { + if (errno != ENOENT) + log_sys_error("opendir", sysfs_path); + return 0; + } + + while ((dirent = readdir(d))) { + if (!strcmp(dirent->d_name, ".") || !strcmp(dirent->d_name, "..")) + continue; + + if (dm_snprintf(sysfs_path, PATH_MAX, "%sfs/%s/%s", + _sysfs_dir, dirent->d_name, kernel_dev_name) < 0) { + log_error("sysfs_path dm_snprintf failed"); + break; + } + + if (!stat(sysfs_path, &st)) { + /* found! */ + r = 1; + break; + } + else if (errno != ENOENT) { + log_sys_error("stat", sysfs_path); + break; + } + } + + if (closedir(d)) + log_error("_fs_present_on_device: %s: closedir failed", kernel_dev_name); + + return r; +} + +int dm_device_has_mounted_fs(uint32_t major, uint32_t minor) +{ + char kernel_dev_name[PATH_MAX]; + + /* Get kernel device name first */ + if (!dm_device_get_name(major, minor, 1, kernel_dev_name, PATH_MAX)) + return 0; + + /* Check /sys/fs/<fs_name>/<kernel_dev_name> presence */ + return _mounted_fs_on_device(kernel_dev_name); +} + int dm_mknodes(const char *name) { struct dm_task *dmt; @@ -984,6 +1849,8 @@ int dm_udev_complete(uint32_t cookie) int dm_udev_wait(uint32_t cookie) { + update_devs(); + return 1; } @@ -1111,6 +1978,7 @@ static int _get_cookie_sem(uint32_t cookie, int *semid) static int _udev_notify_sem_inc(uint32_t cookie, int semid) { struct sembuf sb = {0, 1, 0}; + int val; if (semop(semid, &sb, 1) < 0) { log_error("semid %d: semop failed for cookie 0x%" PRIx32 ": %s", @@ -1118,8 +1986,15 @@ static int _udev_notify_sem_inc(uint32_t cookie, int semid) return 0; } - log_debug("Udev cookie 0x%" PRIx32 " (semid %d) incremented", - cookie, semid); + if ((val = semctl(semid, 0, GETVAL)) < 0) { + log_error("semid %d: sem_ctl GETVAL failed for " + "cookie 0x%" PRIx32 ": %s", + semid, cookie, strerror(errno)); + return 0; + } + + log_debug("Udev cookie 0x%" PRIx32 " (semid %d) incremented to %d", + cookie, semid, val); return 1; } @@ -1127,6 +2002,14 @@ static int _udev_notify_sem_inc(uint32_t cookie, int semid) static int _udev_notify_sem_dec(uint32_t cookie, int semid) { struct sembuf sb = {0, -1, IPC_NOWAIT}; + int val; + + if ((val = semctl(semid, 0, GETVAL)) < 0) { + log_error("semid %d: sem_ctl GETVAL failed for " + "cookie 0x%" PRIx32 ": %s", + semid, cookie, strerror(errno)); + return 0; + } if (semop(semid, &sb, 1) < 0) { switch (errno) { @@ -1145,8 +2028,8 @@ static int _udev_notify_sem_dec(uint32_t cookie, int semid) return 0; } - log_debug("Udev cookie 0x%" PRIx32 " (semid %d) decremented", - cookie, semid); + log_debug("Udev cookie 0x%" PRIx32 " (semid %d) decremented to %d", + cookie, semid, val - 1); return 1; } @@ -1170,6 +2053,7 @@ static int _udev_notify_sem_create(uint32_t *cookie, int *semid) { int fd; int gen_semid; + int val; uint16_t base_cookie; uint32_t gen_cookie; union semun sem_arg; @@ -1230,8 +2114,15 @@ static int _udev_notify_sem_create(uint32_t *cookie, int *semid) goto bad; } - log_debug("Udev cookie 0x%" PRIx32 " (semid %d) incremented", - gen_cookie, gen_semid); + if ((val = semctl(gen_semid, 0, GETVAL)) < 0) { + log_error("semid %d: sem_ctl GETVAL failed for " + "cookie 0x%" PRIx32 ": %s", + gen_semid, gen_cookie, strerror(errno)); + goto bad; + } + + log_debug("Udev cookie 0x%" PRIx32 " (semid %d) incremented to %d", + gen_cookie, gen_semid, val); if (close(fd)) stack; @@ -1262,6 +2153,51 @@ int dm_udev_create_cookie(uint32_t *cookie) return _udev_notify_sem_create(cookie, &semid); } +static const char *_task_type_disp(int type) +{ + switch(type) { + case DM_DEVICE_CREATE: + return "CREATE"; + case DM_DEVICE_RELOAD: + return "RELOAD"; + case DM_DEVICE_REMOVE: + return "REMOVE"; + case DM_DEVICE_REMOVE_ALL: + return "REMOVE_ALL"; + case DM_DEVICE_SUSPEND: + return "SUSPEND"; + case DM_DEVICE_RESUME: + return "RESUME"; + case DM_DEVICE_INFO: + return "INFO"; + case DM_DEVICE_DEPS: + return "DEPS"; + case DM_DEVICE_RENAME: + return "RENAME"; + case DM_DEVICE_VERSION: + return "VERSION"; + case DM_DEVICE_STATUS: + return "STATUS"; + case DM_DEVICE_TABLE: + return "TABLE"; + case DM_DEVICE_WAITEVENT: + return "WAITEVENT"; + case DM_DEVICE_LIST: + return "LIST"; + case DM_DEVICE_CLEAR: + return "CLEAR"; + case DM_DEVICE_MKNODES: + return "MKNODES"; + case DM_DEVICE_LIST_VERSIONS: + return "LIST_VERSIONS"; + case DM_DEVICE_TARGET_MSG: + return "TARGET_MSG"; + case DM_DEVICE_SET_GEOMETRY: + return "SET_GEOMETRY"; + } + return "unknown"; +} + int dm_task_set_cookie(struct dm_task *dmt, uint32_t *cookie, uint16_t flags) { int semid; @@ -1290,8 +2226,16 @@ int dm_task_set_cookie(struct dm_task *dmt, uint32_t *cookie, uint16_t flags) dmt->event_nr |= ~DM_UDEV_FLAGS_MASK & *cookie; dmt->cookie_set = 1; - log_debug("Udev cookie 0x%" PRIx32 " (semid %d) assigned to dm_task " - "type %d with flags 0x%" PRIx16, *cookie, semid, dmt->type, flags); + log_debug("Udev cookie 0x%" PRIx32 " (semid %d) assigned to " + "%s task(%d) with flags%s%s%s%s%s%s%s (0x%" PRIx16 ")", *cookie, semid, _task_type_disp(dmt->type), dmt->type, + (flags & DM_UDEV_DISABLE_DM_RULES_FLAG) ? " DISABLE_DM_RULES" : "", + (flags & DM_UDEV_DISABLE_SUBSYSTEM_RULES_FLAG) ? " DISABLE_SUBSYSTEM_RULES" : "", + (flags & DM_UDEV_DISABLE_DISK_RULES_FLAG) ? " DISABLE_DISK_RULES" : "", + (flags & DM_UDEV_DISABLE_OTHER_RULES_FLAG) ? " DISABLE_OTHER_RULES" : "", + (flags & DM_UDEV_LOW_PRIORITY_FLAG) ? " LOW_PRIORITY" : "", + (flags & DM_UDEV_DISABLE_LIBRARY_FALLBACK) ? " DISABLE_LIBRARY_FALLBACK" : "", + (flags & DM_UDEV_PRIMARY_SOURCE_FLAG) ? " PRIMARY_SOURCE" : "", + flags); return 1; @@ -1320,7 +2264,7 @@ int dm_udev_complete(uint32_t cookie) return 1; } -int dm_udev_wait(uint32_t cookie) +static int _udev_wait(uint32_t cookie) { int semid; struct sembuf sb = {0, 0, 0}; @@ -1340,7 +2284,7 @@ int dm_udev_wait(uint32_t cookie) return 0; } - log_debug("Udev cookie 0x%" PRIx32 " (semid %d): Waiting for zero", + log_debug("Udev cookie 0x%" PRIx32 " (semid %d) waiting for zero", cookie, semid); repeat_wait: @@ -1360,4 +2304,13 @@ repeat_wait: return _udev_notify_sem_destroy(cookie, semid); } +int dm_udev_wait(uint32_t cookie) +{ + int r = _udev_wait(cookie); + + update_devs(); + + return r; +} + #endif /* UDEV_SYNC_SUPPORT */ diff --git a/libdm/libdm-common.h b/libdm/libdm-common.h index 3267cfc..4705a77 100644 --- a/libdm/libdm-common.h +++ b/libdm/libdm-common.h @@ -1,6 +1,6 @@ /* * Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved. - * Copyright (C) 2004-2007 Red Hat, Inc. All rights reserved. + * Copyright (C) 2004-2012 Red Hat, Inc. All rights reserved. * * This file is part of the device-mapper userspace tools. * @@ -18,19 +18,37 @@ #include "libdevmapper.h" +#define DM_DEFAULT_NAME_MANGLING_MODE_ENV_VAR_NAME "DM_DEFAULT_NAME_MANGLING_MODE" + +#define DEV_NAME(dmt) (dmt->mangled_dev_name ? : dmt->dev_name) +#define DEV_UUID(DMT) (dmt->mangled_uuid ? : dmt->uuid) + +int mangle_string(const char *str, const char *str_name, size_t len, + char *buf, size_t buf_len, dm_string_mangling_t mode); + +int unmangle_string(const char *str, const char *str_name, size_t len, + char *buf, size_t buf_len, dm_string_mangling_t mode); + +int check_multiple_mangled_string_allowed(const char *str, const char *str_name, + dm_string_mangling_t mode); + struct target *create_target(uint64_t start, uint64_t len, const char *type, const char *params); int add_dev_node(const char *dev_name, uint32_t minor, uint32_t major, - uid_t uid, gid_t gid, mode_t mode, int check_udev); -int rm_dev_node(const char *dev_name, int check_udev); + uid_t uid, gid_t gid, mode_t mode, int check_udev, unsigned rely_on_udev); +int rm_dev_node(const char *dev_name, int check_udev, unsigned rely_on_udev); int rename_dev_node(const char *old_name, const char *new_name, - int check_udev); -int get_dev_node_read_ahead(const char *dev_name, uint32_t *read_ahead); -int set_dev_node_read_ahead(const char *dev_name, uint32_t read_ahead, - uint32_t read_ahead_flags); + int check_udev, unsigned rely_on_udev); +int get_dev_node_read_ahead(const char *dev_name, uint32_t major, uint32_t minor, + uint32_t *read_ahead); +int set_dev_node_read_ahead(const char *dev_name, uint32_t major, uint32_t minor, + uint32_t read_ahead, uint32_t read_ahead_flags); void update_devs(void); void selinux_release(void); +void inc_suspended(void); +void dec_suspended(void); + #endif diff --git a/libdm/libdm-config.c b/libdm/libdm-config.c new file mode 100644 index 0000000..c19f51d --- /dev/null +++ b/libdm/libdm-config.c @@ -0,0 +1,1186 @@ +/* + * Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved. + * Copyright (C) 2004-2011 Red Hat, Inc. All rights reserved. + * + * This file is part of LVM2. + * + * 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 Lesser General Public License v.2.1. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "dmlib.h" + +#include <sys/stat.h> +#include <sys/mman.h> +#include <unistd.h> +#include <fcntl.h> +#include <ctype.h> + +#define SECTION_B_CHAR '{' +#define SECTION_E_CHAR '}' + +enum { + TOK_INT, + TOK_FLOAT, + TOK_STRING, /* Single quotes */ + TOK_STRING_ESCAPED, /* Double quotes */ + TOK_EQ, + TOK_SECTION_B, + TOK_SECTION_E, + TOK_ARRAY_B, + TOK_ARRAY_E, + TOK_IDENTIFIER, + TOK_COMMA, + TOK_EOF +}; + +struct parser { + const char *fb, *fe; /* file limits */ + + int t; /* token limits and type */ + const char *tb, *te; + + int line; /* line number we are on */ + + struct dm_pool *mem; +}; + +struct output_line { + struct dm_pool *mem; + dm_putline_fn putline; + void *putline_baton; +}; + +static void _get_token(struct parser *p, int tok_prev); +static void _eat_space(struct parser *p); +static struct dm_config_node *_file(struct parser *p); +static struct dm_config_node *_section(struct parser *p); +static struct dm_config_value *_value(struct parser *p); +static struct dm_config_value *_type(struct parser *p); +static int _match_aux(struct parser *p, int t); +static struct dm_config_value *_create_value(struct dm_pool *mem); +static struct dm_config_node *_create_node(struct dm_pool *mem); +static char *_dup_tok(struct parser *p); + +static const int sep = '/'; + +#define MAX_INDENT 32 + +#define match(t) do {\ + if (!_match_aux(p, (t))) {\ + log_error("Parse error at byte %" PRIptrdiff_t " (line %d): unexpected token", \ + p->tb - p->fb + 1, p->line); \ + return 0;\ + } \ +} while(0) + +static int _tok_match(const char *str, const char *b, const char *e) +{ + while (*str && (b != e)) { + if (*str++ != *b++) + return 0; + } + + return !(*str || (b != e)); +} + +struct dm_config_tree *dm_config_create(void) +{ + struct dm_config_tree *cft; + struct dm_pool *mem = dm_pool_create("config", 10 * 1024); + + if (!mem) { + log_error("Failed to allocate config pool."); + return 0; + } + + if (!(cft = dm_pool_zalloc(mem, sizeof(*cft)))) { + log_error("Failed to allocate config tree."); + dm_pool_destroy(mem); + return 0; + } + cft->root = NULL; + cft->cascade = NULL; + cft->custom = NULL; + cft->mem = mem; + return cft; +} + +void dm_config_set_custom(struct dm_config_tree *cft, void *custom) +{ + cft->custom = custom; +} + +void *dm_config_get_custom(struct dm_config_tree *cft) +{ + return cft->custom; +} + +void dm_config_destroy(struct dm_config_tree *cft) +{ + dm_pool_destroy(cft->mem); +} + +/* + * If there's a cascaded dm_config_tree, remove and return it, otherwise + * return NULL. + */ +struct dm_config_tree *dm_config_remove_cascaded_tree(struct dm_config_tree *cft) +{ + struct dm_config_tree *second_cft; + + if (!cft) + return NULL; + + second_cft = cft->cascade; + cft->cascade = NULL; + + return second_cft; +} + +/* + * When searching, first_cft is checked before second_cft. + */ +struct dm_config_tree *dm_config_insert_cascaded_tree(struct dm_config_tree *first_cft, struct dm_config_tree *second_cft) +{ + first_cft->cascade = second_cft; + + return first_cft; +} + +int dm_config_parse(struct dm_config_tree *cft, const char *start, const char *end) +{ + /* TODO? if (start == end) return 1; */ + + struct parser *p; + if (!(p = dm_pool_alloc(cft->mem, sizeof(*p)))) + return_0; + + p->mem = cft->mem; + p->fb = start; + p->fe = end; + p->tb = p->te = p->fb; + p->line = 1; + + _get_token(p, TOK_SECTION_E); + if (!(cft->root = _file(p))) + return_0; + + return 1; +} + +struct dm_config_tree *dm_config_from_string(const char *config_settings) +{ + struct dm_config_tree *cft; + + if (!(cft = dm_config_create())) + return_NULL; + + if (!dm_config_parse(cft, config_settings, config_settings + strlen(config_settings))) { + dm_config_destroy(cft); + return_NULL; + } + + return cft; +} + +static int _line_start(struct output_line *outline) +{ + if (!dm_pool_begin_object(outline->mem, 128)) { + log_error("dm_pool_begin_object failed for config line"); + return 0; + } + + return 1; +} + +__attribute__ ((format(printf, 2, 3))) +static int _line_append(struct output_line *outline, const char *fmt, ...) +{ + char buf[4096]; + va_list ap; + int n; + + va_start(ap, fmt); + n = vsnprintf(&buf[0], sizeof buf - 1, fmt, ap); + va_end(ap); + + if (n < 0 || n > (int) sizeof buf - 1) { + log_error("vsnprintf failed for config line"); + return 0; + } + + if (!dm_pool_grow_object(outline->mem, &buf[0], strlen(buf))) { + log_error("dm_pool_grow_object failed for config line"); + return 0; + } + + return 1; +} + +#define line_append(args...) do {if (!_line_append(outline, args)) {return_0;}} while (0) + +static int _line_end(struct output_line *outline) +{ + const char *line; + + if (!dm_pool_grow_object(outline->mem, "\0", 1)) { + log_error("dm_pool_grow_object failed for config line"); + return 0; + } + + line = dm_pool_end_object(outline->mem); + + if (!outline->putline) + return 0; + + outline->putline(line, outline->putline_baton); + + return 1; +} + +static int _write_value(struct output_line *outline, const struct dm_config_value *v) +{ + char *buf; + + switch (v->type) { + case DM_CFG_STRING: + if (!(buf = alloca(dm_escaped_len(v->v.str)))) { + log_error("temporary stack allocation for a config " + "string failed"); + return 0; + } + line_append("\"%s\"", dm_escape_double_quotes(buf, v->v.str)); + break; + + case DM_CFG_FLOAT: + line_append("%f", v->v.f); + break; + + case DM_CFG_INT: + line_append("%" PRId64, v->v.i); + break; + + case DM_CFG_EMPTY_ARRAY: + line_append("[]"); + break; + + default: + log_error("_write_value: Unknown value type: %d", v->type); + + } + + return 1; +} + +static int _write_config(const struct dm_config_node *n, int only_one, + struct output_line *outline, int level) +{ + char space[MAX_INDENT + 1]; + int l = (level < MAX_INDENT) ? level : MAX_INDENT; + int i; + + if (!n) + return 1; + + for (i = 0; i < l; i++) + space[i] = '\t'; + space[i] = '\0'; + + do { + if (!_line_start(outline)) + return_0; + line_append("%s%s", space, n->key); + if (!n->v) { + /* it's a sub section */ + line_append(" {"); + if (!_line_end(outline)) + return_0; + _write_config(n->child, 0, outline, level + 1); + if (!_line_start(outline)) + return_0; + line_append("%s}", space); + } else { + /* it's a value */ + const struct dm_config_value *v = n->v; + line_append("="); + if (v->next) { + line_append("["); + while (v && v->type != DM_CFG_EMPTY_ARRAY) { + if (!_write_value(outline, v)) + return_0; + v = v->next; + if (v && v->type != DM_CFG_EMPTY_ARRAY) + line_append(", "); + } + line_append("]"); + } else + if (!_write_value(outline, v)) + return_0; + } + if (!_line_end(outline)) + return_0; + n = n->sib; + } while (n && !only_one); + /* FIXME: add error checking */ + return 1; +} + +static int _write_node(const struct dm_config_node *cn, int only_one, + dm_putline_fn putline, void *baton) +{ + struct output_line outline; + if (!(outline.mem = dm_pool_create("config_line", 1024))) + return_0; + outline.putline = putline; + outline.putline_baton = baton; + if (!_write_config(cn, only_one, &outline, 0)) { + dm_pool_destroy(outline.mem); + return_0; + } + dm_pool_destroy(outline.mem); + return 1; +} + +int dm_config_write_one_node(const struct dm_config_node *cn, dm_putline_fn putline, void *baton) +{ + return _write_node(cn, 1, putline, baton); +} + +int dm_config_write_node(const struct dm_config_node *cn, dm_putline_fn putline, void *baton) +{ + return _write_node(cn, 0, putline, baton); +} + +/* + * parser + */ +static struct dm_config_node *_file(struct parser *p) +{ + struct dm_config_node *root = NULL, *n, *l = NULL; + while (p->t != TOK_EOF) { + if (!(n = _section(p))) + return_NULL; + + if (!root) + root = n; + else + l->sib = n; + n->parent = root; + l = n; + } + return root; +} + +static struct dm_config_node *_section(struct parser *p) +{ + /* IDENTIFIER SECTION_B_CHAR VALUE* SECTION_E_CHAR */ + struct dm_config_node *root, *n, *l = NULL; + if (!(root = _create_node(p->mem))) { + log_error("Failed to allocate section node"); + return NULL; + } + + if (!(root->key = _dup_tok(p))) + return_NULL; + + match(TOK_IDENTIFIER); + + if (p->t == TOK_SECTION_B) { + match(TOK_SECTION_B); + while (p->t != TOK_SECTION_E) { + if (!(n = _section(p))) + return_NULL; + + if (!l) + root->child = n; + else + l->sib = n; + n->parent = root; + l = n; + } + match(TOK_SECTION_E); + } else { + match(TOK_EQ); + if (!(root->v = _value(p))) + return_NULL; + } + + return root; +} + +static struct dm_config_value *_value(struct parser *p) +{ + /* '[' TYPE* ']' | TYPE */ + struct dm_config_value *h = NULL, *l, *ll = NULL; + if (p->t == TOK_ARRAY_B) { + match(TOK_ARRAY_B); + while (p->t != TOK_ARRAY_E) { + if (!(l = _type(p))) + return_NULL; + + if (!h) + h = l; + else + ll->next = l; + ll = l; + + if (p->t == TOK_COMMA) + match(TOK_COMMA); + } + match(TOK_ARRAY_E); + /* + * Special case for an empty array. + */ + if (!h) { + if (!(h = _create_value(p->mem))) { + log_error("Failed to allocate value"); + return NULL; + } + + h->type = DM_CFG_EMPTY_ARRAY; + } + + } else + if (!(h = _type(p))) + return_NULL; + + return h; +} + +static struct dm_config_value *_type(struct parser *p) +{ + /* [+-]{0,1}[0-9]+ | [0-9]*\.[0-9]* | ".*" */ + struct dm_config_value *v = _create_value(p->mem); + char *str; + + if (!v) { + log_error("Failed to allocate type value"); + return NULL; + } + + switch (p->t) { + case TOK_INT: + v->type = DM_CFG_INT; + v->v.i = strtoll(p->tb, NULL, 0); /* FIXME: check error */ + match(TOK_INT); + break; + + case TOK_FLOAT: + v->type = DM_CFG_FLOAT; + v->v.f = strtod(p->tb, NULL); /* FIXME: check error */ + match(TOK_FLOAT); + break; + + case TOK_STRING: + v->type = DM_CFG_STRING; + + p->tb++, p->te--; /* strip "'s */ + if (!(v->v.str = _dup_tok(p))) + return_NULL; + p->te++; + match(TOK_STRING); + break; + + case TOK_STRING_ESCAPED: + v->type = DM_CFG_STRING; + + p->tb++, p->te--; /* strip "'s */ + if (!(str = _dup_tok(p))) + return_NULL; + dm_unescape_double_quotes(str); + v->v.str = str; + p->te++; + match(TOK_STRING_ESCAPED); + break; + + default: + log_error("Parse error at byte %" PRIptrdiff_t " (line %d): expected a value", + p->tb - p->fb + 1, p->line); + return NULL; + } + return v; +} + +static int _match_aux(struct parser *p, int t) +{ + if (p->t != t) + return 0; + + _get_token(p, t); + return 1; +} + +/* + * tokeniser + */ +static void _get_token(struct parser *p, int tok_prev) +{ + int values_allowed = 0; + + const char *te; + + p->tb = p->te; + _eat_space(p); + if (p->tb == p->fe || !*p->tb) { + p->t = TOK_EOF; + return; + } + + /* Should next token be interpreted as value instead of identifier? */ + if (tok_prev == TOK_EQ || tok_prev == TOK_ARRAY_B || + tok_prev == TOK_COMMA) + values_allowed = 1; + + p->t = TOK_INT; /* fudge so the fall through for + floats works */ + + te = p->te; + switch (*te) { + case SECTION_B_CHAR: + p->t = TOK_SECTION_B; + te++; + break; + + case SECTION_E_CHAR: + p->t = TOK_SECTION_E; + te++; + break; + + case '[': + p->t = TOK_ARRAY_B; + te++; + break; + + case ']': + p->t = TOK_ARRAY_E; + te++; + break; + + case ',': + p->t = TOK_COMMA; + te++; + break; + + case '=': + p->t = TOK_EQ; + te++; + break; + + case '"': + p->t = TOK_STRING_ESCAPED; + te++; + while ((te != p->fe) && (*te) && (*te != '"')) { + if ((*te == '\\') && (te + 1 != p->fe) && + *(te + 1)) + te++; + te++; + } + + if ((te != p->fe) && (*te)) + te++; + break; + + case '\'': + p->t = TOK_STRING; + te++; + while ((te != p->fe) && (*te) && (*te != '\'')) + te++; + + if ((te != p->fe) && (*te)) + te++; + break; + + case '.': + p->t = TOK_FLOAT; + /* Fall through */ + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '+': + case '-': + if (values_allowed) { + while (++te != p->fe) { + if (!isdigit((int) *te)) { + if (*te == '.') { + if (p->t != TOK_FLOAT) { + p->t = TOK_FLOAT; + continue; + } + } + break; + } + } + break; + } + /* fall through */ + + default: + p->t = TOK_IDENTIFIER; + while ((te != p->fe) && (*te) && !isspace(*te) && + (*te != '#') && (*te != '=') && + (*te != SECTION_B_CHAR) && + (*te != SECTION_E_CHAR)) + te++; + break; + } + + p->te = te; +} + +static void _eat_space(struct parser *p) +{ + while (p->tb != p->fe) { + if (*p->te == '#') + while ((p->te != p->fe) && (*p->te != '\n') && (*p->te)) + ++p->te; + + else if (!isspace(*p->te)) + break; + + while ((p->te != p->fe) && isspace(*p->te)) { + if (*p->te == '\n') + ++p->line; + ++p->te; + } + + p->tb = p->te; + } +} + +/* + * memory management + */ +static struct dm_config_value *_create_value(struct dm_pool *mem) +{ + return dm_pool_zalloc(mem, sizeof(struct dm_config_value)); +} + +static struct dm_config_node *_create_node(struct dm_pool *mem) +{ + return dm_pool_zalloc(mem, sizeof(struct dm_config_node)); +} + +static char *_dup_tok(struct parser *p) +{ + size_t len = p->te - p->tb; + char *str = dm_pool_alloc(p->mem, len + 1); + if (!str) { + log_error("Failed to duplicate token."); + return 0; + } + memcpy(str, p->tb, len); + str[len] = '\0'; + return str; +} + +/* + * Utility functions + */ + +/* + * node_lookup_fn is either: + * _find_config_node to perform a lookup starting from a given config_node + * in a config_tree; + * or + * _find_first_config_node to find the first config_node in a set of + * cascaded trees. + */ +typedef const struct dm_config_node *node_lookup_fn(const void *start, const char *path); + +static const struct dm_config_node *_find_config_node(const void *start, + const char *path) +{ + const char *e; + const struct dm_config_node *cn = start; + const struct dm_config_node *cn_found = NULL; + + while (cn) { + /* trim any leading slashes */ + while (*path && (*path == sep)) + path++; + + /* find the end of this segment */ + for (e = path; *e && (*e != sep); e++) ; + + /* hunt for the node */ + cn_found = NULL; + while (cn) { + if (_tok_match(cn->key, path, e)) { + /* Inefficient */ + if (!cn_found) + cn_found = cn; + else + log_warn("WARNING: Ignoring duplicate" + " config node: %s (" + "seeking %s)", cn->key, path); + } + + cn = cn->sib; + } + + if (cn_found && *e) + cn = cn_found->child; + else + break; /* don't move into the last node */ + + path = e; + } + + return cn_found; +} + +static const struct dm_config_node *_find_first_config_node(const void *start, const char *path) +{ + const struct dm_config_tree *cft = start; + const struct dm_config_node *cn = NULL; + + while (cft) { + if ((cn = _find_config_node(cft->root, path))) + return cn; + cft = cft->cascade; + } + + return NULL; +} + +static const char *_find_config_str(const void *start, node_lookup_fn find_fn, + const char *path, const char *fail, int allow_empty) +{ + const struct dm_config_node *n = find_fn(start, path); + + /* Empty strings are ignored if allow_empty is set */ + if (n && n->v) { + if ((n->v->type == DM_CFG_STRING) && + (allow_empty || (*n->v->v.str))) { + log_very_verbose("Setting %s to %s", path, n->v->v.str); + return n->v->v.str; + } + if ((n->v->type != DM_CFG_STRING) || (!allow_empty && fail)) + log_warn("WARNING: Ignoring unsupported value for %s.", path); + } + + if (fail) + log_very_verbose("%s not found in config: defaulting to %s", + path, fail); + return fail; +} + +const char *dm_config_find_str(const struct dm_config_node *cn, + const char *path, const char *fail) +{ + return _find_config_str(cn, _find_config_node, path, fail, 0); +} + +const char *dm_config_find_str_allow_empty(const struct dm_config_node *cn, + const char *path, const char *fail) +{ + return _find_config_str(cn, _find_config_node, path, fail, 1); +} + +static int64_t _find_config_int64(const void *start, node_lookup_fn find, + const char *path, int64_t fail) +{ + const struct dm_config_node *n = find(start, path); + + if (n && n->v && n->v->type == DM_CFG_INT) { + log_very_verbose("Setting %s to %" PRId64, path, n->v->v.i); + return n->v->v.i; + } + + log_very_verbose("%s not found in config: defaulting to %" PRId64, + path, fail); + return fail; +} + +static float _find_config_float(const void *start, node_lookup_fn find, + const char *path, float fail) +{ + const struct dm_config_node *n = find(start, path); + + if (n && n->v && n->v->type == DM_CFG_FLOAT) { + log_very_verbose("Setting %s to %f", path, n->v->v.f); + return n->v->v.f; + } + + log_very_verbose("%s not found in config: defaulting to %f", + path, fail); + + return fail; + +} + +static int _str_in_array(const char *str, const char * const values[]) +{ + int i; + + for (i = 0; values[i]; i++) + if (!strcasecmp(str, values[i])) + return 1; + + return 0; +} + +static int _str_to_bool(const char *str, int fail) +{ + const char * const _true_values[] = { "y", "yes", "on", "true", NULL }; + const char * const _false_values[] = { "n", "no", "off", "false", NULL }; + + if (_str_in_array(str, _true_values)) + return 1; + + if (_str_in_array(str, _false_values)) + return 0; + + return fail; +} + +static int _find_config_bool(const void *start, node_lookup_fn find, + const char *path, int fail) +{ + const struct dm_config_node *n = find(start, path); + const struct dm_config_value *v; + int b; + + if (n) { + v = n->v; + + switch (v->type) { + case DM_CFG_INT: + b = v->v.i ? 1 : 0; + log_very_verbose("Setting %s to %d", path, b); + return b; + + case DM_CFG_STRING: + b = _str_to_bool(v->v.str, fail); + log_very_verbose("Setting %s to %d", path, b); + return b; + default: + ; + } + } + + log_very_verbose("%s not found in config: defaulting to %d", + path, fail); + + return fail; +} + +/*********************************** + * node-based lookup + **/ + +struct dm_config_node *dm_config_find_node(const struct dm_config_node *cn, + const char *path) +{ + return (struct dm_config_node *) _find_config_node(cn, path); +} + +int dm_config_find_int(const struct dm_config_node *cn, const char *path, int fail) +{ + /* FIXME Add log_error message on overflow */ + return (int) _find_config_int64(cn, _find_config_node, path, (int64_t) fail); +} + +int64_t dm_config_find_int64(const struct dm_config_node *cn, const char *path, int64_t fail) +{ + return _find_config_int64(cn, _find_config_node, path, fail); +} + +float dm_config_find_float(const struct dm_config_node *cn, const char *path, + float fail) +{ + return _find_config_float(cn, _find_config_node, path, fail); +} + +int dm_config_find_bool(const struct dm_config_node *cn, const char *path, int fail) +{ + return _find_config_bool(cn, _find_config_node, path, fail); +} + +/*********************************** + * tree-based lookup + **/ + +const struct dm_config_node *dm_config_tree_find_node(const struct dm_config_tree *cft, + const char *path) +{ + return _find_first_config_node(cft, path); +} + +const char *dm_config_tree_find_str(const struct dm_config_tree *cft, const char *path, + const char *fail) +{ + return _find_config_str(cft, _find_first_config_node, path, fail, 0); +} + +const char *dm_config_tree_find_str_allow_empty(const struct dm_config_tree *cft, const char *path, + const char *fail) +{ + return _find_config_str(cft, _find_first_config_node, path, fail, 1); +} + +int dm_config_tree_find_int(const struct dm_config_tree *cft, const char *path, int fail) +{ + /* FIXME Add log_error message on overflow */ + return (int) _find_config_int64(cft, _find_first_config_node, path, (int64_t) fail); +} + +int64_t dm_config_tree_find_int64(const struct dm_config_tree *cft, const char *path, int64_t fail) +{ + return _find_config_int64(cft, _find_first_config_node, path, fail); +} + +float dm_config_tree_find_float(const struct dm_config_tree *cft, const char *path, + float fail) +{ + return _find_config_float(cft, _find_first_config_node, path, fail); +} + +int dm_config_tree_find_bool(const struct dm_config_tree *cft, const char *path, int fail) +{ + return _find_config_bool(cft, _find_first_config_node, path, fail); +} + +/************************************/ + + +int dm_config_get_uint32(const struct dm_config_node *cn, const char *path, + uint32_t *result) +{ + const struct dm_config_node *n; + + n = _find_config_node(cn, path); + + if (!n || !n->v || n->v->type != DM_CFG_INT) + return 0; + + if (result) + *result = n->v->v.i; + return 1; +} + +int dm_config_get_uint64(const struct dm_config_node *cn, const char *path, + uint64_t *result) +{ + const struct dm_config_node *n; + + n = _find_config_node(cn, path); + + if (!n || !n->v || n->v->type != DM_CFG_INT) + return 0; + + if (result) + *result = (uint64_t) n->v->v.i; + return 1; +} + +int dm_config_get_str(const struct dm_config_node *cn, const char *path, + const char **result) +{ + const struct dm_config_node *n; + + n = _find_config_node(cn, path); + + if (!n || !n->v || n->v->type != DM_CFG_STRING) + return 0; + + if (result) + *result = n->v->v.str; + return 1; +} + +int dm_config_get_list(const struct dm_config_node *cn, const char *path, + const struct dm_config_value **result) +{ + const struct dm_config_node *n; + + n = _find_config_node(cn, path); + /* TODO when we represent single-item lists consistently, add a check + * for n->v->next != NULL */ + if (!n || !n->v) + return 0; + + if (result) + *result = n->v; + return 1; +} + +int dm_config_get_section(const struct dm_config_node *cn, const char *path, + const struct dm_config_node **result) +{ + const struct dm_config_node *n; + + n = _find_config_node(cn, path); + if (!n || n->v) + return 0; + + if (result) + *result = n; + return 1; +} + +int dm_config_has_node(const struct dm_config_node *cn, const char *path) +{ + return _find_config_node(cn, path) ? 1 : 0; +} + +/* + * Convert a token type to the char it represents. + */ +static char _token_type_to_char(int type) +{ + switch (type) { + case TOK_SECTION_B: + return SECTION_B_CHAR; + case TOK_SECTION_E: + return SECTION_E_CHAR; + default: + return 0; + } +} + +/* + * Returns: + * # of 'type' tokens in 'str'. + */ +static unsigned _count_tokens(const char *str, unsigned len, int type) +{ + char c; + + c = _token_type_to_char(type); + + return dm_count_chars(str, len, c); +} + +const char *dm_config_parent_name(const struct dm_config_node *n) +{ + return (n->parent ? n->parent->key : "(root)"); +} +/* + * Heuristic function to make a quick guess as to whether a text + * region probably contains a valid config "section". (Useful for + * scanning areas of the disk for old metadata.) + * Config sections contain various tokens, may contain other sections + * and strings, and are delimited by begin (type 'TOK_SECTION_B') and + * end (type 'TOK_SECTION_E') tokens. As a quick heuristic, we just + * count the number of begin and end tokens, and see if they are + * non-zero and the counts match. + * Full validation of the section should be done with another function + * (for example, read_config_fd). + * + * Returns: + * 0 - probably is not a valid config section + * 1 - probably _is_ a valid config section + */ +unsigned dm_config_maybe_section(const char *str, unsigned len) +{ + int begin_count; + int end_count; + + begin_count = _count_tokens(str, len, TOK_SECTION_B); + end_count = _count_tokens(str, len, TOK_SECTION_E); + + if (begin_count && end_count && (begin_count == end_count)) + return 1; + else + return 0; +} + +__attribute__((nonnull(1, 2))) +static struct dm_config_value *_clone_config_value(struct dm_pool *mem, + const struct dm_config_value *v) +{ + struct dm_config_value *new_cv; + + if (!(new_cv = _create_value(mem))) { + log_error("Failed to clone config value."); + return NULL; + } + + new_cv->type = v->type; + if (v->type == DM_CFG_STRING) { + if (!(new_cv->v.str = dm_pool_strdup(mem, v->v.str))) { + log_error("Failed to clone config string value."); + return NULL; + } + } else + new_cv->v = v->v; + + if (v->next && !(new_cv->next = _clone_config_value(mem, v->next))) + return_NULL; + + return new_cv; +} + +struct dm_config_node *dm_config_clone_node_with_mem(struct dm_pool *mem, const struct dm_config_node *cn, int siblings) +{ + struct dm_config_node *new_cn; + + if (!cn) { + log_error("Cannot clone NULL config node."); + return NULL; + } + + if (!(new_cn = _create_node(mem))) { + log_error("Failed to clone config node."); + return NULL; + } + + if ((cn->key && !(new_cn->key = dm_pool_strdup(mem, cn->key)))) { + log_error("Failed to clone config node key."); + return NULL; + } + + if ((cn->v && !(new_cn->v = _clone_config_value(mem, cn->v))) || + (cn->child && !(new_cn->child = dm_config_clone_node_with_mem(mem, cn->child, 1))) || + (siblings && cn->sib && !(new_cn->sib = dm_config_clone_node_with_mem(mem, cn->sib, siblings)))) + return_NULL; /* 'new_cn' released with mem pool */ + + return new_cn; +} + +struct dm_config_node *dm_config_clone_node(struct dm_config_tree *cft, const struct dm_config_node *node, int sib) +{ + return dm_config_clone_node_with_mem(cft->mem, node, sib); +} + +struct dm_config_node *dm_config_create_node(struct dm_config_tree *cft, const char *key) +{ + struct dm_config_node *cn; + + if (!(cn = _create_node(cft->mem))) { + log_error("Failed to create config node."); + return NULL; + } + if (!(cn->key = dm_pool_strdup(cft->mem, key))) { + log_error("Failed to create config node's key."); + return NULL; + } + cn->parent = NULL; + cn->v = NULL; + + return cn; +} + +struct dm_config_value *dm_config_create_value(struct dm_config_tree *cft) +{ + return _create_value(cft->mem); +} + +struct dm_pool *dm_config_memory(struct dm_config_tree *cft) +{ + return cft->mem; +} diff --git a/libdm/libdm-deptree.c b/libdm/libdm-deptree.c index 8d00514..096eba2 100644 --- a/libdm/libdm-deptree.c +++ b/libdm/libdm-deptree.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Red Hat, Inc. All rights reserved. + * Copyright (C) 2005-2012 Red Hat, Inc. All rights reserved. * * This file is part of the device-mapper userspace tools. * @@ -16,7 +16,6 @@ #include "libdm-targets.h" #include "libdm-common.h" #include "kdev_t.h" -#include "dm-ioctl.h" #include <stdarg.h> #include <sys/param.h> @@ -24,9 +23,6 @@ #define MAX_TARGET_PARAMSIZE 500000 -/* FIXME Fix interface so this is used only by LVM */ -#define UUID_PREFIX "LVM-" - #define REPLICATOR_LOCAL_SITE 0 /* Supported segment types */ @@ -42,6 +38,19 @@ enum { SEG_SNAPSHOT_MERGE, SEG_STRIPED, SEG_ZERO, + SEG_THIN_POOL, + SEG_THIN, + SEG_RAID1, + SEG_RAID10, + SEG_RAID4, + SEG_RAID5_LA, + SEG_RAID5_RA, + SEG_RAID5_LS, + SEG_RAID5_RS, + SEG_RAID6_ZR, + SEG_RAID6_NR, + SEG_RAID6_NC, + SEG_LAST, }; /* FIXME Add crypt and multipath support */ @@ -61,6 +70,26 @@ struct { { SEG_SNAPSHOT_MERGE, "snapshot-merge" }, { SEG_STRIPED, "striped" }, { SEG_ZERO, "zero"}, + { SEG_THIN_POOL, "thin-pool"}, + { SEG_THIN, "thin"}, + { SEG_RAID1, "raid1"}, + { SEG_RAID10, "raid10"}, + { SEG_RAID4, "raid4"}, + { SEG_RAID5_LA, "raid5_la"}, + { SEG_RAID5_RA, "raid5_ra"}, + { SEG_RAID5_LS, "raid5_ls"}, + { SEG_RAID5_RS, "raid5_rs"}, + { SEG_RAID6_ZR, "raid6_zr"}, + { SEG_RAID6_NR, "raid6_nr"}, + { SEG_RAID6_NC, "raid6_nc"}, + + /* + *WARNING: Since 'raid' target overloads this 1:1 mapping table + * for search do not add new enum elements past them! + */ + { SEG_RAID5_LS, "raid5"}, /* same as "raid5_ls" (default for MD also) */ + { SEG_RAID6_ZR, "raid6"}, /* same as "raid6_zr" */ + { SEG_LAST, NULL }, }; /* Some segment types have a list of areas of other devices attached */ @@ -77,6 +106,32 @@ struct seg_area { uint32_t flags; /* Replicator sync log flags */ }; +struct dm_thin_message { + dm_thin_message_t type; + union { + struct { + uint32_t device_id; + uint32_t origin_id; + } m_create_snap; + struct { + uint32_t device_id; + } m_create_thin; + struct { + uint32_t device_id; + } m_delete; + struct { + uint64_t current_id; + uint64_t new_id; + } m_set_transaction_id; + } u; +}; + +struct thin_message { + struct dm_list list; + struct dm_thin_message message; + int expected_errno; +}; + /* Replicator-log has a list of sites */ /* FIXME: maybe move to seg_area too? */ struct replicator_site { @@ -100,7 +155,7 @@ struct load_segment { unsigned area_count; /* Linear + Striped + Mirrored + Crypt + Replicator */ struct dm_list areas; /* Linear + Striped + Mirrored + Crypt + Replicator */ - uint32_t stripe_size; /* Striped */ + uint32_t stripe_size; /* Striped + raid */ int persistent; /* Snapshot */ uint32_t chunk_size; /* Snapshot */ @@ -109,7 +164,7 @@ struct load_segment { struct dm_tree_node *merge; /* Snapshot */ struct dm_tree_node *log; /* Mirror + Replicator */ - uint32_t region_size; /* Mirror */ + uint32_t region_size; /* Mirror + raid */ unsigned clustered; /* Mirror */ unsigned mirror_area_count; /* Mirror */ uint32_t flags; /* Mirror log */ @@ -127,6 +182,21 @@ struct load_segment { unsigned rdevice_count; /* Replicator */ struct dm_tree_node *replicator;/* Replicator-dev */ uint64_t rdevice_index; /* Replicator-dev */ + + uint64_t rebuilds; /* raid */ + + struct dm_tree_node *metadata; /* Thin_pool */ + struct dm_tree_node *pool; /* Thin_pool, Thin */ + struct dm_tree_node *external; /* Thin */ + struct dm_list thin_messages; /* Thin_pool */ + uint64_t transaction_id; /* Thin_pool */ + uint64_t low_water_mark; /* Thin_pool */ + uint32_t data_block_size; /* Thin_pool */ + unsigned skip_block_zeroing; /* Thin_pool */ + unsigned ignore_discard; /* Thin_pool target vsn 1.1 */ + unsigned no_discard_passdown; /* Thin_pool target vsn 1.1 */ + uint32_t device_id; /* Thin */ + }; /* Per-device properties */ @@ -149,7 +219,19 @@ struct load_properties { * and processing of dm tree). This will also flush all stacked dev * node operations, synchronizing with udev. */ - int immediate_dev_node; + unsigned immediate_dev_node; + + /* + * If the device size changed from zero and this is set, + * don't resume the device immediately, even if the device + * has parents. This works provided the parents do not + * validate the device size and is required by pvmove to + * avoid starting the mirror resync operation too early. + */ + unsigned delay_resume_if_new; + + /* Send messages for this node in preload */ + unsigned send_messages; }; /* Two of these used to join two nodes with uses and used_by. */ @@ -161,12 +243,12 @@ struct dm_tree_link { struct dm_tree_node { struct dm_tree *dtree; - const char *name; - const char *uuid; - struct dm_info info; + const char *name; + const char *uuid; + struct dm_info info; - struct dm_list uses; /* Nodes this node uses */ - struct dm_list used_by; /* Nodes that use this node */ + struct dm_list uses; /* Nodes this node uses */ + struct dm_list used_by; /* Nodes that use this node */ int activation_priority; /* 0 gets activated first */ @@ -181,6 +263,10 @@ struct dm_tree_node { * Note: only direct child is allowed */ struct dm_tree_node *presuspend_node; + + /* Callback */ + dm_node_callback_fn callback; + void *callback_data; }; struct dm_tree { @@ -189,16 +275,24 @@ struct dm_tree { struct dm_hash_table *uuids; struct dm_tree_node root; int skip_lockfs; /* 1 skips lockfs (for non-snapshots) */ - int no_flush; /* 1 sets noflush (mirrors/multipath) */ + int no_flush; /* 1 sets noflush (mirrors/multipath) */ + int retry_remove; /* 1 retries remove if not successful */ uint32_t cookie; }; +/* + * Tree functions. + */ struct dm_tree *dm_tree_create(void) { + struct dm_pool *dmem; struct dm_tree *dtree; - if (!(dtree = dm_zalloc(sizeof(*dtree)))) { - log_error("dm_tree_create malloc failed"); + if (!(dmem = dm_pool_create("dtree", 1024)) || + !(dtree = dm_pool_zalloc(dmem, sizeof(*dtree)))) { + log_error("Failed to allocate dtree."); + if (dmem) + dm_pool_destroy(dmem); return NULL; } @@ -207,17 +301,11 @@ struct dm_tree *dm_tree_create(void) dm_list_init(&dtree->root.used_by); dtree->skip_lockfs = 0; dtree->no_flush = 0; - - if (!(dtree->mem = dm_pool_create("dtree", 1024))) { - log_error("dtree pool creation failed"); - dm_free(dtree); - return NULL; - } + dtree->mem = dmem; if (!(dtree->devs = dm_hash_create(8))) { log_error("dtree hash creation failed"); dm_pool_destroy(dtree->mem); - dm_free(dtree); return NULL; } @@ -225,7 +313,6 @@ struct dm_tree *dm_tree_create(void) log_error("dtree uuid hash creation failed"); dm_hash_destroy(dtree->devs); dm_pool_destroy(dtree->mem); - dm_free(dtree); return NULL; } @@ -240,9 +327,36 @@ void dm_tree_free(struct dm_tree *dtree) dm_hash_destroy(dtree->uuids); dm_hash_destroy(dtree->devs); dm_pool_destroy(dtree->mem); - dm_free(dtree); } +void dm_tree_set_cookie(struct dm_tree_node *node, uint32_t cookie) +{ + node->dtree->cookie = cookie; +} + +uint32_t dm_tree_get_cookie(struct dm_tree_node *node) +{ + return node->dtree->cookie; +} + +void dm_tree_skip_lockfs(struct dm_tree_node *dnode) +{ + dnode->dtree->skip_lockfs = 1; +} + +void dm_tree_use_no_flush_suspend(struct dm_tree_node *dnode) +{ + dnode->dtree->no_flush = 1; +} + +void dm_tree_retry_remove(struct dm_tree_node *dnode) +{ + dnode->dtree->retry_remove = 1; +} + +/* + * Node functions. + */ static int _nodes_are_linked(const struct dm_tree_node *parent, const struct dm_tree_node *child) { @@ -329,13 +443,13 @@ static void _remove_from_bottomlevel(struct dm_tree_node *node) static int _link_tree_nodes(struct dm_tree_node *parent, struct dm_tree_node *child) { /* Don't link to root node if child already has a parent */ - if ((parent == &parent->dtree->root)) { + if (parent == &parent->dtree->root) { if (dm_tree_node_num_children(child, 1)) return 1; } else _remove_from_toplevel(child); - if ((child == &child->dtree->root)) { + if (child == &child->dtree->root) { if (dm_tree_node_num_children(parent, 0)) return 1; } else @@ -352,7 +466,7 @@ static struct dm_tree_node *_create_dm_tree_node(struct dm_tree *dtree, uint16_t udev_flags) { struct dm_tree_node *node; - uint64_t dev; + dev_t dev; if (!(node = dm_pool_zalloc(dtree->mem, sizeof(*node)))) { log_error("_create_dm_tree_node alloc failed"); @@ -372,7 +486,7 @@ static struct dm_tree_node *_create_dm_tree_node(struct dm_tree *dtree, dm_list_init(&node->used_by); dm_list_init(&node->props.segs); - dev = MKDEV(info->major, info->minor); + dev = MKDEV((dev_t)info->major, info->minor); if (!dm_hash_insert_binary(dtree->devs, (const char *) &dev, sizeof(dev), node)) { @@ -396,7 +510,7 @@ static struct dm_tree_node *_create_dm_tree_node(struct dm_tree *dtree, static struct dm_tree_node *_find_dm_tree_node(struct dm_tree *dtree, uint32_t major, uint32_t minor) { - uint64_t dev = MKDEV(major, minor); + dev_t dev = MKDEV((dev_t)major, minor); return dm_hash_lookup_binary(dtree->devs, (const char *) &dev, sizeof(dev)); @@ -406,287 +520,34 @@ static struct dm_tree_node *_find_dm_tree_node_by_uuid(struct dm_tree *dtree, const char *uuid) { struct dm_tree_node *node; + const char *default_uuid_prefix; + size_t default_uuid_prefix_len; if ((node = dm_hash_lookup(dtree->uuids, uuid))) return node; - if (strncmp(uuid, UUID_PREFIX, sizeof(UUID_PREFIX) - 1)) - return NULL; - - return dm_hash_lookup(dtree->uuids, uuid + sizeof(UUID_PREFIX) - 1); -} + default_uuid_prefix = dm_uuid_prefix(); + default_uuid_prefix_len = strlen(default_uuid_prefix); -static int _deps(struct dm_task **dmt, struct dm_pool *mem, uint32_t major, uint32_t minor, - const char **name, const char **uuid, - struct dm_info *info, struct dm_deps **deps) -{ - memset(info, 0, sizeof(*info)); - - if (!dm_is_dm_major(major)) { - *name = ""; - *uuid = ""; - *deps = NULL; - info->major = major; - info->minor = minor; - info->exists = 0; - info->live_table = 0; - info->inactive_table = 0; - info->read_only = 0; - return 1; - } - - if (!(*dmt = dm_task_create(DM_DEVICE_DEPS))) { - log_error("deps dm_task creation failed"); - return 0; - } - - if (!dm_task_set_major(*dmt, major)) { - log_error("_deps: failed to set major for (%" PRIu32 ":%" PRIu32 ")", - major, minor); - goto failed; - } - - if (!dm_task_set_minor(*dmt, minor)) { - log_error("_deps: failed to set minor for (%" PRIu32 ":%" PRIu32 ")", - major, minor); - goto failed; - } - - if (!dm_task_run(*dmt)) { - log_error("_deps: task run failed for (%" PRIu32 ":%" PRIu32 ")", - major, minor); - goto failed; - } - - if (!dm_task_get_info(*dmt, info)) { - log_error("_deps: failed to get info for (%" PRIu32 ":%" PRIu32 ")", - major, minor); - goto failed; - } - - if (!info->exists) { - *name = ""; - *uuid = ""; - *deps = NULL; - } else { - if (info->major != major) { - log_error("Inconsistent dtree major number: %u != %u", - major, info->major); - goto failed; - } - if (info->minor != minor) { - log_error("Inconsistent dtree minor number: %u != %u", - minor, info->minor); - goto failed; - } - if (!(*name = dm_pool_strdup(mem, dm_task_get_name(*dmt)))) { - log_error("name pool_strdup failed"); - goto failed; - } - if (!(*uuid = dm_pool_strdup(mem, dm_task_get_uuid(*dmt)))) { - log_error("uuid pool_strdup failed"); - goto failed; - } - *deps = dm_task_get_deps(*dmt); - } - - return 1; + if (strncmp(uuid, default_uuid_prefix, default_uuid_prefix_len)) + return NULL; -failed: - dm_task_destroy(*dmt); - return 0; + return dm_hash_lookup(dtree->uuids, uuid + default_uuid_prefix_len); } -static struct dm_tree_node *_add_dev(struct dm_tree *dtree, - struct dm_tree_node *parent, - uint32_t major, uint32_t minor, - uint16_t udev_flags) -{ - struct dm_task *dmt = NULL; - struct dm_info info; - struct dm_deps *deps = NULL; - const char *name = NULL; - const char *uuid = NULL; - struct dm_tree_node *node = NULL; - uint32_t i; - int new = 0; - - /* Already in tree? */ - if (!(node = _find_dm_tree_node(dtree, major, minor))) { - if (!_deps(&dmt, dtree->mem, major, minor, &name, &uuid, &info, &deps)) - return_NULL; - - if (!(node = _create_dm_tree_node(dtree, name, uuid, &info, - NULL, udev_flags))) - goto_out; - new = 1; - } - - if (!_link_tree_nodes(parent, node)) { - node = NULL; - goto_out; - } - - /* If node was already in tree, no need to recurse. */ - if (!new) - goto out; +void dm_tree_node_set_udev_flags(struct dm_tree_node *dnode, uint16_t udev_flags) - /* Can't recurse if not a mapped device or there are no dependencies */ - if (!node->info.exists || !deps->count) { - if (!_add_to_bottomlevel(node)) { - stack; - node = NULL; - } - goto out; - } - - /* Add dependencies to tree */ - for (i = 0; i < deps->count; i++) - if (!_add_dev(dtree, node, MAJOR(deps->device[i]), - MINOR(deps->device[i]), udev_flags)) { - node = NULL; - goto_out; - } - -out: - if (dmt) - dm_task_destroy(dmt); - - return node; -} - -static int _node_clear_table(struct dm_tree_node *dnode) { - struct dm_task *dmt; - struct dm_info *info; - const char *name; - int r; + struct dm_info *dinfo = &dnode->info; - if (!(info = &dnode->info)) { - log_error("_node_clear_table failed: missing info"); - return 0; - } - - if (!(name = dm_tree_node_get_name(dnode))) { - log_error("_node_clear_table failed: missing name"); - return 0; - } - - /* Is there a table? */ - if (!info->exists || !info->inactive_table) - return 1; - - log_verbose("Clearing inactive table %s (%" PRIu32 ":%" PRIu32 ")", - name, info->major, info->minor); - - if (!(dmt = dm_task_create(DM_DEVICE_CLEAR))) { - log_error("Table clear dm_task creation failed for %s", name); - return 0; - } - - if (!dm_task_set_major(dmt, info->major) || - !dm_task_set_minor(dmt, info->minor)) { - log_error("Failed to set device number for %s table clear", name); - dm_task_destroy(dmt); - return 0; - } - - r = dm_task_run(dmt); - - if (!dm_task_get_info(dmt, info)) { - log_error("_node_clear_table failed: info missing after running task for %s", name); - r = 0; - } - - dm_task_destroy(dmt); - - return r; + if (udev_flags != dnode->udev_flags) + log_debug("Resetting %s (%" PRIu32 ":%" PRIu32 + ") udev_flags from 0x%x to 0x%x", + dnode->name, dinfo->major, dinfo->minor, + dnode->udev_flags, udev_flags); + dnode->udev_flags = udev_flags; } -struct dm_tree_node *dm_tree_add_new_dev(struct dm_tree *dtree, - const char *name, - const char *uuid, - uint32_t major, uint32_t minor, - int read_only, - int clear_inactive, - void *context) -{ - struct dm_tree_node *dnode; - struct dm_info info; - const char *name2; - const char *uuid2; - - /* Do we need to add node to tree? */ - if (!(dnode = dm_tree_find_node_by_uuid(dtree, uuid))) { - if (!(name2 = dm_pool_strdup(dtree->mem, name))) { - log_error("name pool_strdup failed"); - return NULL; - } - if (!(uuid2 = dm_pool_strdup(dtree->mem, uuid))) { - log_error("uuid pool_strdup failed"); - return NULL; - } - - info.major = 0; - info.minor = 0; - info.exists = 0; - info.live_table = 0; - info.inactive_table = 0; - info.read_only = 0; - - if (!(dnode = _create_dm_tree_node(dtree, name2, uuid2, &info, - context, 0))) - return_NULL; - - /* Attach to root node until a table is supplied */ - if (!_add_to_toplevel(dnode) || !_add_to_bottomlevel(dnode)) - return_NULL; - - dnode->props.major = major; - dnode->props.minor = minor; - dnode->props.new_name = NULL; - dnode->props.size_changed = 0; - } else if (strcmp(name, dnode->name)) { - /* Do we need to rename node? */ - if (!(dnode->props.new_name = dm_pool_strdup(dtree->mem, name))) { - log_error("name pool_strdup failed"); - return 0; - } - } - - dnode->props.read_only = read_only ? 1 : 0; - dnode->props.read_ahead = DM_READ_AHEAD_AUTO; - dnode->props.read_ahead_flags = 0; - - if (clear_inactive && !_node_clear_table(dnode)) - return_NULL; - - dnode->context = context; - dnode->udev_flags = 0; - - return dnode; -} - -struct dm_tree_node *dm_tree_add_new_dev_with_udev_flags(struct dm_tree *dtree, - const char *name, - const char *uuid, - uint32_t major, - uint32_t minor, - int read_only, - int clear_inactive, - void *context, - uint16_t udev_flags) -{ - struct dm_tree_node *node; - - if ((node = dm_tree_add_new_dev(dtree, name, uuid, major, minor, read_only, - clear_inactive, context))) - node->udev_flags = udev_flags; - - return node; -} - - void dm_tree_node_set_read_ahead(struct dm_tree_node *dnode, uint32_t read_ahead, uint32_t read_ahead_flags) @@ -701,17 +562,6 @@ void dm_tree_node_set_presuspend_node(struct dm_tree_node *node, node->presuspend_node = presuspend_node; } -int dm_tree_add_dev(struct dm_tree *dtree, uint32_t major, uint32_t minor) -{ - return _add_dev(dtree, &dtree->root, major, minor, 0) ? 1 : 0; -} - -int dm_tree_add_dev_with_udev_flags(struct dm_tree *dtree, uint32_t major, - uint32_t minor, uint16_t udev_flags) -{ - return _add_dev(dtree, &dtree->root, major, minor, udev_flags) ? 1 : 0; -} - const char *dm_tree_node_get_name(const struct dm_tree_node *node) { return node->info.exists ? node->name : ""; @@ -756,6 +606,9 @@ int dm_tree_node_num_children(const struct dm_tree_node *node, uint32_t inverted */ static int _uuid_prefix_matches(const char *uuid, const char *uuid_prefix, size_t uuid_prefix_len) { + const char *default_uuid_prefix = dm_uuid_prefix(); + size_t default_uuid_prefix_len = strlen(default_uuid_prefix); + if (!uuid_prefix) return 1; @@ -766,13 +619,13 @@ static int _uuid_prefix_matches(const char *uuid, const char *uuid_prefix, size_ if (uuid_prefix_len <= 4) return 0; - if (!strncmp(uuid, UUID_PREFIX, sizeof(UUID_PREFIX) - 1)) + if (!strncmp(uuid, default_uuid_prefix, default_uuid_prefix_len)) return 0; - if (strncmp(uuid_prefix, UUID_PREFIX, sizeof(UUID_PREFIX) - 1)) + if (strncmp(uuid_prefix, default_uuid_prefix, default_uuid_prefix_len)) return 0; - if (!strncmp(uuid, uuid_prefix + sizeof(UUID_PREFIX) - 1, uuid_prefix_len - (sizeof(UUID_PREFIX) - 1))) + if (!strncmp(uuid, uuid_prefix + default_uuid_prefix_len, uuid_prefix_len - default_uuid_prefix_len)) return 1; return 0; @@ -876,11 +729,99 @@ struct dm_tree_node *dm_tree_next_child(void **handle, return (*dlink) ? dm_list_item(*dlink, struct dm_tree_link)->node : NULL; } +static int _deps(struct dm_task **dmt, struct dm_pool *mem, uint32_t major, uint32_t minor, + const char **name, const char **uuid, unsigned inactive_table, + struct dm_info *info, struct dm_deps **deps) +{ + memset(info, 0, sizeof(*info)); + + if (!dm_is_dm_major(major)) { + if (name) + *name = ""; + if (uuid) + *uuid = ""; + *deps = NULL; + info->major = major; + info->minor = minor; + return 1; + } + + if (!(*dmt = dm_task_create(DM_DEVICE_DEPS))) { + log_error("deps dm_task creation failed"); + return 0; + } + + if (!dm_task_set_major(*dmt, major)) { + log_error("_deps: failed to set major for (%" PRIu32 ":%" PRIu32 ")", + major, minor); + goto failed; + } + + if (!dm_task_set_minor(*dmt, minor)) { + log_error("_deps: failed to set minor for (%" PRIu32 ":%" PRIu32 ")", + major, minor); + goto failed; + } + + if (inactive_table && !dm_task_query_inactive_table(*dmt)) { + log_error("_deps: failed to set inactive table for (%" PRIu32 ":%" PRIu32 ")", + major, minor); + goto failed; + } + + if (!dm_task_run(*dmt)) { + log_error("_deps: task run failed for (%" PRIu32 ":%" PRIu32 ")", + major, minor); + goto failed; + } + + if (!dm_task_get_info(*dmt, info)) { + log_error("_deps: failed to get info for (%" PRIu32 ":%" PRIu32 ")", + major, minor); + goto failed; + } + + if (!info->exists) { + if (name) + *name = ""; + if (uuid) + *uuid = ""; + *deps = NULL; + } else { + if (info->major != major) { + log_error("Inconsistent dtree major number: %u != %u", + major, info->major); + goto failed; + } + if (info->minor != minor) { + log_error("Inconsistent dtree minor number: %u != %u", + minor, info->minor); + goto failed; + } + if (name && !(*name = dm_pool_strdup(mem, dm_task_get_name(*dmt)))) { + log_error("name pool_strdup failed"); + goto failed; + } + if (uuid && !(*uuid = dm_pool_strdup(mem, dm_task_get_uuid(*dmt)))) { + log_error("uuid pool_strdup failed"); + goto failed; + } + *deps = dm_task_get_deps(*dmt); + } + + return 1; + +failed: + dm_task_destroy(*dmt); + return 0; +} + /* * Deactivate a device with its dependencies if the uuid prefix matches. */ static int _info_by_dev(uint32_t major, uint32_t minor, int with_open_count, - struct dm_info *info) + struct dm_info *info, struct dm_pool *mem, + const char **name, const char **uuid) { struct dm_task *dmt; int r; @@ -899,14 +840,61 @@ static int _info_by_dev(uint32_t major, uint32_t minor, int with_open_count, if (!with_open_count && !dm_task_no_open_count(dmt)) log_error("Failed to disable open_count"); - if ((r = dm_task_run(dmt))) - r = dm_task_get_info(dmt, info); + if (!(r = dm_task_run(dmt))) + goto_out; + if (!(r = dm_task_get_info(dmt, info))) + goto_out; + + if (name && !(*name = dm_pool_strdup(mem, dm_task_get_name(dmt)))) { + log_error("name pool_strdup failed"); + r = 0; + goto_out; + } + + if (uuid && !(*uuid = dm_pool_strdup(mem, dm_task_get_uuid(dmt)))) { + log_error("uuid pool_strdup failed"); + r = 0; + goto_out; + } + +out: dm_task_destroy(dmt); return r; } +static int _check_device_not_in_use(const char *name, struct dm_info *info) +{ + if (!info->exists) + return 1; + + /* If sysfs is not used, use open_count information only. */ + if (!*dm_sysfs_dir()) { + if (info->open_count) { + log_error("Device %s (%" PRIu32 ":%" PRIu32 ") in use", + name, info->major, info->minor); + return 0; + } + + return 1; + } + + if (dm_device_has_holders(info->major, info->minor)) { + log_error("Device %s (%" PRIu32 ":%" PRIu32 ") is used " + "by another device.", name, info->major, info->minor); + return 0; + } + + if (dm_device_has_mounted_fs(info->major, info->minor)) { + log_error("Device %s (%" PRIu32 ":%" PRIu32 ") contains " + "a filesystem in use.", name, info->major, info->minor); + return 0; + } + + return 1; +} + /* Check if all parent nodes of given node have open_count == 0 */ static int _node_has_closed_parents(struct dm_tree_node *node, const char *uuid_prefix, @@ -934,19 +922,22 @@ static int _node_has_closed_parents(struct dm_tree_node *node, } /* Refresh open_count */ - if (!_info_by_dev(dinfo->major, dinfo->minor, 1, &info) || + if (!_info_by_dev(dinfo->major, dinfo->minor, 1, &info, NULL, NULL, NULL) || !info.exists) continue; - if (info.open_count) + if (info.open_count) { + log_debug("Node %s %d:%d has open_count %d", uuid_prefix, + dinfo->major, dinfo->minor, info.open_count); return 0; + } } return 1; } static int _deactivate_node(const char *name, uint32_t major, uint32_t minor, - uint32_t *cookie, uint16_t udev_flags) + uint32_t *cookie, uint16_t udev_flags, int retry) { struct dm_task *dmt; int r = 0; @@ -966,14 +957,18 @@ static int _deactivate_node(const char *name, uint32_t major, uint32_t minor, if (!dm_task_no_open_count(dmt)) log_error("Failed to disable open_count"); - if (!dm_task_set_cookie(dmt, cookie, udev_flags)) - goto out; + if (cookie) + if (!dm_task_set_cookie(dmt, cookie, udev_flags)) + goto out; + + if (retry) + dm_task_retry_remove(dmt); r = dm_task_run(dmt); - /* FIXME Until kernel returns actual name so dm-ioctl.c can handle it */ - rm_dev_node(name, dmt->cookie_set && - !(udev_flags & DM_UDEV_DISABLE_DM_RULES_FLAG)); + /* FIXME Until kernel returns actual name so dm-iface.c can handle it */ + rm_dev_node(name, dmt->cookie_set && !(udev_flags & DM_UDEV_DISABLE_DM_RULES_FLAG), + dmt->cookie_set && (udev_flags & DM_UDEV_DISABLE_LIBRARY_FALLBACK)); /* FIXME Remove node from tree or mark invalid? */ @@ -983,6 +978,244 @@ out: return r; } +static int _node_clear_table(struct dm_tree_node *dnode, uint16_t udev_flags) +{ + struct dm_task *dmt = NULL, *deps_dmt = NULL; + struct dm_info *info, deps_info; + struct dm_deps *deps = NULL; + const char *name, *uuid; + const char *default_uuid_prefix; + size_t default_uuid_prefix_len; + uint32_t i; + int r = 0; + + if (!(info = &dnode->info)) { + log_error("_node_clear_table failed: missing info"); + return 0; + } + + if (!(name = dm_tree_node_get_name(dnode))) { + log_error("_node_clear_table failed: missing name"); + return 0; + } + + /* Is there a table? */ + if (!info->exists || !info->inactive_table) + return 1; + + /* Get devices used by inactive table that's about to be deleted. */ + if (!_deps(&deps_dmt, dnode->dtree->mem, info->major, info->minor, NULL, NULL, 1, info, &deps)) { + log_error("Failed to obtain dependencies for %s before clearing table.", name); + return 0; + } + + log_verbose("Clearing inactive table %s (%" PRIu32 ":%" PRIu32 ")", + name, info->major, info->minor); + + if (!(dmt = dm_task_create(DM_DEVICE_CLEAR))) { + log_error("Table clear dm_task creation failed for %s", name); + goto_out; + } + + if (!dm_task_set_major(dmt, info->major) || + !dm_task_set_minor(dmt, info->minor)) { + log_error("Failed to set device number for %s table clear", name); + goto_out; + } + + r = dm_task_run(dmt); + + if (!dm_task_get_info(dmt, info)) { + log_error("_node_clear_table failed: info missing after running task for %s", name); + r = 0; + } + + if (!r || !deps) + goto_out; + + /* + * Remove (incomplete) devices that the inactive table referred to but + * which are not in the tree, no longer referenced and don't have a live + * table. + */ + default_uuid_prefix = dm_uuid_prefix(); + default_uuid_prefix_len = strlen(default_uuid_prefix); + + for (i = 0; i < deps->count; i++) { + /* If already in tree, assume it's under control */ + if (_find_dm_tree_node(dnode->dtree, MAJOR(deps->device[i]), MINOR(deps->device[i]))) + continue; + + if (!_info_by_dev(MAJOR(deps->device[i]), MINOR(deps->device[i]), 1, + &deps_info, dnode->dtree->mem, &name, &uuid)) + continue; + + /* Proceed if device is an 'orphan' - unreferenced and without a live table. */ + if (!deps_info.exists || deps_info.live_table || deps_info.open_count) + continue; + + if (strncmp(uuid, default_uuid_prefix, default_uuid_prefix_len)) + continue; + + /* Remove device. */ + if (!_deactivate_node(name, deps_info.major, deps_info.minor, &dnode->dtree->cookie, udev_flags, 0)) { + log_error("Failed to deactivate no-longer-used device %s (%" + PRIu32 ":%" PRIu32 ")", name, deps_info.major, deps_info.minor); + } else if (deps_info.suspended) + dec_suspended(); + } + +out: + if (dmt) + dm_task_destroy(dmt); + + if (deps_dmt) + dm_task_destroy(deps_dmt); + + return r; +} + +struct dm_tree_node *dm_tree_add_new_dev_with_udev_flags(struct dm_tree *dtree, + const char *name, + const char *uuid, + uint32_t major, + uint32_t minor, + int read_only, + int clear_inactive, + void *context, + uint16_t udev_flags) +{ + struct dm_tree_node *dnode; + struct dm_info info = { 0 }; + const char *name2; + const char *uuid2; + + if (!name || !uuid) { + log_error("Cannot add device without name and uuid."); + return NULL; + } + + /* Do we need to add node to tree? */ + if (!(dnode = dm_tree_find_node_by_uuid(dtree, uuid))) { + if (!(name2 = dm_pool_strdup(dtree->mem, name))) { + log_error("name pool_strdup failed"); + return NULL; + } + if (!(uuid2 = dm_pool_strdup(dtree->mem, uuid))) { + log_error("uuid pool_strdup failed"); + return NULL; + } + + if (!(dnode = _create_dm_tree_node(dtree, name2, uuid2, &info, + context, 0))) + return_NULL; + + /* Attach to root node until a table is supplied */ + if (!_add_to_toplevel(dnode) || !_add_to_bottomlevel(dnode)) + return_NULL; + + dnode->props.major = major; + dnode->props.minor = minor; + dnode->props.new_name = NULL; + dnode->props.size_changed = 0; + } else if (strcmp(name, dnode->name)) { + /* Do we need to rename node? */ + if (!(dnode->props.new_name = dm_pool_strdup(dtree->mem, name))) { + log_error("name pool_strdup failed"); + return NULL; + } + } + + dnode->props.read_only = read_only ? 1 : 0; + dnode->props.read_ahead = DM_READ_AHEAD_AUTO; + dnode->props.read_ahead_flags = 0; + + if (clear_inactive && !_node_clear_table(dnode, udev_flags)) + return_NULL; + + dnode->context = context; + dnode->udev_flags = udev_flags; + + return dnode; +} + +struct dm_tree_node *dm_tree_add_new_dev(struct dm_tree *dtree, const char *name, + const char *uuid, uint32_t major, uint32_t minor, + int read_only, int clear_inactive, void *context) +{ + return dm_tree_add_new_dev_with_udev_flags(dtree, name, uuid, major, minor, + read_only, clear_inactive, context, 0); +} + +static struct dm_tree_node *_add_dev(struct dm_tree *dtree, + struct dm_tree_node *parent, + uint32_t major, uint32_t minor, + uint16_t udev_flags) +{ + struct dm_task *dmt = NULL; + struct dm_info info; + struct dm_deps *deps = NULL; + const char *name = NULL; + const char *uuid = NULL; + struct dm_tree_node *node = NULL; + uint32_t i; + int new = 0; + + /* Already in tree? */ + if (!(node = _find_dm_tree_node(dtree, major, minor))) { + if (!_deps(&dmt, dtree->mem, major, minor, &name, &uuid, 0, &info, &deps)) + return_NULL; + + if (!(node = _create_dm_tree_node(dtree, name, uuid, &info, + NULL, udev_flags))) + goto_out; + new = 1; + } + + if (!_link_tree_nodes(parent, node)) { + node = NULL; + goto_out; + } + + /* If node was already in tree, no need to recurse. */ + if (!new) + goto out; + + /* Can't recurse if not a mapped device or there are no dependencies */ + if (!node->info.exists || !deps || !deps->count) { + if (!_add_to_bottomlevel(node)) { + stack; + node = NULL; + } + goto out; + } + + /* Add dependencies to tree */ + for (i = 0; i < deps->count; i++) + if (!_add_dev(dtree, node, MAJOR(deps->device[i]), + MINOR(deps->device[i]), udev_flags)) { + node = NULL; + goto_out; + } + +out: + if (dmt) + dm_task_destroy(dmt); + + return node; +} + +int dm_tree_add_dev(struct dm_tree *dtree, uint32_t major, uint32_t minor) +{ + return _add_dev(dtree, &dtree->root, major, minor, 0) ? 1 : 0; +} + +int dm_tree_add_dev_with_udev_flags(struct dm_tree *dtree, uint32_t major, + uint32_t minor, uint16_t udev_flags) +{ + return _add_dev(dtree, &dtree->root, major, minor, udev_flags) ? 1 : 0; +} + static int _rename_node(const char *old_name, const char *new_name, uint32_t major, uint32_t minor, uint32_t *cookie, uint16_t udev_flags) { @@ -1002,7 +1235,7 @@ static int _rename_node(const char *old_name, const char *new_name, uint32_t maj } if (!dm_task_set_newname(dmt, new_name)) - goto_out; + goto_out; if (!dm_task_no_open_count(dmt)) log_error("Failed to disable open_count"); @@ -1022,7 +1255,7 @@ out: static int _resume_node(const char *name, uint32_t major, uint32_t minor, uint32_t read_ahead, uint32_t read_ahead_flags, struct dm_info *newinfo, uint32_t *cookie, - uint16_t udev_flags) + uint16_t udev_flags, int already_suspended) { struct dm_task *dmt; int r = 0; @@ -1030,13 +1263,13 @@ static int _resume_node(const char *name, uint32_t major, uint32_t minor, log_verbose("Resuming %s (%" PRIu32 ":%" PRIu32 ")", name, major, minor); if (!(dmt = dm_task_create(DM_DEVICE_RESUME))) { - log_error("Suspend dm_task creation failed for %s", name); + log_debug("Suspend dm_task creation failed for %s.", name); return 0; } /* FIXME Kernel should fill in name on return instead */ if (!dm_task_set_name(dmt, name)) { - log_error("Failed to set readahead device name for %s", name); + log_debug("Failed to set device name for %s resumption.", name); goto out; } @@ -1052,10 +1285,16 @@ static int _resume_node(const char *name, uint32_t major, uint32_t minor, log_error("Failed to set read ahead"); if (!dm_task_set_cookie(dmt, cookie, udev_flags)) - goto out; + goto_out; - if ((r = dm_task_run(dmt))) - r = dm_task_get_info(dmt, newinfo); + if (!(r = dm_task_run(dmt))) + goto_out; + + if (already_suspended) + dec_suspended(); + + if (!(r = dm_task_get_info(dmt, newinfo))) + stack; out: dm_task_destroy(dmt); @@ -1094,14 +1333,176 @@ static int _suspend_node(const char *name, uint32_t major, uint32_t minor, if (no_flush && !dm_task_no_flush(dmt)) log_error("Failed to set no_flush flag."); - if ((r = dm_task_run(dmt))) + if ((r = dm_task_run(dmt))) { + inc_suspended(); r = dm_task_get_info(dmt, newinfo); + } + + dm_task_destroy(dmt); + + return r; +} + +static int _thin_pool_status_transaction_id(struct dm_tree_node *dnode, uint64_t *transaction_id) +{ + struct dm_task *dmt; + int r = 0; + uint64_t start, length; + char *type = NULL; + char *params = NULL; + + if (!(dmt = dm_task_create(DM_DEVICE_STATUS))) + return_0; + + if (!dm_task_set_major(dmt, dnode->info.major) || + !dm_task_set_minor(dmt, dnode->info.minor)) { + log_error("Failed to set major minor."); + goto out; + } + + if (!dm_task_run(dmt)) + goto_out; + + dm_get_next_target(dmt, NULL, &start, &length, &type, ¶ms); + + if (type && (strcmp(type, "thin-pool") != 0)) { + log_error("Expected thin-pool target for %d:%d and got %s.", + dnode->info.major, dnode->info.minor, type); + goto out; + } + + if (!params || (sscanf(params, "%" PRIu64, transaction_id) != 1)) { + log_error("Failed to parse transaction_id from %s.", params); + goto out; + } + + log_debug("Thin pool transaction id: %" PRIu64 " status: %s.", *transaction_id, params); + r = 1; +out: dm_task_destroy(dmt); return r; } +static int _thin_pool_node_message(struct dm_tree_node *dnode, struct thin_message *tm) +{ + struct dm_task *dmt; + struct dm_thin_message *m = &tm->message; + char buf[64]; + int r; + + switch (m->type) { + case DM_THIN_MESSAGE_CREATE_SNAP: + r = dm_snprintf(buf, sizeof(buf), "create_snap %u %u", + m->u.m_create_snap.device_id, + m->u.m_create_snap.origin_id); + break; + case DM_THIN_MESSAGE_CREATE_THIN: + r = dm_snprintf(buf, sizeof(buf), "create_thin %u", + m->u.m_create_thin.device_id); + break; + case DM_THIN_MESSAGE_DELETE: + r = dm_snprintf(buf, sizeof(buf), "delete %u", + m->u.m_delete.device_id); + break; + case DM_THIN_MESSAGE_SET_TRANSACTION_ID: + r = dm_snprintf(buf, sizeof(buf), + "set_transaction_id %" PRIu64 " %" PRIu64, + m->u.m_set_transaction_id.current_id, + m->u.m_set_transaction_id.new_id); + break; + case DM_THIN_MESSAGE_RESERVE_METADATA_SNAP: /* target vsn 1.1 */ + r = dm_snprintf(buf, sizeof(buf), "reserve_metadata_snap"); + break; + case DM_THIN_MESSAGE_RELEASE_METADATA_SNAP: /* target vsn 1.1 */ + r = dm_snprintf(buf, sizeof(buf), "release_metadata_snap"); + break; + default: + r = -1; + } + + if (r < 0) { + log_error("Failed to prepare message."); + return 0; + } + + r = 0; + + if (!(dmt = dm_task_create(DM_DEVICE_TARGET_MSG))) + return_0; + + if (!dm_task_set_major(dmt, dnode->info.major) || + !dm_task_set_minor(dmt, dnode->info.minor)) { + log_error("Failed to set message major minor."); + goto out; + } + + if (!dm_task_set_message(dmt, buf)) + goto_out; + + /* Internal functionality of dm_task */ + dmt->expected_errno = tm->expected_errno; + + if (!dm_task_run(dmt)) + goto_out; + + r = 1; +out: + dm_task_destroy(dmt); + + return r; +} + +static int _node_send_messages(struct dm_tree_node *dnode, + const char *uuid_prefix, + size_t uuid_prefix_len) +{ + struct load_segment *seg; + struct thin_message *tmsg; + uint64_t trans_id; + const char *uuid; + + if (!dnode->info.exists || (dm_list_size(&dnode->props.segs) != 1)) + return 1; + + seg = dm_list_item(dm_list_last(&dnode->props.segs), struct load_segment); + if (seg->type != SEG_THIN_POOL) + return 1; + + if (!(uuid = dm_tree_node_get_uuid(dnode))) + return_0; + + if (!_uuid_prefix_matches(uuid, uuid_prefix, uuid_prefix_len)) { + log_debug("UUID \"%s\" does not match.", uuid); + return 1; + } + + if (!_thin_pool_status_transaction_id(dnode, &trans_id)) + goto_bad; + + if (trans_id == seg->transaction_id) + return 1; /* In sync - skip messages */ + + if (trans_id != (seg->transaction_id - 1)) { + log_error("Thin pool transaction_id=%" PRIu64 ", while expected: %" PRIu64 ".", + trans_id, seg->transaction_id - 1); + goto bad; /* Nothing to send */ + } + + dm_list_iterate_items(tmsg, &seg->thin_messages) + if (!(_thin_pool_node_message(dnode, tmsg))) + goto_bad; + + return 1; +bad: + /* Try to deactivate */ + if (!(dm_tree_deactivate_children(dnode, uuid_prefix, uuid_prefix_len))) + log_error("Failed to deactivate %s", dnode->name); + + return 0; +} + /* * FIXME Don't attempt to deactivate known internal dependencies. */ @@ -1139,13 +1540,34 @@ static int _dm_tree_deactivate_children(struct dm_tree_node *dnode, continue; /* Refresh open_count */ - if (!_info_by_dev(dinfo->major, dinfo->minor, 1, &info) || + if (!_info_by_dev(dinfo->major, dinfo->minor, 1, &info, NULL, NULL, NULL) || !info.exists) continue; + if (info.open_count) { + /* Skip internal non-toplevel opened nodes */ + if (level) + continue; + + /* When retry is not allowed, error */ + if (!child->dtree->retry_remove) { + log_error("Unable to deactivate open %s (%" PRIu32 + ":%" PRIu32 ")", name, info.major, info.minor); + r = 0; + continue; + } + + /* Check toplevel node for holders/mounted fs */ + if (!_check_device_not_in_use(name, &info)) { + stack; + r = 0; + continue; + } + /* Go on with retry */ + } + /* Also checking open_count in parent nodes of presuspend_node */ - if (info.open_count || - (child->presuspend_node && + if ((child->presuspend_node && !_node_has_closed_parents(child->presuspend_node, uuid_prefix, uuid_prefix_len))) { /* Only report error from (likely non-internal) dependency at top level */ @@ -1164,18 +1586,27 @@ static int _dm_tree_deactivate_children(struct dm_tree_node *dnode, continue; if (!_deactivate_node(name, info.major, info.minor, - &child->dtree->cookie, child->udev_flags)) { + &child->dtree->cookie, child->udev_flags, + (level == 0) ? child->dtree->retry_remove : 0)) { log_error("Unable to deactivate %s (%" PRIu32 ":%" PRIu32 ")", name, info.major, info.minor); r = 0; continue; - } + } else if (info.suspended) + dec_suspended(); - if (dm_tree_node_num_children(child, 0)) { - if (!_dm_tree_deactivate_children(child, uuid_prefix, uuid_prefix_len, level + 1)) - return_0; - } + if (child->callback && + !child->callback(child, DM_NODE_CALLBACK_DEACTIVATED, + child->callback_data)) + stack; + // FIXME: We need to let lvremove pass, + // so for now deactivation ignores check result + //r = 0; // FIXME: _node_clear_table() without callback ? + + if (dm_tree_node_num_children(child, 0) && + !_dm_tree_deactivate_children(child, uuid_prefix, uuid_prefix_len, level + 1)) + return_0; } return r; @@ -1188,16 +1619,6 @@ int dm_tree_deactivate_children(struct dm_tree_node *dnode, return _dm_tree_deactivate_children(dnode, uuid_prefix, uuid_prefix_len, 0); } -void dm_tree_skip_lockfs(struct dm_tree_node *dnode) -{ - dnode->dtree->skip_lockfs = 1; -} - -void dm_tree_use_no_flush_suspend(struct dm_tree_node *dnode) -{ - dnode->dtree->no_flush = 1; -} - int dm_tree_suspend_children(struct dm_tree_node *dnode, const char *uuid_prefix, size_t uuid_prefix_len) @@ -1235,7 +1656,7 @@ int dm_tree_suspend_children(struct dm_tree_node *dnode, if (!_children_suspended(child, 1, uuid_prefix, uuid_prefix_len)) continue; - if (!_info_by_dev(dinfo->major, dinfo->minor, 0, &info) || + if (!_info_by_dev(dinfo->major, dinfo->minor, 0, &info, NULL, NULL, NULL) || !info.exists || info.suspended) continue; @@ -1305,6 +1726,9 @@ int dm_tree_activate_children(struct dm_tree_node *dnode, for (priority = 0; priority < 3; priority++) { while ((child = dm_tree_next_child(&handle, dnode, 0))) { + if (priority != child->activation_priority) + continue; + if (!(uuid = dm_tree_node_get_uuid(child))) { stack; continue; @@ -1313,9 +1737,6 @@ int dm_tree_activate_children(struct dm_tree_node *dnode, if (!_uuid_prefix_matches(uuid, uuid_prefix, uuid_prefix_len)) continue; - if (priority != child->activation_priority) - continue; - if (!(name = dm_tree_node_get_name(child))) { stack; continue; @@ -1340,7 +1761,7 @@ int dm_tree_activate_children(struct dm_tree_node *dnode, if (!_resume_node(child->name, child->info.major, child->info.minor, child->props.read_ahead, child->props.read_ahead_flags, - &newinfo, &child->dtree->cookie, child->udev_flags)) { + &newinfo, &child->dtree->cookie, child->udev_flags, child->info.suspended)) { log_error("Unable to resume %s (%" PRIu32 ":%" PRIu32 ")", child->name, child->info.major, child->info.minor); @@ -1353,6 +1774,17 @@ int dm_tree_activate_children(struct dm_tree_node *dnode, } } + /* + * FIXME: Implement delayed error reporting + * activation should be stopped only in the case, + * the submission of transation_id message fails, + * resume should continue further, just whole command + * has to report failure. + */ + if (r && dnode->props.send_messages && + !(r = _node_send_messages(dnode, uuid_prefix, uuid_prefix_len))) + stack; + handle = NULL; return r; @@ -1408,10 +1840,10 @@ out: static int _build_dev_string(char *devbuf, size_t bufsize, struct dm_tree_node *node) { if (!dm_format_dev(devbuf, bufsize, node->info.major, node->info.minor)) { - log_error("Failed to format %s device number for %s as dm " - "target (%u,%u)", - node->name, node->uuid, node->info.major, node->info.minor); - return 0; + log_error("Failed to format %s device number for %s as dm " + "target (%u,%u)", + node->name, node->uuid, node->info.major, node->info.minor); + return 0; } return 1; @@ -1444,11 +1876,11 @@ static int _emit_areas_line(struct dm_task *dmt __attribute__((unused)), unsigned log_parm_count; dm_list_iterate_items(area, &seg->areas) { - if (!_build_dev_string(devbuf, sizeof(devbuf), area->dev_node)) - return_0; - switch (seg->type) { case SEG_REPLICATOR_DEV: + if (!_build_dev_string(devbuf, sizeof(devbuf), area->dev_node)) + return_0; + EMIT_PARAMS(*pos, " %d 1 %s", area->rsite_index, devbuf); if (first_time) EMIT_PARAMS(*pos, " nolog 0"); @@ -1481,7 +1913,29 @@ static int _emit_areas_line(struct dm_task *dmt __attribute__((unused)), EMIT_PARAMS(*pos, "%s", synctype); } break; + case SEG_RAID1: + case SEG_RAID10: + case SEG_RAID4: + case SEG_RAID5_LA: + case SEG_RAID5_RA: + case SEG_RAID5_LS: + case SEG_RAID5_RS: + case SEG_RAID6_ZR: + case SEG_RAID6_NR: + case SEG_RAID6_NC: + if (!area->dev_node) { + EMIT_PARAMS(*pos, " -"); + break; + } + if (!_build_dev_string(devbuf, sizeof(devbuf), area->dev_node)) + return_0; + + EMIT_PARAMS(*pos, " %s", devbuf); + break; default: + if (!_build_dev_string(devbuf, sizeof(devbuf), area->dev_node)) + return_0; + EMIT_PARAMS(*pos, "%s%s %" PRIu64, first_time ? "" : " ", devbuf, area->offset); } @@ -1531,23 +1985,28 @@ static int _replicator_emit_segment_line(const struct load_segment *seg, char *p /* * Returns: 1 on success, 0 on failure */ -static int _mirror_emit_segment_line(struct dm_task *dmt, uint32_t major, - uint32_t minor, struct load_segment *seg, - uint64_t *seg_start, char *params, - size_t paramsize) +static int _mirror_emit_segment_line(struct dm_task *dmt, struct load_segment *seg, + char *params, size_t paramsize) { int block_on_error = 0; int handle_errors = 0; int dm_log_userspace = 0; struct utsname uts; unsigned log_parm_count; - int pos = 0; + int pos = 0, parts; char logbuf[DM_FORMAT_DEV_BUFSIZE]; const char *logtype; - unsigned kmaj, kmin, krel; + unsigned kmaj = 0, kmin = 0, krel = 0; - if (uname(&uts) == -1 || sscanf(uts.release, "%u.%u.%u", &kmaj, &kmin, &krel) != 3) { - log_error("Cannot read kernel release version"); + if (uname(&uts) == -1) { + log_error("Cannot read kernel release version."); + return 0; + } + + /* Kernels with a major number of 2 always had 3 parts. */ + parts = sscanf(uts.release, "%u.%u.%u", &kmaj, &kmin, &krel); + if (parts < 1 || (kmaj < 3 && parts < 3)) { + log_error("Wrong kernel release version %s.", uts.release); return 0; } @@ -1651,6 +2110,103 @@ static int _mirror_emit_segment_line(struct dm_task *dmt, uint32_t major, return 1; } +static int _raid_emit_segment_line(struct dm_task *dmt, uint32_t major, + uint32_t minor, struct load_segment *seg, + uint64_t *seg_start, char *params, + size_t paramsize) +{ + uint32_t i; + int param_count = 1; /* mandatory 'chunk size'/'stripe size' arg */ + int pos = 0; + + if ((seg->flags & DM_NOSYNC) || (seg->flags & DM_FORCESYNC)) + param_count++; + + if (seg->region_size) + param_count += 2; + + /* rebuilds is 64-bit */ + param_count += 2 * hweight32(seg->rebuilds & 0xFFFFFFFF); + param_count += 2 * hweight32(seg->rebuilds >> 32); + + if ((seg->type == SEG_RAID1) && seg->stripe_size) + log_error("WARNING: Ignoring RAID1 stripe size"); + + EMIT_PARAMS(pos, "%s %d %u", dm_segtypes[seg->type].target, + param_count, seg->stripe_size); + + if (seg->flags & DM_NOSYNC) + EMIT_PARAMS(pos, " nosync"); + else if (seg->flags & DM_FORCESYNC) + EMIT_PARAMS(pos, " sync"); + + if (seg->region_size) + EMIT_PARAMS(pos, " region_size %u", seg->region_size); + + for (i = 0; i < (seg->area_count / 2); i++) + if (seg->rebuilds & (1 << i)) + EMIT_PARAMS(pos, " rebuild %u", i); + + /* Print number of metadata/data device pairs */ + EMIT_PARAMS(pos, " %u", seg->area_count/2); + + if (_emit_areas_line(dmt, seg, params, paramsize, &pos) <= 0) + return_0; + + return 1; +} + +static int _thin_pool_emit_segment_line(struct dm_task *dmt, + struct load_segment *seg, + char *params, size_t paramsize) +{ + int pos = 0; + char pool[DM_FORMAT_DEV_BUFSIZE], metadata[DM_FORMAT_DEV_BUFSIZE]; + int features = (seg->skip_block_zeroing ? 1 : 0) + + (seg->ignore_discard ? 1 : 0) + + (seg->no_discard_passdown ? 1 : 0); + + if (!_build_dev_string(metadata, sizeof(metadata), seg->metadata)) + return_0; + + if (!_build_dev_string(pool, sizeof(pool), seg->pool)) + return_0; + + EMIT_PARAMS(pos, "%s %s %d %" PRIu64 " %d%s%s%s", metadata, pool, + seg->data_block_size, seg->low_water_mark, features, + seg->skip_block_zeroing ? " skip_block_zeroing" : "", + seg->ignore_discard ? " ignore_discard" : "", + seg->no_discard_passdown ? " no_discard_passdown" : "" + ); + + return 1; +} + +static int _thin_emit_segment_line(struct dm_task *dmt, + struct load_segment *seg, + char *params, size_t paramsize) +{ + int pos = 0; + char pool[DM_FORMAT_DEV_BUFSIZE]; + char external[DM_FORMAT_DEV_BUFSIZE + 1]; + + if (!_build_dev_string(pool, sizeof(pool), seg->pool)) + return_0; + + if (!seg->external) + *external = 0; + else { + *external = ' '; + if (!_build_dev_string(external + 1, sizeof(external) - 1, + seg->external)) + return_0; + } + + EMIT_PARAMS(pos, "%s %d%s", pool, seg->device_id, external); + + return 1; +} + static int _emit_segment_line(struct dm_task *dmt, uint32_t major, uint32_t minor, struct load_segment *seg, uint64_t *seg_start, char *params, @@ -1658,6 +2214,7 @@ static int _emit_segment_line(struct dm_task *dmt, uint32_t major, { int pos = 0; int r; + int target_type_is_raid = 0; char originbuf[DM_FORMAT_DEV_BUFSIZE], cowbuf[DM_FORMAT_DEV_BUFSIZE]; switch(seg->type) { @@ -1667,8 +2224,7 @@ static int _emit_segment_line(struct dm_task *dmt, uint32_t major, break; case SEG_MIRRORED: /* Mirrors are pretty complicated - now in separate function */ - r = _mirror_emit_segment_line(dmt, major, minor, seg, seg_start, - params, paramsize); + r = _mirror_emit_segment_line(dmt, seg, params, paramsize); if (!r) return_0; break; @@ -1711,6 +2267,31 @@ static int _emit_segment_line(struct dm_task *dmt, uint32_t major, seg->iv_offset != DM_CRYPT_IV_DEFAULT ? seg->iv_offset : *seg_start); break; + case SEG_RAID1: + case SEG_RAID10: + case SEG_RAID4: + case SEG_RAID5_LA: + case SEG_RAID5_RA: + case SEG_RAID5_LS: + case SEG_RAID5_RS: + case SEG_RAID6_ZR: + case SEG_RAID6_NR: + case SEG_RAID6_NC: + target_type_is_raid = 1; + r = _raid_emit_segment_line(dmt, major, minor, seg, seg_start, + params, paramsize); + if (!r) + return_0; + + break; + case SEG_THIN_POOL: + if (!_thin_pool_emit_segment_line(dmt, seg, params, paramsize)) + return_0; + break; + case SEG_THIN: + if (!_thin_emit_segment_line(dmt, seg, params, paramsize)) + return_0; + break; } switch(seg->type) { @@ -1720,6 +2301,8 @@ static int _emit_segment_line(struct dm_task *dmt, uint32_t major, case SEG_SNAPSHOT_ORIGIN: case SEG_SNAPSHOT_MERGE: case SEG_ZERO: + case SEG_THIN_POOL: + case SEG_THIN: break; case SEG_CRYPT: case SEG_LINEAR: @@ -1729,14 +2312,23 @@ static int _emit_segment_line(struct dm_task *dmt, uint32_t major, stack; return r; } + if (!params[0]) { + log_error("No parameters supplied for %s target " + "%u:%u.", dm_segtypes[seg->type].target, + major, minor); + return 0; + } break; } log_debug("Adding target to (%" PRIu32 ":%" PRIu32 "): %" PRIu64 " %" PRIu64 " %s %s", major, minor, - *seg_start, seg->size, dm_segtypes[seg->type].target, params); + *seg_start, seg->size, target_type_is_raid ? "raid" : + dm_segtypes[seg->type].target, params); - if (!dm_task_add_target(dmt, *seg_start, seg->size, dm_segtypes[seg->type].target, params)) + if (!dm_task_add_target(dmt, *seg_start, seg->size, + target_type_is_raid ? "raid" : + dm_segtypes[seg->type].target, params)) return_0; *seg_start += seg->size; @@ -1785,7 +2377,7 @@ static int _load_node(struct dm_tree_node *dnode) int r = 0; struct dm_task *dmt; struct load_segment *seg; - uint64_t seg_start = 0; + uint64_t seg_start = 0, existing_table_size; log_verbose("Loading %s table (%" PRIu32 ":%" PRIu32 ")", dnode->name, dnode->info.major, dnode->info.minor); @@ -1820,15 +2412,28 @@ static int _load_node(struct dm_tree_node *dnode) if ((r = dm_task_run(dmt))) { r = dm_task_get_info(dmt, &dnode->info); if (r && !dnode->info.inactive_table) - log_verbose("Suppressed %s identical table reload.", - dnode->name); + log_verbose("Suppressed %s (%" PRIu32 ":%" PRIu32 + ") identical table reload.", + dnode->name, + dnode->info.major, dnode->info.minor); + existing_table_size = dm_task_get_existing_table_size(dmt); if ((dnode->props.size_changed = - (dm_task_get_existing_table_size(dmt) == seg_start) ? 0 : 1)) + (existing_table_size == seg_start) ? 0 : 1)) { + /* + * Kernel usually skips size validation on zero-length devices + * now so no need to preload them. + */ + /* FIXME In which kernel version did this begin? */ + if (!existing_table_size && dnode->props.delay_resume_if_new) + dnode->props.size_changed = 0; + log_debug("Table size changed from %" PRIu64 " to %" - PRIu64 " for %s", - dm_task_get_existing_table_size(dmt), - seg_start, dnode->name); + PRIu64 " for %s (%" PRIu32 ":%" PRIu32 ").%s", + existing_table_size, seg_start, dnode->name, + dnode->info.major, dnode->info.minor, + dnode->props.size_changed ? "" : " (Ignoring.)"); + } } dnode->props.segment_count = 0; @@ -1865,19 +2470,13 @@ int dm_tree_preload_children(struct dm_tree_node *dnode, return_0; /* FIXME Cope if name exists with no uuid? */ - if (!child->info.exists) { - if (!_create_node(child)) { - stack; - return 0; - } - } + if (!child->info.exists && !_create_node(child)) + return_0; - if (!child->info.inactive_table && child->props.segment_count) { - if (!_load_node(child)) { - stack; - return 0; - } - } + if (!child->info.inactive_table && + child->props.segment_count && + !_load_node(child)) + return_0; /* Propagate device size change change */ if (child->props.size_changed) @@ -1892,7 +2491,8 @@ int dm_tree_preload_children(struct dm_tree_node *dnode, if (!_resume_node(child->name, child->info.major, child->info.minor, child->props.read_ahead, child->props.read_ahead_flags, - &newinfo, &child->dtree->cookie, child->udev_flags)) { + &newinfo, &child->dtree->cookie, child->udev_flags, + child->info.suspended)) { log_error("Unable to resume %s (%" PRIu32 ":%" PRIu32 ")", child->name, child->info.major, child->info.minor); @@ -1902,7 +2502,6 @@ int dm_tree_preload_children(struct dm_tree_node *dnode, /* Update cached info */ child->info = newinfo; - /* * Prepare for immediate synchronization with udev and flush all stacked * dev node operations if requested by immediate_dev_node property. But @@ -1910,16 +2509,18 @@ int dm_tree_preload_children(struct dm_tree_node *dnode, */ if (child->props.immediate_dev_node) update_devs_flag = 1; - } - handle = NULL; - - if (update_devs_flag) { + if (update_devs_flag || + (!dnode->info.exists && dnode->callback)) { if (!dm_udev_wait(dm_tree_get_cookie(dnode))) stack; dm_tree_set_cookie(dnode, 0); - dm_task_update_nodes(); + + if (!dnode->info.exists && dnode->callback && + !dnode->callback(child, DM_NODE_CALLBACK_PRELOADED, + dnode->callback_data)) + return_0; } return r; @@ -1982,8 +2583,8 @@ static struct load_segment *_add_segment(struct dm_tree_node *dnode, unsigned ty } int dm_tree_node_add_snapshot_origin_target(struct dm_tree_node *dnode, - uint64_t size, - const char *origin_uuid) + uint64_t size, + const char *origin_uuid) { struct load_segment *seg; struct dm_tree_node *origin_node; @@ -2003,6 +2604,12 @@ int dm_tree_node_add_snapshot_origin_target(struct dm_tree_node *dnode, /* Resume snapshot origins after new snapshots */ dnode->activation_priority = 1; + /* + * Don't resume the origin immediately in case it is a non-trivial + * target that must not be active more than once concurrently! + */ + origin_node->props.delay_resume_if_new = 1; + return 1; } @@ -2088,7 +2695,7 @@ int dm_tree_node_add_snapshot_merge_target(struct dm_tree_node *node, } int dm_tree_node_add_error_target(struct dm_tree_node *node, - uint64_t size) + uint64_t size) { if (!_add_segment(node, SEG_ERROR, size)) return_0; @@ -2097,7 +2704,7 @@ int dm_tree_node_add_error_target(struct dm_tree_node *node, } int dm_tree_node_add_zero_target(struct dm_tree_node *node, - uint64_t size) + uint64_t size) { if (!_add_segment(node, SEG_ZERO, size)) return_0; @@ -2106,7 +2713,7 @@ int dm_tree_node_add_zero_target(struct dm_tree_node *node, } int dm_tree_node_add_linear_target(struct dm_tree_node *node, - uint64_t size) + uint64_t size) { if (!_add_segment(node, SEG_LINEAR, size)) return_0; @@ -2115,8 +2722,8 @@ int dm_tree_node_add_linear_target(struct dm_tree_node *node, } int dm_tree_node_add_striped_target(struct dm_tree_node *node, - uint64_t size, - uint32_t stripe_size) + uint64_t size, + uint32_t stripe_size) { struct load_segment *seg; @@ -2172,7 +2779,10 @@ int dm_tree_node_add_mirror_target_log(struct dm_tree_node *node, log_error("log uuid pool_strdup failed"); return 0; } - if (!(flags & DM_CORELOG)) { + if ((flags & DM_CORELOG)) + /* For pvmove: immediate resume (for size validation) isn't needed. */ + node->props.delay_resume_if_new = 1; + else { if (!(log_node = dm_tree_find_node_by_uuid(node->dtree, log_uuid))) { log_error("Couldn't find mirror log uuid %s.", log_uuid); return 0; @@ -2181,6 +2791,10 @@ int dm_tree_node_add_mirror_target_log(struct dm_tree_node *node, if (clustered) log_node->props.immediate_dev_node = 1; + /* The kernel validates the size of disk logs. */ + /* FIXME Propagate to any devices below */ + log_node->props.delay_resume_if_new = 0; + if (!_link_tree_nodes(node, log_node)) return_0; } @@ -2196,7 +2810,7 @@ int dm_tree_node_add_mirror_target_log(struct dm_tree_node *node, } int dm_tree_node_add_mirror_target(struct dm_tree_node *node, - uint64_t size) + uint64_t size) { if (!_add_segment(node, SEG_MIRRORED, size)) return_0; @@ -2204,6 +2818,35 @@ int dm_tree_node_add_mirror_target(struct dm_tree_node *node, return 1; } +int dm_tree_node_add_raid_target(struct dm_tree_node *node, + uint64_t size, + const char *raid_type, + uint32_t region_size, + uint32_t stripe_size, + uint64_t rebuilds, + uint64_t flags) +{ + int i; + struct load_segment *seg = NULL; + + for (i = 0; dm_segtypes[i].target && !seg; i++) + if (!strcmp(raid_type, dm_segtypes[i].target)) + if (!(seg = _add_segment(node, + dm_segtypes[i].type, size))) + return_0; + + if (!seg) + return_0; + + seg->region_size = region_size; + seg->stripe_size = stripe_size; + seg->area_count = 0; + seg->rebuilds = rebuilds; + seg->flags = flags; + + return 1; +} + int dm_tree_node_add_replicator_target(struct dm_tree_node *node, uint64_t size, const char *rlog_uuid, @@ -2369,6 +3012,296 @@ int dm_tree_node_add_replicator_dev_target(struct dm_tree_node *node, return 1; } +static struct load_segment *_get_single_load_segment(struct dm_tree_node *node, + unsigned type) +{ + struct load_segment *seg; + + if (node->props.segment_count != 1) { + log_error("Node %s must have only one segment.", + dm_segtypes[type].target); + return NULL; + } + + seg = dm_list_item(dm_list_last(&node->props.segs), struct load_segment); + if (seg->type != type) { + log_error("Node %s has segment type %s.", + dm_segtypes[type].target, + dm_segtypes[seg->type].target); + return NULL; + } + + return seg; +} + +static int _thin_validate_device_id(uint32_t device_id) +{ + if (device_id > DM_THIN_MAX_DEVICE_ID) { + log_error("Device id %u is higher then %u.", + device_id, DM_THIN_MAX_DEVICE_ID); + return 0; + } + + return 1; +} + +int dm_tree_node_add_thin_pool_target(struct dm_tree_node *node, + uint64_t size, + uint64_t transaction_id, + const char *metadata_uuid, + const char *pool_uuid, + uint32_t data_block_size, + uint64_t low_water_mark, + unsigned skip_block_zeroing) +{ + struct load_segment *seg, *mseg; + uint64_t devsize = 0; + /* + * Max supported size for thin pool metadata device + * Limitation is hardcoded into kernel and bigger + * device size is not accepted. (16978542592) + */ + const uint64_t max_metadata_size = + 255ULL * (1 << 14) * (4096 / (1 << 9)) - 256 * 1024; + + if (data_block_size < DM_THIN_MIN_DATA_BLOCK_SIZE) { + log_error("Data block size %u is lower then %u sectors.", + data_block_size, DM_THIN_MIN_DATA_BLOCK_SIZE); + return 0; + } + + if (data_block_size > DM_THIN_MAX_DATA_BLOCK_SIZE) { + log_error("Data block size %u is higher then %u sectors.", + data_block_size, DM_THIN_MAX_DATA_BLOCK_SIZE); + return 0; + } + + if (!(seg = _add_segment(node, SEG_THIN_POOL, size))) + return_0; + + if (!(seg->metadata = dm_tree_find_node_by_uuid(node->dtree, metadata_uuid))) { + log_error("Missing metadata uuid %s.", metadata_uuid); + return 0; + } + + if (!_link_tree_nodes(node, seg->metadata)) + return_0; + + /* FIXME: more complex target may need more tweaks */ + dm_list_iterate_items(mseg, &seg->metadata->props.segs) { + devsize += mseg->size; + if (devsize > max_metadata_size) { + log_debug("Ignoring %" PRIu64 " of device.", + devsize - max_metadata_size); + mseg->size -= (devsize - max_metadata_size); + devsize = max_metadata_size; + /* FIXME: drop remaining segs */ + } + } + + if (!(seg->pool = dm_tree_find_node_by_uuid(node->dtree, pool_uuid))) { + log_error("Missing pool uuid %s.", pool_uuid); + return 0; + } + + if (!_link_tree_nodes(node, seg->pool)) + return_0; + + /* Clean flag delay_resume_if_new - so corelog gets resumed */ + seg->metadata->props.delay_resume_if_new = 0; + seg->pool->props.delay_resume_if_new = 0; + + node->props.send_messages = 1; + seg->transaction_id = transaction_id; + seg->low_water_mark = low_water_mark; + seg->data_block_size = data_block_size; + seg->skip_block_zeroing = skip_block_zeroing; + dm_list_init(&seg->thin_messages); + + return 1; +} + +int dm_tree_node_add_thin_pool_message(struct dm_tree_node *node, + dm_thin_message_t type, + uint64_t id1, uint64_t id2) +{ + struct thin_message *tm; + struct load_segment *seg; + + if (!(seg = _get_single_load_segment(node, SEG_THIN_POOL))) + return_0; + + if (!(tm = dm_pool_zalloc(node->dtree->mem, sizeof (*tm)))) { + log_error("Failed to allocate thin message."); + return 0; + } + + switch (type) { + case DM_THIN_MESSAGE_CREATE_SNAP: + /* If the thin origin is active, it must be suspend first! */ + if (id1 == id2) { + log_error("Cannot use same device id for origin and its snapshot."); + return 0; + } + if (!_thin_validate_device_id(id1) || + !_thin_validate_device_id(id2)) + return_0; + tm->message.u.m_create_snap.device_id = id1; + tm->message.u.m_create_snap.origin_id = id2; + break; + case DM_THIN_MESSAGE_CREATE_THIN: + if (!_thin_validate_device_id(id1)) + return_0; + tm->message.u.m_create_thin.device_id = id1; + tm->expected_errno = EEXIST; + break; + case DM_THIN_MESSAGE_DELETE: + if (!_thin_validate_device_id(id1)) + return_0; + tm->message.u.m_delete.device_id = id1; + tm->expected_errno = ENODATA; + break; + case DM_THIN_MESSAGE_SET_TRANSACTION_ID: + if ((id1 + 1) != id2) { + log_error("New transaction id must be sequential."); + return 0; /* FIXME: Maybe too strict here? */ + } + if (id2 != seg->transaction_id) { + log_error("Current transaction id is different from thin pool."); + return 0; /* FIXME: Maybe too strict here? */ + } + tm->message.u.m_set_transaction_id.current_id = id1; + tm->message.u.m_set_transaction_id.new_id = id2; + break; + default: + log_error("Unsupported message type %d.", (int) type); + return 0; + } + + tm->message.type = type; + dm_list_add(&seg->thin_messages, &tm->list); + + return 1; +} + +int dm_tree_node_set_thin_pool_discard(struct dm_tree_node *node, + unsigned ignore, + unsigned no_passdown) +{ + struct load_segment *seg; + + if (!(seg = _get_single_load_segment(node, SEG_THIN_POOL))) + return_0; + + seg->ignore_discard = ignore; + seg->no_discard_passdown = no_passdown; + + return 1; +} + +int dm_tree_node_add_thin_target(struct dm_tree_node *node, + uint64_t size, + const char *pool_uuid, + uint32_t device_id) +{ + struct dm_tree_node *pool; + struct load_segment *seg; + + if (!(pool = dm_tree_find_node_by_uuid(node->dtree, pool_uuid))) { + log_error("Missing thin pool uuid %s.", pool_uuid); + return 0; + } + + if (!_link_tree_nodes(node, pool)) + return_0; + + if (!_thin_validate_device_id(device_id)) + return_0; + + if (!(seg = _add_segment(node, SEG_THIN, size))) + return_0; + + seg->pool = pool; + seg->device_id = device_id; + + return 1; +} + +int dm_tree_node_set_thin_external_origin(struct dm_tree_node *node, + const char *external_uuid) +{ + struct dm_tree_node *external; + struct load_segment *seg; + + if (!(seg = _get_single_load_segment(node, SEG_THIN))) + return_0; + + if (!(external = dm_tree_find_node_by_uuid(node->dtree, + external_uuid))) { + log_error("Missing thin external origin uuid %s.", + external_uuid); + return 0; + } + + if (!_link_tree_nodes(node, external)) + return_0; + + seg->external = external; + + return 1; +} + +int dm_get_status_thin_pool(struct dm_pool *mem, const char *params, + struct dm_status_thin_pool **status) +{ + struct dm_status_thin_pool *s; + + if (!(s = dm_pool_zalloc(mem, sizeof(struct dm_status_thin_pool)))) { + log_error("Failed to allocate thin_pool status structure."); + return 0; + } + + /* FIXME: add support for held metadata root */ + if (sscanf(params, "%" PRIu64 " %" PRIu64 "/%" PRIu64 " %" PRIu64 "/%" PRIu64, + &s->transaction_id, + &s->used_metadata_blocks, + &s->total_metadata_blocks, + &s->used_data_blocks, + &s->total_data_blocks) != 5) { + log_error("Failed to parse thin pool params: %s.", params); + return 0; + } + + *status = s; + + return 1; +} + +int dm_get_status_thin(struct dm_pool *mem, const char *params, + struct dm_status_thin **status) +{ + struct dm_status_thin *s; + + if (!(s = dm_pool_zalloc(mem, sizeof(struct dm_status_thin)))) { + log_error("Failed to allocate thin status structure."); + return 0; + } + + if (strchr(params, '-')) { + s->mapped_sectors = 0; + s->highest_mapped_sector = 0; + } else if (sscanf(params, "%" PRIu64 " %" PRIu64, + &s->mapped_sectors, + &s->highest_mapped_sector) != 2) { + log_error("Failed to parse thin params: %s.", params); + return 0; + } + + *status = s; + + return 1; +} + static int _add_area(struct dm_tree_node *node, struct load_segment *seg, struct dm_tree_node *dev_node, uint64_t offset) { struct seg_area *area; @@ -2388,9 +3321,9 @@ static int _add_area(struct dm_tree_node *node, struct load_segment *seg, struct } int dm_tree_node_add_target_area(struct dm_tree_node *node, - const char *dev_name, - const char *uuid, - uint64_t offset) + const char *dev_name, + const char *uuid, + uint64_t offset) { struct load_segment *seg; struct stat info; @@ -2409,12 +3342,12 @@ int dm_tree_node_add_target_area(struct dm_tree_node *node, if (!_link_tree_nodes(node, dev_node)) return_0; } else { - if (stat(dev_name, &info) < 0) { + if (stat(dev_name, &info) < 0) { log_error("Device %s not found.", dev_name); return 0; } - if (!S_ISBLK(info.st_mode)) { + if (!S_ISBLK(info.st_mode)) { log_error("Device %s is not a block device.", dev_name); return 0; } @@ -2438,12 +3371,37 @@ int dm_tree_node_add_target_area(struct dm_tree_node *node, return 1; } -void dm_tree_set_cookie(struct dm_tree_node *node, uint32_t cookie) +int dm_tree_node_add_null_area(struct dm_tree_node *node, uint64_t offset) { - node->dtree->cookie = cookie; + struct load_segment *seg; + + seg = dm_list_item(dm_list_last(&node->props.segs), struct load_segment); + + switch (seg->type) { + case SEG_RAID1: + case SEG_RAID4: + case SEG_RAID5_LA: + case SEG_RAID5_RA: + case SEG_RAID5_LS: + case SEG_RAID5_RS: + case SEG_RAID6_ZR: + case SEG_RAID6_NR: + case SEG_RAID6_NC: + break; + default: + log_error("dm_tree_node_add_null_area() called on an unsupported segment type"); + return 0; + } + + if (!_add_area(node, seg, NULL, offset)) + return_0; + + return 1; } -uint32_t dm_tree_get_cookie(struct dm_tree_node *node) +void dm_tree_node_set_callback(struct dm_tree_node *dnode, + dm_node_callback_fn cb, void *data) { - return node->dtree->cookie; + dnode->callback = cb; + dnode->callback_data = data; } diff --git a/libdm/libdm-file.c b/libdm/libdm-file.c index 82fae34..26c8241 100644 --- a/libdm/libdm-file.c +++ b/libdm/libdm-file.c @@ -27,6 +27,11 @@ static int _create_dir_recursive(const char *dir) log_verbose("Creating directory \"%s\"", dir); /* Create parent directories */ orig = s = dm_strdup(dir); + if (!s) { + log_error("Failed to duplicate directory name."); + return 0; + } + while ((s = strchr(s, '/')) != NULL) { *s = '\0'; if (*orig) { @@ -71,6 +76,26 @@ int dm_create_dir(const char *dir) return 0; } +int dm_is_empty_dir(const char *dir) +{ + struct dirent *dirent; + DIR *d; + + if (!(d = opendir(dir))) { + log_sys_error("opendir", dir); + return 0; + } + + while ((dirent = readdir(d))) + if (strcmp(dirent->d_name, ".") && strcmp(dirent->d_name, "..")) + break; + + if (closedir(d)) + log_sys_error("closedir", dir); + + return dirent ? 0 : 1; +} + int dm_fclose(FILE *stream) { int prev_fail = ferror(stream); @@ -110,7 +135,6 @@ retry_fcntl: switch (errno) { case EINTR: goto retry_fcntl; - break; case EACCES: case EAGAIN: if (retries == 20) { @@ -149,7 +173,7 @@ retry_fcntl: goto fail_close_unlink; } - if ((write_out == 0) || (write_out < bufferlen)) { + if ((write_out == 0) || ((size_t)write_out < bufferlen)) { log_error("Cannot write pid to pidfile [%s], shortwrite of" "[%" PRIsize_t "] bytes, expected [%" PRIsize_t "]\n", lockfile, write_out, bufferlen); diff --git a/libdm/libdm-report.c b/libdm/libdm-report.c index 9b8e3c1..0bafa86 100644 --- a/libdm/libdm-report.c +++ b/libdm/libdm-report.c @@ -102,7 +102,7 @@ static const struct dm_report_object_type *_find_type(struct dm_report *rh, */ int dm_report_field_string(struct dm_report *rh, - struct dm_report_field *field, const char **data) + struct dm_report_field *field, const char *const *data) { char *repstr; @@ -139,7 +139,7 @@ int dm_report_field_int(struct dm_report *rh, return 0; } - *sortval = (const uint64_t) value; + *sortval = (uint64_t) value; field->sort_value = sortval; field->report_string = repstr; @@ -168,7 +168,7 @@ int dm_report_field_uint32(struct dm_report *rh, return 0; } - *sortval = (const uint64_t) value; + *sortval = (uint64_t) value; field->sort_value = sortval; field->report_string = repstr; @@ -197,7 +197,7 @@ int dm_report_field_int32(struct dm_report *rh, return 0; } - *sortval = (const uint64_t) value; + *sortval = (uint64_t) value; field->sort_value = sortval; field->report_string = repstr; @@ -367,33 +367,32 @@ static uint32_t _all_match(struct dm_report *rh, const char *field, size_t flen) { size_t prefix_len; const struct dm_report_object_type *t; - char prefixed_all[32]; + uint32_t report_types = 0; + unsigned unprefixed_all_matched = 0; if (!strncasecmp(field, "all", 3) && flen == 3) { - if (strlen(rh->field_prefix)) { - strcpy(prefixed_all, rh->field_prefix); - strcat(prefixed_all, "all"); - /* - * Add also prefix to receive all attributes - * (e.g.LABEL/PVS use the same prefix) - */ - return rh->report_types | - _all_match(rh, prefixed_all, - strlen(prefixed_all)); - } else - return (rh->report_types) - ? rh->report_types : REPORT_TYPES_ALL; + /* If there's no report prefix, match all report types */ + if (!(flen = strlen(rh->field_prefix))) + return rh->report_types ? : REPORT_TYPES_ALL; + + /* otherwise include all fields beginning with the report prefix. */ + unprefixed_all_matched = 1; + field = rh->field_prefix; + report_types = rh->report_types; } + /* Combine all report types that have a matching prefix. */ for (t = rh->types; t->data_fn; t++) { prefix_len = strlen(t->prefix); + if (!strncasecmp(t->prefix, field, prefix_len) && - !strncasecmp(field + prefix_len, "all", 3) && - flen == prefix_len + 3) - return t->id; + ((unprefixed_all_matched && (flen == prefix_len)) || + (!strncasecmp(field + prefix_len, "all", 3) && + (flen == prefix_len + 3)))) + report_types |= t->id; } - return 0; + return report_types; } /* @@ -542,6 +541,9 @@ static int _parse_keys(struct dm_report *rh, const char *keys, const char *ws; /* Word start */ const char *we = keys; /* Word end */ + if (!keys) + return 1; + while (*we) { /* Allow consecutive commas */ while (*we && *we == ',') @@ -675,15 +677,15 @@ int dm_report_set_output_field_name_prefix(struct dm_report *rh, const char *out /* * Create a row of data for an object */ -static void * _report_get_field_data(struct dm_report *rh, - struct field_properties *fp, void *object) +static void *_report_get_field_data(struct dm_report *rh, + struct field_properties *fp, void *object) { - void *ret = fp->type->data_fn(object); + char *ret = fp->type->data_fn(object); if (!ret) return NULL; - return ret + rh->fields[fp->field_num].offset; + return (void *)(ret + rh->fields[fp->field_num].offset); } int dm_report_object(struct dm_report *rh, void *object) @@ -693,6 +695,11 @@ int dm_report_object(struct dm_report *rh, void *object) struct dm_report_field *field; void *data = NULL; + if (!rh) { + log_error(INTERNAL_ERROR "dm_report handler is NULL."); + return 0; + } + if (!(row = dm_pool_zalloc(rh->mem, sizeof(*row)))) { log_error("dm_report_object: struct row allocation failed"); return 0; @@ -734,8 +741,8 @@ int dm_report_object(struct dm_report *rh, void *object) return 0; } - if ((strlen(field->report_string) > field->props->width)) - field->props->width = strlen(field->report_string); + if (((int) strlen(field->report_string) > field->props->width)) + field->props->width = (int) strlen(field->report_string); if ((rh->flags & RH_SORT_REQUIRED) && (field->props->flags & FLD_SORT_KEY)) { @@ -775,8 +782,8 @@ static int _report_headings(struct dm_report *rh) } dm_list_iterate_items(fp, &rh->field_props) { - if (buf_size < fp->width) - buf_size = fp->width; + if ((int) buf_size < fp->width) + buf_size = (size_t) fp->width; } /* Including trailing '\0'! */ buf_size++; @@ -1006,11 +1013,6 @@ static int _output_as_rows(struct dm_report *rh) struct dm_report_field *field; struct row *row; - if (!dm_pool_begin_object(rh->mem, 512)) { - log_error("dm_report: Unable to allocate output line"); - return 0; - } - dm_list_iterate_items(fp, &rh->field_props) { if (fp->flags & FLD_HIDDEN) { dm_list_iterate_items(row, &rh->rows) { @@ -1020,6 +1022,11 @@ static int _output_as_rows(struct dm_report *rh) continue; } + if (!dm_pool_begin_object(rh->mem, 512)) { + log_error("dm_report: Unable to allocate output line"); + return 0; + } + if ((rh->flags & DM_REPORT_OUTPUT_HEADINGS)) { if (!dm_pool_grow_object(rh->mem, rh->fields[fp->field_num].heading, 0)) { log_error("dm_report: Failed to extend row for field name"); diff --git a/libdm/libdm-string.c b/libdm/libdm-string.c index 365e7ec..5ef6334 100644 --- a/libdm/libdm-string.c +++ b/libdm/libdm-string.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006-2007 Red Hat, Inc. All rights reserved. + * Copyright (C) 2006-2012 Red Hat, Inc. All rights reserved. * * This file is part of the device-mapper userspace tools. * @@ -13,7 +13,6 @@ */ #include "dmlib.h" -#include "libdevmapper.h" #include <ctype.h> @@ -130,9 +129,9 @@ const char *dm_basename(const char *path) return p ? p + 1 : path; } -int dm_asprintf(char **result, const char *format, ...) +int dm_vasprintf(char **result, const char *format, va_list aq) { - int n, ok = 0, size = 32; + int i, n, size = 16; va_list ap; char *buf = dm_malloc(size); @@ -141,23 +140,293 @@ int dm_asprintf(char **result, const char *format, ...) if (!buf) return -1; - while (!ok) { - va_start(ap, format); + for (i = 0;; i++) { + va_copy(ap, aq); n = vsnprintf(buf, size, format, ap); va_end(ap); if (0 <= n && n < size) - ok = 1; - else { + break; + + dm_free(buf); + /* Up to glibc 2.0.6 returns -1 */ + size = (n < 0) ? size * 2 : n + 1; + if (!(buf = dm_malloc(size))) + return -1; + } + + if (i > 1) { + /* Reallocating more then once? */ + if (!(*result = dm_strdup(buf))) { dm_free(buf); - size *= 2; - buf = dm_malloc(size); - if (!buf) - return -1; + return -1; } - } + dm_free(buf); + } else + *result = buf; - *result = dm_strdup(buf); - dm_free(buf); return n + 1; } + +int dm_asprintf(char **result, const char *format, ...) +{ + int r; + va_list ap; + va_start(ap, format); + r = dm_vasprintf(result, format, ap); + va_end(ap); + return r; +} + +/* + * Count occurences of 'c' in 'str' until we reach a null char. + * + * Returns: + * len - incremented for each char we encounter. + * count - number of occurrences of 'c' and 'c2'. + */ +static void _count_chars(const char *str, size_t *len, int *count, + const int c1, const int c2) +{ + const char *ptr; + + for (ptr = str; *ptr; ptr++, (*len)++) + if (*ptr == c1 || *ptr == c2) + (*count)++; +} + +/* + * Count occurrences of 'c' in 'str' of length 'size'. + * + * Returns: + * Number of occurrences of 'c' + */ +unsigned dm_count_chars(const char *str, size_t len, const int c) +{ + size_t i; + unsigned count = 0; + + for (i = 0; i < len; i++) + if (str[i] == c) + count++; + + return count; +} + +/* + * Length of string after escaping double quotes and backslashes. + */ +size_t dm_escaped_len(const char *str) +{ + size_t len = 1; + int count = 0; + + _count_chars(str, &len, &count, '\"', '\\'); + + return count + len; +} + +/* + * Copies a string, quoting orig_char with quote_char. + * Optionally also quote quote_char. + */ +static void _quote_characters(char **out, const char *src, + const int orig_char, const int quote_char, + int quote_quote_char) +{ + while (*src) { + if (*src == orig_char || + (*src == quote_char && quote_quote_char)) + *(*out)++ = quote_char; + + *(*out)++ = *src++; + } +} + +static void _unquote_one_character(char *src, const char orig_char, + const char quote_char) +{ + char *out; + char s, n; + + /* Optimise for the common case where no changes are needed. */ + while ((s = *src++)) { + if (s == quote_char && + ((n = *src) == orig_char || n == quote_char)) { + out = src++; + *(out - 1) = n; + + while ((s = *src++)) { + if (s == quote_char && + ((n = *src) == orig_char || n == quote_char)) { + s = n; + src++; + } + *out = s; + out++; + } + + *out = '\0'; + return; + } + } +} + +/* + * Unquote each character given in orig_char array and unquote quote_char + * as well. Also save the first occurrence of each character from orig_char + * that was found unquoted in arr_substr_first_unquoted array. This way we can + * process several characters in one go. + */ +static void _unquote_characters(char *src, const char *orig_chars, + size_t num_orig_chars, + const char quote_char, + char *arr_substr_first_unquoted[]) +{ + char *out = src; + char c, s, n; + unsigned i; + + while ((s = *src++)) { + for (i = 0; i < num_orig_chars; i++) { + c = orig_chars[i]; + if (s == quote_char && + ((n = *src) == c || n == quote_char)) { + s = n; + src++; + break; + } + if (arr_substr_first_unquoted && (s == c) && + !arr_substr_first_unquoted[i]) + arr_substr_first_unquoted[i] = out; + }; + *out++ = s; + } + + *out = '\0'; +} + +/* + * Copies a string, quoting hyphens with hyphens. + */ +static void _quote_hyphens(char **out, const char *src) +{ + _quote_characters(out, src, '-', '-', 0); +} + +/* + * <vg>-<lv>-<layer> or if !layer just <vg>-<lv>. + */ +char *dm_build_dm_name(struct dm_pool *mem, const char *vgname, + const char *lvname, const char *layer) +{ + size_t len = 1; + int hyphens = 1; + char *r, *out; + + _count_chars(vgname, &len, &hyphens, '-', 0); + _count_chars(lvname, &len, &hyphens, '-', 0); + + if (layer && *layer) { + _count_chars(layer, &len, &hyphens, '-', 0); + hyphens++; + } + + len += hyphens; + + if (!(r = dm_pool_alloc(mem, len))) { + log_error("build_dm_name: Allocation failed for %" PRIsize_t + " for %s %s %s.", len, vgname, lvname, layer); + return NULL; + } + + out = r; + _quote_hyphens(&out, vgname); + *out++ = '-'; + _quote_hyphens(&out, lvname); + + if (layer && *layer) { + /* No hyphen if the layer begins with _ e.g. _mlog */ + if (*layer != '_') + *out++ = '-'; + _quote_hyphens(&out, layer); + } + *out = '\0'; + + return r; +} + +char *dm_build_dm_uuid(struct dm_pool *mem, const char *uuid_prefix, const char *lvid, const char *layer) +{ + char *dmuuid; + size_t len; + + if (!layer) + layer = ""; + + len = strlen(uuid_prefix) + strlen(lvid) + strlen(layer) + 2; + + if (!(dmuuid = dm_pool_alloc(mem, len))) { + log_error("build_dm_name: Allocation failed for %" PRIsize_t + " %s %s.", len, lvid, layer); + return NULL; + } + + sprintf(dmuuid, "%s%s%s%s", uuid_prefix, lvid, (*layer) ? "-" : "", layer); + + return dmuuid; +} + +/* + * Copies a string, quoting double quotes with backslashes. + */ +char *dm_escape_double_quotes(char *out, const char *src) +{ + char *buf = out; + + _quote_characters(&buf, src, '\"', '\\', 1); + *buf = '\0'; + + return out; +} + +/* + * Undo quoting in situ. + */ +void dm_unescape_double_quotes(char *src) +{ + _unquote_one_character(src, '\"', '\\'); +} + +/* + * Unescape colons and "at" signs in situ and save the substrings + * starting at the position of the first unescaped colon and the + * first unescaped "at" sign. This is normally used to unescape + * device names used as PVs. + */ +void dm_unescape_colons_and_at_signs(char *src, + char **substr_first_unquoted_colon, + char **substr_first_unquoted_at_sign) +{ + const char *orig_chars = ":@"; + char *arr_substr_first_unquoted[] = {NULL, NULL, NULL}; + + _unquote_characters(src, orig_chars, 2, '\\', arr_substr_first_unquoted); + + if (substr_first_unquoted_colon) + *substr_first_unquoted_colon = arr_substr_first_unquoted[0]; + + if (substr_first_unquoted_at_sign) + *substr_first_unquoted_at_sign = arr_substr_first_unquoted[1]; +} + +int dm_strncpy(char *dest, const char *src, size_t n) +{ + if (memccpy(dest, src, 0, n)) + return 1; + + if (n > 0) + dest[n - 1] = '\0'; + + return 0; +} diff --git a/libdm/misc/dm-ioctl.h b/libdm/misc/dm-ioctl.h index fb11b5c..1cf66fe 100644 --- a/libdm/misc/dm-ioctl.h +++ b/libdm/misc/dm-ioctl.h @@ -1,6 +1,6 @@ /* * Copyright (C) 2001 - 2003 Sistina Software (UK) Limited. - * Copyright (C) 2004 - 2009 Red Hat, Inc. All rights reserved. + * Copyright (C) 2004 - 2012 Red Hat, Inc. All rights reserved. * * This file is released under the LGPL. */ @@ -269,9 +269,9 @@ enum { #define DM_DEV_SET_GEOMETRY _IOWR(DM_IOCTL, DM_DEV_SET_GEOMETRY_CMD, struct dm_ioctl) #define DM_VERSION_MAJOR 4 -#define DM_VERSION_MINOR 19 +#define DM_VERSION_MINOR 23 #define DM_VERSION_PATCHLEVEL 0 -#define DM_VERSION_EXTRA "-ioctl (2010-10-14)" +#define DM_VERSION_EXTRA "-ioctl (2012-07-25)" /* Status bits */ #define DM_READONLY_FLAG (1 << 0) /* In/Out */ @@ -309,6 +309,8 @@ enum { /* * Set this to suspend without flushing queued ios. + * Also disables flushing uncommitted changes in the thin target before + * generating statistics for DM_TABLE_STATUS and DM_DEV_WAIT. */ #define DM_NOFLUSH_FLAG (1 << 11) /* In */ @@ -330,4 +332,10 @@ enum { */ #define DM_UUID_FLAG (1 << 14) /* In */ +/* + * If set, all buffers are wiped after use. Use when sending + * or requesting sensitive data such as an encryption key. + */ +#define DM_SECURE_DATA_FLAG (1 << 15) /* In */ + #endif /* _LINUX_DM_IOCTL_H */ diff --git a/libdm/misc/dm-log-userspace.h b/libdm/misc/dm-log-userspace.h index 7cacb18..3317938 100644 --- a/libdm/misc/dm-log-userspace.h +++ b/libdm/misc/dm-log-userspace.h @@ -52,15 +52,20 @@ * Payload-to-userspace: * A single string containing all the argv arguments separated by ' 's * Payload-to-kernel: - * None. ('data_size' in the dm_ulog_request struct should be 0.) + * The name of the device that is used as the backing store for the log + * data. 'dm_get_device' will be called on this device. ('dm_put_device' + * will be called on this device automatically after calling DM_ULOG_DTR.) + * If there is no device needed for log data, 'data_size' in the + * dm_ulog_request struct should be 0. * * The UUID contained in the dm_ulog_request structure is the reference that * will be used by all request types to a specific log. The constructor must - * record this assotiation with instance created. + * record this assotiation with the instance created. * * When the request has been processed, user-space must return the - * dm_ulog_request to the kernel - setting the 'error' field and - * 'data_size' appropriately. + * dm_ulog_request to the kernel - setting the 'error' field, filling the + * data field with the log device if necessary, and setting 'data_size' + * appropriately. */ #define DM_ULOG_CTR 1 @@ -363,13 +368,26 @@ * various request types above. The remaining 24-bits are currently * set to zero and are reserved for future use and compatibility concerns. * - * User-space should always use DM_ULOG_REQUEST_TYPE to aquire the + * User-space should always use DM_ULOG_REQUEST_TYPE to acquire the * request type from the 'request_type' field to maintain forward compatibility. */ #define DM_ULOG_REQUEST_MASK 0xFF #define DM_ULOG_REQUEST_TYPE(request_type) \ (DM_ULOG_REQUEST_MASK & (request_type)) +/* + * DM_ULOG_REQUEST_VERSION is incremented when there is a + * change to the way information is passed between kernel + * and userspace. This could be a structure change of + * dm_ulog_request or a change in the way requests are + * issued/handled. Changes are outlined here: + * version 1: Initial implementation + * version 2: DM_ULOG_CTR allowed to return a string containing a + * device name that is to be registered with DM via + * 'dm_get_device'. + */ +#define DM_ULOG_REQUEST_VERSION 2 + struct dm_ulog_request { /* * The local unique identifier (luid) and the universally unique @@ -383,8 +401,9 @@ struct dm_ulog_request { */ uint64_t luid; char uuid[DM_UUID_LEN]; - char padding[7]; /* Padding because DM_UUID_LEN = 129 */ + char padding[3]; /* Padding because DM_UUID_LEN = 129 */ + uint32_t version; /* See DM_ULOG_REQUEST_VERSION */ int32_t error; /* Used to report back processing errors */ uint32_t seq; /* Sequence number for request */ diff --git a/libdm/mm/dbg_malloc.c b/libdm/mm/dbg_malloc.c index db7f5f3..d37083f 100644 --- a/libdm/mm/dbg_malloc.c +++ b/libdm/mm/dbg_malloc.c @@ -1,6 +1,6 @@ /* * Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved. - * Copyright (C) 2004-2007 Red Hat, Inc. All rights reserved. + * Copyright (C) 2004-2011 Red Hat, Inc. All rights reserved. * * This file is part of the device-mapper userspace tools. * @@ -15,6 +15,10 @@ #include "dmlib.h" +#ifdef VALGRIND_POOL +#include "memcheck.h" +#endif + #include <assert.h> #include <stdarg.h> @@ -115,7 +119,9 @@ void *dm_malloc_aux_debug(size_t s, const char *file, int line) /* log_debug("Allocated: %u %u %u", nb->id, _mem_stats.blocks_allocated, _mem_stats.bytes); */ - +#ifdef VALGRIND_POOL + VALGRIND_MAKE_MEM_UNDEFINED(nb + 1, s); +#endif return nb + 1; } @@ -141,11 +147,13 @@ void dm_free_aux(void *p) /* sanity check */ assert(mb->magic == p); - +#ifdef VALGRIND_POOL + VALGRIND_MAKE_MEM_DEFINED(p, mb->length); +#endif /* check data at the far boundary */ - ptr = ((char *) mb) + sizeof(struct memblock) + mb->length; + ptr = (char *) p + mb->length; for (i = 0; i < sizeof(unsigned long); i++) - if (*ptr++ != (char) mb->id) + if (ptr[i] != (char) mb->id) assert(!"Damage at far end of block"); /* have we freed this before ? */ @@ -165,9 +173,9 @@ void dm_free_aux(void *p) mb->id = 0; /* stomp a different pattern across the memory */ - ptr = ((char *) mb) + sizeof(struct memblock); + ptr = p; for (i = 0; i < mb->length; i++) - *ptr++ = i & 1 ? (char) 0xde : (char) 0xad; + ptr[i] = i & 1 ? (char) 0xde : (char) 0xad; assert(_mem_stats.blocks_allocated); _mem_stats.blocks_allocated--; @@ -184,7 +192,7 @@ void *dm_realloc_aux(void *p, unsigned int s, const char *file, int line) r = dm_malloc_aux_debug(s, file, line); - if (p) { + if (r && p) { memcpy(r, p, mb->length); dm_free_aux(p); } diff --git a/libdm/mm/dbg_malloc.h b/libdm/mm/dbg_malloc.h deleted file mode 100644 index 6de1020..0000000 --- a/libdm/mm/dbg_malloc.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved. - * Copyright (C) 2004-2007 Red Hat, Inc. All rights reserved. - * - * This file is part of the device-mapper userspace tools. - * - * 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 Lesser General Public License v.2.1. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#ifndef _DM_DBG_MALLOC_H -#define _DM_DBG_MALLOC_H - -#include <stdlib.h> -#include <string.h> - -void *malloc_aux(size_t s, const char *file, int line); -#define dm_malloc(s) malloc_aux((s), __FILE__, __LINE__) - -char *dbg_strdup(const char *str); - -#ifdef DEBUG_MEM - -void free_aux(void *p); -void *realloc_aux(void *p, unsigned int s, const char *file, int line); -int dump_memory(void); -void bounds_check(void); - -# define dm_free(p) free_aux(p) -# define dbg_realloc(p, s) realloc_aux(p, s, __FILE__, __LINE__) - -#else - -# define dm_free(p) do {if (p) free(p); } while (0) -# define dbg_realloc(p, s) realloc(p, s) -# define dump_memory() -# define bounds_check() - -#endif - -#endif diff --git a/libdm/mm/pool-debug.c b/libdm/mm/pool-debug.c index 7fde3a6..7f9a0e4 100644 --- a/libdm/mm/pool-debug.c +++ b/libdm/mm/pool-debug.c @@ -33,6 +33,8 @@ struct dm_pool { struct dm_list list; const char *name; void *orig_pool; /* to pair it with first allocation call */ + unsigned locked; + long crc; int begun; struct block *object; @@ -48,7 +50,7 @@ struct dm_pool { struct dm_pool *dm_pool_create(const char *name, size_t chunk_hint) { - struct dm_pool *mem = dm_malloc(sizeof(*mem)); + struct dm_pool *mem = dm_zalloc(sizeof(*mem)); if (!mem) { log_error("Couldn't create memory pool %s (size %" @@ -57,16 +59,6 @@ struct dm_pool *dm_pool_create(const char *name, size_t chunk_hint) } mem->name = name; - mem->begun = 0; - mem->object = 0; - mem->blocks = mem->tail = NULL; - - mem->stats.block_serialno = 0; - mem->stats.blocks_allocated = 0; - mem->stats.blocks_max = 0; - mem->stats.bytes = 0; - mem->stats.maxbytes = 0; - mem->orig_pool = mem; #ifdef DEBUG_POOL @@ -81,6 +73,10 @@ static void _free_blocks(struct dm_pool *p, struct block *b) { struct block *n; + if (p->locked) + log_error(INTERNAL_ERROR "_free_blocks from locked pool %s", + p->name); + while (b) { p->stats.bytes -= b->size; p->stats.blocks_allocated--; @@ -119,6 +115,10 @@ void *dm_pool_alloc(struct dm_pool *p, size_t s) static void _append_block(struct dm_pool *p, struct block *b) { + if (p->locked) + log_error(INTERNAL_ERROR "_append_blocks to locked pool %s", + p->name); + if (p->tail) { p->tail->next = b; p->tail = b; @@ -146,7 +146,7 @@ static struct block *_new_block(size_t s, unsigned alignment) * I don't think LVM will use anything but default * align. */ - assert(alignment == DEFAULT_ALIGNMENT); + assert(alignment <= DEFAULT_ALIGNMENT); if (!b) { log_error("Out of memory"); @@ -226,6 +226,10 @@ int dm_pool_grow_object(struct dm_pool *p, const void *extra, size_t delta) struct block *new; size_t new_size; + if (p->locked) + log_error(INTERNAL_ERROR "Grow objects in locked pool %s", + p->name); + if (!delta) delta = strlen(extra); @@ -248,7 +252,7 @@ int dm_pool_grow_object(struct dm_pool *p, const void *extra, size_t delta) } p->object = new; - memcpy(new->data + new_size - delta, extra, delta); + memcpy((char*)new->data + new_size - delta, extra, delta); return 1; } @@ -270,3 +274,19 @@ void dm_pool_abandon_object(struct dm_pool *p) p->begun = 0; p->object = NULL; } + +static long _pool_crc(const struct dm_pool *p) +{ +#ifndef DEBUG_ENFORCE_POOL_LOCKING +#warning pool crc not implemented with pool debug +#endif + return 0; +} + +static int _pool_protect(struct dm_pool *p, int prot) +{ +#ifdef DEBUG_ENFORCE_POOL_LOCKING +#warning pool mprotect not implemented with pool debug +#endif + return 1; +} diff --git a/libdm/mm/pool-fast.c b/libdm/mm/pool-fast.c index ebd982e..61e076a 100644 --- a/libdm/mm/pool-fast.c +++ b/libdm/mm/pool-fast.c @@ -1,6 +1,6 @@ /* * Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved. - * Copyright (C) 2004-2010 Red Hat, Inc. All rights reserved. + * Copyright (C) 2004-2011 Red Hat, Inc. All rights reserved. * * This file is part of the device-mapper userspace tools. * @@ -14,23 +14,27 @@ */ #ifdef VALGRIND_POOL -#include "valgrind/memcheck.h" +#include "memcheck.h" #endif #include "dmlib.h" +#include <malloc.h> struct chunk { char *begin, *end; struct chunk *prev; -}; +} __attribute__((aligned(8))); struct dm_pool { struct dm_list list; struct chunk *chunk, *spare_chunk; /* spare_chunk is a one entry free list to stop 'bobbling' */ + const char *name; size_t chunk_size; size_t object_len; unsigned object_alignment; + int locked; + long crc; }; static void _align_chunk(struct chunk *c, unsigned alignment); @@ -51,6 +55,7 @@ struct dm_pool *dm_pool_create(const char *name, size_t chunk_hint) return 0; } + p->name = name; /* round chunk_hint up to the next power of 2 */ p->chunk_size = chunk_hint + sizeof(struct chunk); while (new_size < p->chunk_size) @@ -236,6 +241,9 @@ void *dm_pool_end_object(struct dm_pool *p) void dm_pool_abandon_object(struct dm_pool *p) { +#ifdef VALGRIND_POOL + VALGRIND_MAKE_MEM_NOACCESS(p->chunk, p->object_len); +#endif p->object_len = 0; p->object_alignment = DEFAULT_ALIGNMENT; } @@ -250,12 +258,28 @@ static struct chunk *_new_chunk(struct dm_pool *p, size_t s) struct chunk *c; if (p->spare_chunk && - ((p->spare_chunk->end - p->spare_chunk->begin) >= s)) { + ((p->spare_chunk->end - p->spare_chunk->begin) >= (ptrdiff_t)s)) { /* reuse old chunk */ c = p->spare_chunk; p->spare_chunk = 0; } else { - if (!(c = dm_malloc(s))) { +#ifdef DEBUG_ENFORCE_POOL_LOCKING + if (!pagesize) { + pagesize = getpagesize(); /* lvm_pagesize(); */ + pagesize_mask = pagesize - 1; + } + /* + * Allocate page aligned size so malloc could work. + * Otherwise page fault would happen from pool unrelated + * memory writes of internal malloc pointers. + */ +# define aligned_malloc(s) (posix_memalign((void**)&c, pagesize, \ + ALIGN_ON_PAGE(s)) == 0) +#else +# define aligned_malloc(s) (c = dm_malloc(s)) +#endif /* DEBUG_ENFORCE_POOL_LOCKING */ + if (!aligned_malloc(s)) { +#undef aligned_malloc log_error("Out of memory. Requested %" PRIsize_t " bytes.", s); return NULL; @@ -276,11 +300,59 @@ static struct chunk *_new_chunk(struct dm_pool *p, size_t s) static void _free_chunk(struct chunk *c) { - if (c) { #ifdef VALGRIND_POOL - VALGRIND_MAKE_MEM_UNDEFINED(c, c->end - (char *) c); +# ifdef DEBUG_MEM + if (c) + VALGRIND_MAKE_MEM_UNDEFINED(c + 1, c->end - (char *) (c + 1)); +# endif +#endif +#ifdef DEBUG_ENFORCE_POOL_LOCKING + /* since DEBUG_MEM is using own memory list */ + free(c); /* for posix_memalign() */ +#else + dm_free(c); +#endif +} + + +/** + * Calc crc/hash from pool's memory chunks with internal pointers + */ +static long _pool_crc(const struct dm_pool *p) +{ + long crc_hash = 0; +#ifndef DEBUG_ENFORCE_POOL_LOCKING + const struct chunk *c; + const long *ptr, *end; + + for (c = p->chunk; c; c = c->prev) { + end = (const long *) (c->begin < c->end ? (long) c->begin & ~7: (long) c->end); + ptr = (const long *) c; +#ifdef VALGRIND_POOL + VALGRIND_MAKE_MEM_DEFINED(ptr, (end - ptr) * sizeof(*end)); #endif + while (ptr < end) { + crc_hash += *ptr++; + crc_hash += (crc_hash << 10); + crc_hash ^= (crc_hash >> 6); + } + } +#endif /* DEBUG_ENFORCE_POOL_LOCKING */ + + return crc_hash; +} + +static int _pool_protect(struct dm_pool *p, int prot) +{ +#ifdef DEBUG_ENFORCE_POOL_LOCKING + struct chunk *c; - dm_free(c); + for (c = p->chunk; c; c = c->prev) { + if (mprotect(c, (size_t) ((c->end - (char *) c) - 1), prot) != 0) { + log_sys_error("mprotect", ""); + return 0; + } } +#endif + return 1; } diff --git a/libdm/mm/pool.c b/libdm/mm/pool.c index 35bfffa..b81a9b6 100644 --- a/libdm/mm/pool.c +++ b/libdm/mm/pool.c @@ -1,6 +1,6 @@ /* * Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved. - * Copyright (C) 2004-2005 Red Hat, Inc. All rights reserved. + * Copyright (C) 2004-2011 Red Hat, Inc. All rights reserved. * * This file is part of the device-mapper userspace tools. * @@ -14,11 +14,31 @@ */ #include "dmlib.h" +#include <sys/mman.h> /* FIXME: thread unsafe */ static DM_LIST_INIT(_dm_pools); void dm_pools_check_leaks(void); +#ifdef DEBUG_ENFORCE_POOL_LOCKING +#ifdef DEBUG_POOL +#error Do not use DEBUG_POOL with DEBUG_ENFORCE_POOL_LOCKING +#endif + +/* + * Use mprotect system call to ensure all locked pages are not writable. + * Generates segmentation fault with write access to the locked pool. + * + * - Implementation is using posix_memalign() to get page aligned + * memory blocks (could be implemented also through malloc). + * - Only pool-fast is properly handled for now. + * - Checksum is slower compared to mprotect. + */ +static size_t pagesize = 0; +static size_t pagesize_mask = 0; +#define ALIGN_ON_PAGE(size) (((size) + (pagesize_mask)) & ~(pagesize_mask)) +#endif + #ifdef DEBUG_POOL #include "pool-debug.c" #else @@ -27,7 +47,7 @@ void dm_pools_check_leaks(void); char *dm_pool_strdup(struct dm_pool *p, const char *str) { - char *ret = dm_pool_alloc(p, strlen(str) + 1); + char *ret = dm_pool_alloc_aligned(p, strlen(str) + 1, 2); if (ret) strcpy(ret, str); @@ -37,7 +57,7 @@ char *dm_pool_strdup(struct dm_pool *p, const char *str) char *dm_pool_strndup(struct dm_pool *p, const char *str, size_t n) { - char *ret = dm_pool_alloc(p, n + 1); + char *ret = dm_pool_alloc_aligned(p, n + 1, 2); if (ret) { strncpy(ret, str, n); @@ -71,7 +91,93 @@ void dm_pools_check_leaks(void) p->orig_pool, p->name, p->stats.bytes); #else - log_error(" [%p]", p); + log_error(" [%p] %s", p, p->name); #endif } + log_error(INTERNAL_ERROR "Unreleased memory pool(s) found."); +} + +/** + * Status of locked pool. + * + * \param p + * Pool to be tested for lock status. + * + * \return + * 1 when the pool is locked, 0 otherwise. + */ +int dm_pool_locked(struct dm_pool *p) +{ + return p->locked; +} + +/** + * Lock memory pool. + * + * \param p + * Pool to be locked. + * + * \param crc + * Bool specifies whether to store the pool crc/hash checksum. + * + * \return + * 1 (success) when the pool was preperly locked, 0 otherwise. + */ +int dm_pool_lock(struct dm_pool *p, int crc) +{ + if (p->locked) { + log_error(INTERNAL_ERROR "Pool %s is already locked.", + p->name); + return 0; + } + + if (crc) + p->crc = _pool_crc(p); /* Get crc for pool */ + + if (!_pool_protect(p, PROT_READ)) { + _pool_protect(p, PROT_READ | PROT_WRITE); + return_0; + } + + p->locked = 1; + + log_debug("Pool %s is locked.", p->name); + + return 1; +} + +/** + * Unlock memory pool. + * + * \param p + * Pool to be unlocked. + * + * \param crc + * Bool enables compare of the pool crc/hash with the stored value + * at pool lock. The pool is not properly unlocked if there is a mismatch. + * + * \return + * 1 (success) when the pool was properly unlocked, 0 otherwise. + */ +int dm_pool_unlock(struct dm_pool *p, int crc) +{ + if (!p->locked) { + log_error(INTERNAL_ERROR "Pool %s is already unlocked.", + p->name); + return 0; + } + + p->locked = 0; + + if (!_pool_protect(p, PROT_READ | PROT_WRITE)) + return_0; + + log_debug("Pool %s is unlocked.", p->name); + + if (crc && (p->crc != _pool_crc(p))) { + log_error(INTERNAL_ERROR "Pool %s crc mismatch.", p->name); + return 0; + } + + return 1; } diff --git a/libdm/regex/matcher.c b/libdm/regex/matcher.c index 9590865..aa58d98 100644 --- a/libdm/regex/matcher.c +++ b/libdm/regex/matcher.c @@ -1,6 +1,6 @@ /* * Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved. - * Copyright (C) 2004-2007 Red Hat, Inc. All rights reserved. + * Copyright (C) 2004-2012 Red Hat, Inc. All rights reserved. * * This file is part of the device-mapper userspace tools. * @@ -98,21 +98,27 @@ static void _fill_table(struct dm_regex *m, struct rx_node *rx) m->charsets[m->charsets_entered++] = rx; } -static void _create_bitsets(struct dm_regex *m) +static int _create_bitsets(struct dm_regex *m) { - int i; + unsigned i; + struct rx_node *n; for (i = 0; i < m->num_nodes; i++) { - struct rx_node *n = m->nodes[i]; - n->firstpos = dm_bitset_create(m->scratch, m->num_charsets); - n->lastpos = dm_bitset_create(m->scratch, m->num_charsets); - n->followpos = dm_bitset_create(m->scratch, m->num_charsets); + n = m->nodes[i]; + if (!(n->firstpos = dm_bitset_create(m->scratch, m->num_charsets))) + return_0; + if (!(n->lastpos = dm_bitset_create(m->scratch, m->num_charsets))) + return_0; + if (!(n->followpos = dm_bitset_create(m->scratch, m->num_charsets))) + return_0; } + + return 1; } static void _calc_functions(struct dm_regex *m) { - int i, j, final = 1; + unsigned i, j, final = 1; struct rx_node *rx, *c1, *c2; for (i = 0; i < m->num_nodes; i++) { @@ -206,14 +212,17 @@ static struct dfa_state *_create_state_queue(struct dm_pool *mem, struct dfa_state *dfa, dm_bitset_t bits) { - dfa->bits = dm_bitset_create(mem, bits[0]); /* first element is the size */ + if (!(dfa->bits = dm_bitset_create(mem, bits[0]))) /* first element is the size */ + return_NULL; + dm_bit_copy(dfa->bits, bits); dfa->next = 0; - dfa->final = -1; + dfa->final = -1; + return dfa; } -static void _calc_state(struct dm_regex *m, struct dfa_state *dfa, int a) +static int _calc_state(struct dm_regex *m, struct dfa_state *dfa, int a) { int set_bits = 0, i; dm_bitset_t dfa_bits = dfa->bits; @@ -233,9 +242,12 @@ static void _calc_state(struct dm_regex *m, struct dfa_state *dfa, int a) struct dfa_state *ldfa = ttree_lookup(m->tt, m->bs + 1); if (!ldfa) { /* push */ - ldfa = _create_dfa_state(m->mem); - ttree_insert(m->tt, m->bs + 1, ldfa); - tmp = _create_state_queue(m->scratch, ldfa, m->bs); + if (!(ldfa = _create_dfa_state(m->mem))) + return_0; + + ttree_insert(m->tt, m->bs + 1, ldfa); + if (!(tmp = _create_state_queue(m->scratch, ldfa, m->bs))) + return_0; if (!m->h) m->h = m->t = tmp; else { @@ -247,31 +259,32 @@ static void _calc_state(struct dm_regex *m, struct dfa_state *dfa, int a) dfa->lookup[a] = ldfa; dm_bit_clear_all(m->bs); } + + return 1; } static int _calc_states(struct dm_regex *m, struct rx_node *rx) { unsigned iwidth = (m->num_charsets / DM_BITS_PER_INT) + 1; struct dfa_state *dfa; - int i, a; + struct rx_node *n; + unsigned i; + int a; - m->tt = ttree_create(m->scratch, iwidth); - if (!m->tt) + if (!(m->tt = ttree_create(m->scratch, iwidth))) return_0; if (!(m->bs = dm_bitset_create(m->scratch, m->num_charsets))) return_0; /* build some char maps */ - for (a = 0; a < 256; a++) { - m->charmap[a] = dm_bitset_create(m->scratch, m->num_charsets); - if (!m->charmap[a]) - return_0; - } + for (a = 0; a < 256; a++) + if (!(m->charmap[a] = dm_bitset_create(m->scratch, m->num_charsets))) + return_0; for (i = 0; i < m->num_nodes; i++) { - struct rx_node *n = m->nodes[i]; - if (n->type == CHARSET) { + n = m->nodes[i]; + if (n->type == CHARSET) { for (a = dm_bit_get_first(n->charset); a >= 0; a = dm_bit_get_next(n->charset, a)) dm_bit_set(m->charmap[a], n->charset_index); @@ -279,13 +292,19 @@ static int _calc_states(struct dm_regex *m, struct rx_node *rx) } /* create first state */ - dfa = _create_dfa_state(m->mem); + if (!(dfa = _create_dfa_state(m->mem))) + return_0; + m->start = dfa; ttree_insert(m->tt, rx->firstpos + 1, dfa); /* prime the queue */ - m->h = m->t = _create_state_queue(m->scratch, dfa, rx->firstpos); - m->dfa_copy = dm_bitset_create(m->scratch, m->num_charsets); + if (!(m->h = m->t = _create_state_queue(m->scratch, dfa, rx->firstpos))) + return_0; + + if (!(m->dfa_copy = dm_bitset_create(m->scratch, m->num_charsets))) + return_0; + return 1; } @@ -293,7 +312,7 @@ static int _calc_states(struct dm_regex *m, struct rx_node *rx) * Forces all the dfa states to be calculated up front, ie. what * _calc_states() used to do before we switched to calculating on demand. */ -static void _force_states(struct dm_regex *m) +static int _force_states(struct dm_regex *m) { int a; @@ -306,15 +325,18 @@ static void _force_states(struct dm_regex *m) /* iterate through all the inputs for this state */ dm_bit_clear_all(m->bs); for (a = 0; a < 256; a++) - _calc_state(m, s, a); + if (!_calc_state(m, s, a)) + return_0; } + + return 1; } struct dm_regex *dm_regex_create(struct dm_pool *mem, const char * const *patterns, unsigned num_patterns) { char *all, *ptr; - int i; + unsigned i; size_t len = 0; struct rx_node *rx; struct dm_regex *m; @@ -347,24 +369,29 @@ struct dm_regex *dm_regex_create(struct dm_pool *mem, const char * const *patter m->mem = mem; m->scratch = scratch; m->num_nodes = _count_nodes(rx); - m->num_charsets = _count_charsets(rx); - _enumerate_charsets(rx); - m->nodes = dm_pool_alloc(scratch, sizeof(*m->nodes) * m->num_nodes); - if (!m->nodes) + m->num_charsets = _count_charsets(rx); + _enumerate_charsets(rx); + if (!(m->nodes = dm_pool_alloc(scratch, sizeof(*m->nodes) * m->num_nodes))) goto_bad; - m->charsets = dm_pool_alloc(scratch, sizeof(*m->charsets) * m->num_charsets); - if (!m->charsets) - goto_bad; + if (!(m->charsets = dm_pool_alloc(scratch, sizeof(*m->charsets) * m->num_charsets))) + goto_bad; _fill_table(m, rx); - _create_bitsets(m); + + if (!_create_bitsets(m)) + goto_bad; + _calc_functions(m); - _calc_states(m, rx); + + if (!_calc_states(m, rx)) + goto_bad; + return m; bad: dm_pool_free(mem, m); + return NULL; } @@ -373,14 +400,17 @@ static struct dfa_state *_step_matcher(struct dm_regex *m, int c, struct dfa_sta struct dfa_state *ns; if (!(ns = cs->lookup[(unsigned char) c])) { - _calc_state(m, cs, (unsigned char) c); + if (!_calc_state(m, cs, (unsigned char) c)) + return_NULL; + if (!(ns = cs->lookup[(unsigned char) c])) return NULL; } // yuck, we have to special case the target trans - if (ns->final == -1) - _calc_state(m, ns, TARGET_TRANS); + if ((ns->final == -1) && + !_calc_state(m, ns, TARGET_TRANS)) + return_NULL; if (ns->final && (ns->final > *r)) *r = ns->final; @@ -433,14 +463,14 @@ struct printer { unsigned next_index; }; -static uint32_t randomise_(uint32_t n) +static uint32_t _randomise(uint32_t n) { /* 2^32 - 5 */ uint32_t const prime = (~0) - 4; return n * prime; } -static int seen_(struct node_list *n, struct dfa_state *node, uint32_t *i) +static int _seen(struct node_list *n, struct dfa_state *node, uint32_t *i) { while (n) { if (n->node == node) { @@ -456,32 +486,36 @@ static int seen_(struct node_list *n, struct dfa_state *node, uint32_t *i) /* * Push node if it's not been seen before, returning a unique index. */ -static uint32_t push_node_(struct printer *p, struct dfa_state *node) +static uint32_t _push_node(struct printer *p, struct dfa_state *node) { uint32_t i; - if (seen_(p->pending, node, &i) || - seen_(p->processed, node, &i)) + struct node_list *n; + + if (_seen(p->pending, node, &i) || + _seen(p->processed, node, &i)) return i; - else { - struct node_list *n = dm_pool_alloc(p->mem, sizeof(*n)); - assert(n); - n->node_id = p->next_index++; - n->node = node; - n->next = p->pending; - p->pending = n; - return n->node_id; - } + + if (!(n = dm_pool_alloc(p->mem, sizeof(*n)))) + return_0; + + n->node_id = ++p->next_index; /* start from 1, keep 0 as error code */ + n->node = node; + n->next = p->pending; + p->pending = n; + + return n->node_id; } /* * Pop the front node, and fill out it's previously assigned index. */ -static struct dfa_state *pop_node_(struct printer *p) +static struct dfa_state *_pop_node(struct printer *p) { struct dfa_state *node = NULL; + struct node_list *n; - if (p->pending) { - struct node_list *n = p->pending; + if (p->pending) { + n = p->pending; p->pending = n->next; n->next = p->processed; p->processed = n; @@ -492,22 +526,22 @@ static struct dfa_state *pop_node_(struct printer *p) return node; } -static uint32_t combine_(uint32_t n1, uint32_t n2) +static uint32_t _combine(uint32_t n1, uint32_t n2) { - return ((n1 << 8) | (n1 >> 24)) ^ randomise_(n2); + return ((n1 << 8) | (n1 >> 24)) ^ _randomise(n2); } -static uint32_t fingerprint_(struct printer *p) +static uint32_t _fingerprint(struct printer *p) { int c; uint32_t result = 0; struct dfa_state *node; - while ((node = pop_node_(p))) { - result = combine_(result, node->final < 0 ? 0 : node->final); + while ((node = _pop_node(p))) { + result = _combine(result, (node->final < 0) ? 0 : node->final); for (c = 0; c < 256; c++) - result = combine_(result, - push_node_(p, node->lookup[c])); + result = _combine(result, + _push_node(p, node->lookup[c])); } return result; @@ -515,20 +549,27 @@ static uint32_t fingerprint_(struct printer *p) uint32_t dm_regex_fingerprint(struct dm_regex *regex) { - uint32_t result; struct printer p; + uint32_t result = 0; struct dm_pool *mem = dm_pool_create("regex fingerprint", 1024); - _force_states(regex); + if (!mem) + return_0; + + if (!_force_states(regex)) + goto_out; - assert(mem); p.mem = mem; p.pending = NULL; p.processed = NULL; p.next_index = 0; - push_node_(&p, regex->start); - result = fingerprint_(&p); + if (!_push_node(&p, regex->start)) + goto_out; + + result = _fingerprint(&p); +out: dm_pool_destroy(mem); + return result; } diff --git a/libdm/regex/ttree.c b/libdm/regex/ttree.c index ec97c98..00b371e 100644 --- a/libdm/regex/ttree.c +++ b/libdm/regex/ttree.c @@ -28,6 +28,7 @@ struct ttree { struct node *root; }; +__attribute__((nonnull(1))) static struct node **_lookup_single(struct node **c, unsigned int k) { while (*c) { |