| /* |
| * Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved. |
| * Copyright (C) 2004-2012 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 |
| */ |
| |
| #include "dmlib.h" |
| #include "libdm-targets.h" |
| #include "libdm-common.h" |
| #include "kdev_t.h" |
| #include "dm-ioctl.h" |
| |
| #include <stdarg.h> |
| #include <sys/param.h> |
| #include <sys/ioctl.h> |
| #include <fcntl.h> |
| #include <dirent.h> |
| |
| #ifdef UDEV_SYNC_SUPPORT |
| # include <sys/types.h> |
| # include <sys/ipc.h> |
| # include <sys/sem.h> |
| # include <libudev.h> |
| #endif |
| |
| #ifdef __linux__ |
| # include <linux/fs.h> |
| #endif |
| |
| #ifdef HAVE_SELINUX |
| # include <selinux/selinux.h> |
| #endif |
| #ifdef HAVE_SELINUX_LABEL_H |
| # 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 |
| #ifdef _SEM_SEMUN_UNDEFINED |
| union semun |
| { |
| int val; /* value for SETVAL */ |
| struct semid_ds *buf; /* buffer for IPC_STAT & IPC_SET */ |
| unsigned short int *array; /* array for GETALL & SETALL */ |
| struct seminfo *__buf; /* buffer for IPC_INFO */ |
| }; |
| #endif |
| #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 */ |
| static const char _mountinfo[] = "/proc/self/mountinfo"; |
| |
| #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; |
| #endif |
| |
| static int _udev_disabled = 0; |
| |
| #ifdef UDEV_SYNC_SUPPORT |
| static int _semaphore_supported = -1; |
| static int _udev_running = -1; |
| static int _sync_with_udev = 1; |
| static int _udev_checking = 1; |
| #endif |
| |
| void dm_lib_init(void) |
| { |
| const char *env; |
| |
| if (getenv("DM_DISABLE_UDEV")) |
| _udev_disabled = 1; |
| |
| _name_mangling_mode = DEFAULT_DM_NAME_MANGLING; |
| if ((env = getenv(DM_DEFAULT_NAME_MANGLING_MODE_ENV_VAR_NAME))) { |
| 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; |
| } |
| } |
| |
| /* |
| * 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_or_class, |
| const char *f, va_list ap) |
| { |
| static int _abort_on_internal_errors = -1; |
| FILE *out = (level & _LOG_STDERR) ? stderr : stdout; |
| |
| level &= ~_LOG_STDERR; |
| |
| if (level <= _LOG_WARN || _verbose) { |
| if (level < _LOG_WARN) |
| out = stderr; |
| vfprintf(out, f, ap); |
| fputc('\n', out); |
| } |
| |
| if (_abort_on_internal_errors < 0) |
| /* Set when env DM_ABORT_ON_INTERNAL_ERRORS is not "0" */ |
| _abort_on_internal_errors = |
| strcmp(getenv("DM_ABORT_ON_INTERNAL_ERRORS") ? : "0", "0"); |
| |
| if (_abort_on_internal_errors && |
| !strncmp(f, INTERNAL_ERROR, sizeof(INTERNAL_ERROR) - 1)) |
| abort(); |
| } |
| |
| __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_or_class, |
| const char *f, ...) |
| { |
| va_list ap; |
| |
| va_start(ap, f); |
| _default_log_line(level, file, line, dm_errno_or_class, f, ap); |
| va_end(ap); |
| } |
| |
| __attribute__((format(printf, 4, 5))) |
| static void _default_log(int level, const char *file, |
| int line, const char *f, ...) |
| { |
| va_list ap; |
| |
| va_start(ap, f); |
| _default_log_line(level, file, line, 0, f, ap); |
| va_end(ap); |
| } |
| |
| dm_log_fn dm_log = _default_log; |
| dm_log_with_errno_fn dm_log_with_errno = _default_log_with_errno; |
| |
| void dm_log_init(dm_log_fn fn) |
| { |
| if (fn) |
| dm_log = fn; |
| else |
| dm_log = _default_log; |
| |
| dm_log_with_errno = _default_log_with_errno; |
| } |
| |
| int dm_log_is_non_default(void) |
| { |
| return (dm_log == _default_log) ? 0 : 1; |
| } |
| |
| void dm_log_with_errno_init(dm_log_with_errno_fn fn) |
| { |
| if (fn) |
| dm_log_with_errno = fn; |
| else |
| dm_log_with_errno = _default_log_with_errno; |
| |
| dm_log = _default_log; |
| } |
| |
| void dm_log_init_verbose(int level) |
| { |
| _verbose = level; |
| } |
| |
| static int _build_dev_path(char *buffer, size_t len, const char *dev_name) |
| { |
| int r; |
| |
| /* If there's a /, assume caller knows what they're doing */ |
| if (strchr(dev_name, '/')) |
| r = dm_strncpy(buffer, dev_name, len); |
| else |
| r = (dm_snprintf(buffer, len, "%s/%s", |
| _dm_dir, dev_name) < 0) ? 0 : 1; |
| if (!r) |
| log_error("Failed to build dev path for \"%s\".", dev_name); |
| |
| return r; |
| } |
| |
| int dm_get_library_version(char *version, size_t size) |
| { |
| return dm_strncpy(version, DM_LIB_VERSION, size); |
| } |
| |
| void inc_suspended(void) |
| { |
| _suspended_dev_counter++; |
| log_debug_activation("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_activation("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)); |
| |
| if (!dmt) { |
| log_error("dm_task_create: malloc(%" PRIsize_t ") failed", |
| sizeof(*dmt)); |
| return NULL; |
| } |
| |
| if (!dm_check_version()) { |
| dm_free(dmt); |
| return_NULL; |
| } |
| |
| dmt->type = type; |
| dmt->minor = -1; |
| dmt->major = -1; |
| dmt->allow_default_major_fallback = 1; |
| dmt->uid = DM_DEVICE_UID; |
| dmt->gid = DM_DEVICE_GID; |
| dmt->mode = DM_DEVICE_MODE; |
| dmt->no_open_count = 0; |
| dmt->read_ahead = DM_READ_AHEAD_AUTO; |
| dmt->read_ahead_flags = 0; |
| dmt->event_nr = 0; |
| dmt->cookie_set = 0; |
| dmt->query_inactive_table = 0; |
| dmt->new_uuid = 0; |
| dmt->secure_data = 0; |
| dmt->record_timestamp = 0; |
| |
| return dmt; |
| } |
| |
| /* |
| * Find the name associated with a given device number by scanning _dm_dir. |
| */ |
| 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 st; |
| int r = 0; |
| |
| if (!(d = opendir(_dm_dir))) { |
| log_sys_error("opendir", _dm_dir); |
| return 0; |
| } |
| |
| while ((dirent = readdir(d))) { |
| name = dirent->d_name; |
| |
| if (!strcmp(name, ".") || !strcmp(name, "..")) |
| continue; |
| |
| if (dm_snprintf(path, sizeof(path), "%s/%s", _dm_dir, |
| name) == -1) { |
| log_error("Couldn't create path for %s", name); |
| continue; |
| } |
| |
| if (stat(path, &st)) |
| continue; |
| |
| if (st.st_rdev == st_rdev) { |
| strncpy(buf, name, buf_len); |
| r = 1; |
| break; |
| } |
| } |
| |
| if (closedir(d)) |
| log_sys_error("closedir", _dm_dir); |
| |
| return r; |
| } |
| |
| static int _is_whitelisted_char(char c) |
| { |
| /* |
| * Actually, DM supports any character in a device name. |
| * This whitelist is just for proper integration with udev. |
| */ |
| 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 (_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 (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 (str[i] == '\\' && str[i+1] == 'x') { |
| if (!sscanf(&str[i+2], "%2x%s", &code, str_rest)) { |
| log_debug_activation("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_activation("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 (!(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 = NULL; |
| size_t len; |
| |
| if (dmt->type == DM_DEVICE_CREATE) { |
| log_error("Name \"%s\" invalid. It contains \"/\".", path); |
| return 0; |
| } |
| |
| if (!stat(path, &st1)) { |
| /* |
| * Found directly. |
| * If supplied path points to same device as last component |
| * under /dev/mapper, use that name directly. |
| */ |
| 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 { |
| /* Not found. */ |
| /* If there is exactly one '/' try a prefix of /dev */ |
| if ((len = strlen(path)) < 3 || path[0] == '/' || |
| dm_count_chars(path, len, '/') != 1) { |
| log_error("Device %s not found", path); |
| return 0; |
| } |
| if (dm_snprintf(buf, sizeof(buf), "%s/../%s", _dm_dir, path) == -1) { |
| log_error("Couldn't create /dev path for %s", path); |
| return 0; |
| } |
| if (stat(buf, &st1)) { |
| log_error("Device %s not found", path); |
| return 0; |
| } |
| /* Found */ |
| } |
| |
| /* |
| * If we don't have the dm name yet, Call _find_dm_name_of_device() to |
| * scan _dm_dir for a match. |
| */ |
| if (!final_name) { |
| 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 (!*newname) { |
| log_error("Non empty new name is required."); |
| 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_activation("New device name mangled [%s]: %s --> %s", |
| mangling_mode == DM_STRING_MANGLING_AUTO ? "auto" : "hex", |
| newname, mangled_name); |
| newname = mangled_name; |
| } |
| |
| dm_free(dmt->newname); |
| 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_activation("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); |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| int dm_task_set_major(struct dm_task *dmt, int major) |
| { |
| dmt->major = major; |
| dmt->allow_default_major_fallback = 0; |
| |
| return 1; |
| } |
| |
| int dm_task_set_minor(struct dm_task *dmt, int minor) |
| { |
| dmt->minor = minor; |
| |
| return 1; |
| } |
| |
| int dm_task_set_major_minor(struct dm_task *dmt, int major, int minor, |
| int allow_default_major_fallback) |
| { |
| dmt->major = major; |
| dmt->minor = minor; |
| dmt->allow_default_major_fallback = allow_default_major_fallback; |
| |
| return 1; |
| } |
| |
| int dm_task_set_uid(struct dm_task *dmt, uid_t uid) |
| { |
| dmt->uid = uid; |
| |
| return 1; |
| } |
| |
| int dm_task_set_gid(struct dm_task *dmt, gid_t gid) |
| { |
| dmt->gid = gid; |
| |
| return 1; |
| } |
| |
| int dm_task_set_mode(struct dm_task *dmt, mode_t mode) |
| { |
| dmt->mode = 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; |
| |
| if (!dmt->head) |
| dmt->head = dmt->tail = t; |
| else { |
| dmt->tail->next = t; |
| dmt->tail = t; |
| } |
| |
| return 1; |
| } |
| |
| #ifdef HAVE_SELINUX |
| static int _selabel_lookup(const char *path, mode_t mode, |
| security_context_t *scontext) |
| { |
| #ifdef HAVE_SELINUX_LABEL_H |
| if (!_selabel_handle && |
| !(_selabel_handle = selabel_open(SELABEL_CTX_FILE, NULL, 0))) { |
| log_error("selabel_open failed: %s", strerror(errno)); |
| return 0; |
| } |
| |
| if (selabel_lookup(_selabel_handle, scontext, path, mode)) { |
| log_debug_activation("selabel_lookup failed for %s: %s", |
| path, strerror(errno)); |
| return 0; |
| } |
| #else |
| if (matchpathcon(path, mode, scontext)) { |
| log_debug_activation("matchpathcon failed for %s: %s", |
| path, strerror(errno)); |
| return 0; |
| } |
| #endif |
| return 1; |
| } |
| #endif |
| |
| #ifdef HAVE_SELINUX |
| static int _is_selinux_enabled(void) |
| { |
| static int _tested = 0; |
| static int _enabled; |
| |
| if (!_tested) { |
| _tested = 1; |
| _enabled = is_selinux_enabled(); |
| } |
| |
| return _enabled; |
| } |
| #endif |
| |
| int dm_prepare_selinux_context(const char *path, mode_t mode) |
| { |
| #ifdef HAVE_SELINUX |
| security_context_t scontext = NULL; |
| |
| if (_is_selinux_enabled() <= 0) |
| return 1; |
| |
| if (path) { |
| if (!_selabel_lookup(path, mode, &scontext)) |
| return_0; |
| |
| log_debug_activation("Preparing SELinux context for %s to %s.", path, scontext); |
| } |
| else |
| log_debug_activation("Resetting SELinux context to default value."); |
| |
| if (setfscreatecon(scontext) < 0) { |
| log_sys_error("setfscreatecon", (path ? : "SELinux context reset")); |
| freecon(scontext); |
| return 0; |
| } |
| |
| freecon(scontext); |
| #endif |
| return 1; |
| } |
| |
| int dm_set_selinux_context(const char *path, mode_t mode) |
| { |
| #ifdef HAVE_SELINUX |
| security_context_t scontext = NULL; |
| |
| if (_is_selinux_enabled() <= 0) |
| return 1; |
| |
| if (!_selabel_lookup(path, mode, &scontext)) |
| return_0; |
| |
| log_debug_activation("Setting SELinux context for %s to %s.", path, scontext); |
| |
| if ((lsetfilecon(path, scontext) < 0) && (errno != ENOTSUP)) { |
| log_sys_error("lsetfilecon", path); |
| freecon(scontext); |
| return 0; |
| } |
| |
| freecon(scontext); |
| #endif |
| return 1; |
| } |
| |
| void selinux_release(void) |
| { |
| #ifdef HAVE_SELINUX_LABEL_H |
| if (_selabel_handle) |
| selabel_close(_selabel_handle); |
| _selabel_handle = NULL; |
| #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 warn_if_udev_failed) |
| { |
| char path[PATH_MAX]; |
| struct stat info; |
| dev_t dev = MKDEV((dev_t)major, (dev_t)minor); |
| mode_t old_mask; |
| |
| if (!_build_dev_path(path, sizeof(path), dev_name)) |
| return_0; |
| |
| if (stat(path, &info) >= 0) { |
| if (!S_ISBLK(info.st_mode)) { |
| log_error("A non-block device file at '%s' " |
| "is already present", path); |
| return 0; |
| } |
| |
| /* If right inode already exists we don't touch uid etc. */ |
| if (info.st_rdev == dev) |
| return 1; |
| |
| if (unlink(path) < 0) { |
| log_error("Unable to unlink device node for '%s'", |
| dev_name); |
| return 0; |
| } |
| } 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); |
| |
| /* The node may already have been created by udev. So ignore EEXIST. */ |
| if (mknod(path, S_IFBLK | mode, dev) < 0 && errno != EEXIST) { |
| 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; |
| } |
| umask(old_mask); |
| (void) dm_prepare_selinux_context(NULL, 0); |
| |
| if (chown(path, uid, gid) < 0) { |
| log_sys_error("chown", path); |
| return 0; |
| } |
| |
| log_debug_activation("Created %s", path); |
| |
| return 1; |
| } |
| |
| static int _rm_dev_node(const char *dev_name, int warn_if_udev_failed) |
| { |
| char path[PATH_MAX]; |
| struct stat info; |
| |
| if (!_build_dev_path(path, sizeof(path), dev_name)) |
| return_0; |
| if (stat(path, &info) < 0) |
| return 1; |
| 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); |
| |
| /* udev may already have deleted the node. Ignore ENOENT. */ |
| if (unlink(path) < 0 && errno != ENOENT) { |
| log_error("Unable to unlink device node for '%s'", dev_name); |
| return 0; |
| } |
| |
| log_debug_activation("Removed %s", path); |
| |
| return 1; |
| } |
| |
| static int _rename_dev_node(const char *old_name, const char *new_name, |
| int warn_if_udev_failed) |
| { |
| char oldpath[PATH_MAX]; |
| char newpath[PATH_MAX]; |
| struct stat info; |
| |
| if (!_build_dev_path(oldpath, sizeof(oldpath), old_name) || |
| !_build_dev_path(newpath, sizeof(newpath), new_name)) |
| return_0; |
| |
| if (stat(newpath, &info) == 0) { |
| if (!S_ISBLK(info.st_mode)) { |
| log_error("A non-block device file at '%s' " |
| "is already present", newpath); |
| return 0; |
| } |
| else if (_warn_if_op_needed(warn_if_udev_failed)) { |
| if (stat(oldpath, &info) < 0 && |
| errno == ENOENT) |
| /* assume udev already deleted this */ |
| return 1; |
| else { |
| log_warn("The node %s should have been renamed to %s " |
| "by udev but old node is still present. " |
| "Falling back to direct old node removal.", |
| oldpath, newpath); |
| return _rm_dev_node(old_name, 0); |
| } |
| } |
| |
| if (unlink(newpath) < 0) { |
| if (errno == EPERM) { |
| /* devfs, entry has already been renamed */ |
| return 1; |
| } |
| log_error("Unable to unlink device node for '%s'", |
| new_name); |
| return 0; |
| } |
| } |
| 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.", |
| oldpath, newpath); |
| |
| /* udev may already have renamed the node. Ignore ENOENT. */ |
| /* FIXME: when renaming to target mangling mode "none" with udev |
| * while there are some blacklisted characters in the node name, |
| * udev will remove the old_node, but fails to properly rename |
| * to new_node. The libdevmapper code tries to call |
| * rename(old_node,new_node), but that won't do anything |
| * since the old node is already removed by udev. |
| * For example renaming 'a\x20b' to 'a b': |
| * - udev removes 'a\x20b' |
| * - udev creates 'a' and 'b' (since it considers the ' ' as a delimiter |
| * - libdevmapper checks udev has done the rename properly |
| * - libdevmapper calls stat(new_node) and it does not see it |
| * - libdevmapper calls rename(old_node,new_node) |
| * - the rename is a NOP since the old_node does not exist anymore |
| * |
| * However, this situation is very rare - why would anyone need |
| * to rename to an unsupported mode??? So a fix for this would be |
| * just for completeness. |
| */ |
| if (rename(oldpath, newpath) < 0 && errno != ENOENT) { |
| log_error("Unable to rename device node from '%s' to '%s'", |
| old_name, new_name); |
| return 0; |
| } |
| |
| log_debug_activation("Renamed %s to %s", oldpath, newpath); |
| |
| return 1; |
| } |
| |
| #ifdef __linux__ |
| static int _open_dev_node(const char *dev_name) |
| { |
| int fd = -1; |
| char path[PATH_MAX]; |
| |
| if (!_build_dev_path(path, sizeof(path), dev_name)) |
| return fd; |
| |
| if ((fd = open(path, O_RDONLY, 0)) < 0) |
| log_sys_error("open", path); |
| |
| return fd; |
| } |
| |
| 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_activation("%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; |
| } |
| |
| if ((fd = _open_dev_node(dev_name)) < 0) |
| return_0; |
| |
| if (ioctl(fd, BLKRAGET, &read_ahead_long)) { |
| log_sys_error("BLKRAGET", dev_name); |
| *read_ahead = 0; |
| r = 0; |
| } else { |
| *read_ahead = (uint32_t) read_ahead_long; |
| log_debug_activation("%s: read ahead is %" PRIu32, dev_name, *read_ahead); |
| } |
| |
| if (close(fd)) |
| log_sys_debug("close", dev_name); |
| |
| return r; |
| } |
| |
| 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_activation("%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), FMTu32, |
| (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; |
| } |
| |
| if ((fd = _open_dev_node(dev_name)) < 0) |
| return_0; |
| |
| if (ioctl(fd, BLKRASET, read_ahead_long)) { |
| log_sys_error("BLKRASET", dev_name); |
| r = 0; |
| } |
| |
| if (close(fd)) |
| log_sys_debug("close", dev_name); |
| |
| return r; |
| } |
| |
| 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; |
| |
| if (read_ahead == DM_READ_AHEAD_AUTO) |
| return 1; |
| |
| if (read_ahead == DM_READ_AHEAD_NONE) |
| read_ahead = 0; |
| |
| if (read_ahead_flags & DM_READ_AHEAD_MINIMUM_FLAG) { |
| if (!get_dev_node_read_ahead(dev_name, major, minor, ¤t_read_ahead)) |
| return_0; |
| |
| if (current_read_ahead >= read_ahead) { |
| log_debug_activation("%s: retaining kernel read ahead of %" PRIu32 |
| " (requested %" PRIu32 ")", |
| dev_name, current_read_ahead, read_ahead); |
| return 1; |
| } |
| } |
| |
| return _set_read_ahead(dev_name, major, minor, read_ahead); |
| } |
| |
| #else |
| |
| int get_dev_node_read_ahead(const char *dev_name, uint32_t *read_ahead) |
| { |
| *read_ahead = 0; |
| |
| return 1; |
| } |
| |
| 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; |
| } |
| #endif |
| |
| typedef enum { |
| NODE_ADD, |
| NODE_DEL, |
| NODE_RENAME, |
| 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 warn_if_udev_failed) |
| { |
| switch (type) { |
| case NODE_ADD: |
| return _add_dev_node(dev_name, major, minor, uid, gid, |
| mode, warn_if_udev_failed); |
| case NODE_DEL: |
| return _rm_dev_node(dev_name, warn_if_udev_failed); |
| case NODE_RENAME: |
| return _rename_dev_node(old_name, dev_name, warn_if_udev_failed); |
| case NODE_READ_AHEAD: |
| 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; |
| node_op_t type; |
| char *dev_name; |
| uint32_t major; |
| uint32_t minor; |
| uid_t uid; |
| gid_t gid; |
| mode_t mode; |
| uint32_t read_ahead; |
| uint32_t read_ahead_flags; |
| char *old_name; |
| int warn_if_udev_failed; |
| unsigned rely_on_udev; |
| char names[0]; |
| }; |
| |
| static void _store_str(char **pos, char **ptr, const char *str) |
| { |
| strcpy(*pos, str); |
| *ptr = *pos; |
| *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_activation("%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_activation("%s: %s NODE_DEL%s%s", nop->dev_name, action_str, rely, verify); |
| break; |
| case NODE_RENAME: |
| log_debug_activation("%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_activation("%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 warn_if_udev_failed, |
| unsigned rely_on_udev) |
| { |
| struct node_op_parms *nop; |
| struct dm_list *noph, *nopht; |
| size_t len = strlen(dev_name) + strlen(old_name) + 2; |
| char *pos; |
| |
| /* |
| * Note: warn_if_udev_failed must have valid content |
| */ |
| 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)) { |
| _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))) { |
| log_error("Insufficient memory to stack mknod operation"); |
| return 0; |
| } |
| |
| pos = nop->names; |
| nop->type = type; |
| nop->major = major; |
| nop->minor = minor; |
| nop->uid = uid; |
| nop->gid = gid; |
| nop->mode = mode; |
| nop->read_ahead = read_ahead; |
| nop->read_ahead_flags = read_ahead_flags; |
| 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; |
| } |
| |
| static void _pop_node_ops(void) |
| { |
| struct dm_list *noph, *nopht; |
| struct node_op_parms *nop; |
| |
| dm_list_iterate_safe(noph, nopht, &_node_ops) { |
| nop = dm_list_item(noph, struct node_op_parms); |
| 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, unsigned rely_on_udev) |
| { |
| return _stack_node_op(NODE_ADD, dev_name, major, minor, uid, |
| gid, mode, "", 0, 0, check_udev, rely_on_udev); |
| } |
| |
| int rename_dev_node(const char *old_name, const char *new_name, int check_udev, unsigned rely_on_udev) |
| { |
| return _stack_node_op(NODE_RENAME, new_name, 0, 0, 0, |
| 0, 0, old_name, 0, 0, check_udev, rely_on_udev); |
| } |
| |
| int rm_dev_node(const char *dev_name, int check_udev, unsigned rely_on_udev) |
| { |
| return _stack_node_op(NODE_DEL, dev_name, 0, 0, 0, |
| 0, 0, "", 0, 0, check_udev, rely_on_udev); |
| } |
| |
| 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; |
| |
| 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) |
| { |
| _pop_node_ops(); |
| } |
| |
| 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 (*src != '/') { |
| log_debug_activation("Invalid directory value, %s: " |
| "not an absolute name.", src); |
| return 0; |
| } |
| |
| len = strlen(src); |
| slash = src[len-1] == '/' ? "" : "/"; |
| |
| if (dm_snprintf(dir, max_len, "%s%s%s", src, slash, suffix ? suffix : "") < 0) { |
| log_debug_activation("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; |
| } |
| |
| 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 _is_octal(int a) |
| { |
| return (((a) & ~7) == '0'); |
| } |
| |
| /* Convert mangled mountinfo into normal ASCII string */ |
| static void _unmangle_mountinfo_string(const char *src, char *buf) |
| { |
| while (*src) { |
| if ((*src == '\\') && |
| _is_octal(src[1]) && _is_octal(src[2]) && _is_octal(src[3])) { |
| *buf++ = 64 * (src[1] & 7) + 8 * (src[2] & 7) + (src[3] & 7); |
| src += 4; |
| } else |
| *buf++ = *src++; |
| } |
| *buf = '\0'; |
| } |
| |
| /* Parse one line of mountinfo and unmangled target line */ |
| static int _mountinfo_parse_line(const char *line, unsigned *maj, unsigned *min, char *buf) |
| { |
| char root[PATH_MAX + 1]; /* sscanf needs extra '\0' */ |
| char target[PATH_MAX + 1]; |
| |
| /* TODO: maybe detect availability of %ms glib support ? */ |
| if (sscanf(line, "%*u %*u %u:%u %" DM_TO_STRING(PATH_MAX) |
| "s %" DM_TO_STRING(PATH_MAX) "s", |
| maj, min, root, target) < 4) { |
| log_error("Failed to parse mountinfo line."); |
| return 0; |
| } |
| |
| _unmangle_mountinfo_string(target, buf); |
| |
| return 1; |
| } |
| |
| /* |
| * Function to operate on individal mountinfo line, |
| * minor, major and mount target are parsed and unmangled |
| */ |
| int dm_mountinfo_read(dm_mountinfo_line_callback_fn read_fn, void *cb_data) |
| { |
| FILE *minfo; |
| char buffer[2 * PATH_MAX]; |
| char target[PATH_MAX]; |
| unsigned maj, min; |
| int r = 1; |
| |
| if (!(minfo = fopen(_mountinfo, "r"))) { |
| if (errno != ENOENT) |
| log_sys_error("fopen", _mountinfo); |
| else |
| log_sys_debug("fopen", _mountinfo); |
| return 0; |
| } |
| |
| while (!feof(minfo) && fgets(buffer, sizeof(buffer), minfo)) |
| if (!_mountinfo_parse_line(buffer, &maj, &min, target) || |
| !read_fn(buffer, maj, min, target, cb_data)) { |
| stack; |
| r = 0; |
| break; |
| } |
| |
| if (fclose(minfo)) |
| log_sys_error("fclose", _mountinfo); |
| |
| return r; |
| } |
| |
| 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)) { |
| if (errno != ENOENT) |
| 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; |
| } |
| |
| struct mountinfo_s { |
| unsigned maj; |
| unsigned min; |
| int mounted; |
| }; |
| |
| static int _device_has_mounted_fs(char *buffer, unsigned major, unsigned minor, |
| char *target, void *cb_data) |
| { |
| struct mountinfo_s *data = cb_data; |
| char kernel_dev_name[PATH_MAX]; |
| |
| if ((major == data->maj) && (minor == data->min)) { |
| if (!dm_device_get_name(major, minor, 1, kernel_dev_name, |
| sizeof(kernel_dev_name))) { |
| stack; |
| *kernel_dev_name = '\0'; |
| } |
| log_verbose("Device %s (%u:%u) appears to be mounted on %s.", |
| kernel_dev_name, major, minor, target); |
| data->mounted = 1; |
| } |
| |
| return 1; |
| } |
| |
| int dm_device_has_mounted_fs(uint32_t major, uint32_t minor) |
| { |
| char kernel_dev_name[PATH_MAX]; |
| struct mountinfo_s data = { |
| .maj = major, |
| .min = minor, |
| }; |
| |
| if (!dm_mountinfo_read(_device_has_mounted_fs, &data)) |
| stack; |
| |
| if (data.mounted) |
| return 1; |
| /* |
| * TODO: Verify dm_mountinfo_read() is superset |
| * and remove sysfs check (namespaces) |
| */ |
| /* 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; |
| int r = 0; |
| |
| if (!(dmt = dm_task_create(DM_DEVICE_MKNODES))) |
| return 0; |
| |
| if (name && !dm_task_set_name(dmt, name)) |
| goto out; |
| |
| if (!dm_task_no_open_count(dmt)) |
| goto out; |
| |
| r = dm_task_run(dmt); |
| |
| out: |
| dm_task_destroy(dmt); |
| return r; |
| } |
| |
| int dm_driver_version(char *version, size_t size) |
| { |
| struct dm_task *dmt; |
| int r = 0; |
| |
| if (!(dmt = dm_task_create(DM_DEVICE_VERSION))) |
| return 0; |
| |
| if (!dm_task_run(dmt)) |
| log_error("Failed to get driver version"); |
| |
| if (!dm_task_get_driver_version(dmt, version, size)) |
| goto out; |
| |
| r = 1; |
| |
| out: |
| dm_task_destroy(dmt); |
| return r; |
| } |
| |
| static void _set_cookie_flags(struct dm_task *dmt, uint16_t flags) |
| { |
| if (!dm_cookie_supported()) |
| return; |
| |
| if (_udev_disabled) { |
| /* |
| * If udev is disabled, hardcode this functionality: |
| * - we want libdm to create the nodes |
| * - we don't want the /dev/mapper and any subsystem |
| * related content to be created by udev if udev |
| * rules are installed |
| */ |
| flags &= ~DM_UDEV_DISABLE_LIBRARY_FALLBACK; |
| flags |= DM_UDEV_DISABLE_DM_RULES_FLAG | DM_UDEV_DISABLE_SUBSYSTEM_RULES_FLAG; |
| } |
| |
| dmt->event_nr = flags << DM_UDEV_FLAGS_SHIFT; |
| } |
| |
| #ifndef UDEV_SYNC_SUPPORT |
| void dm_udev_set_sync_support(int sync_with_udev) |
| { |
| } |
| |
| int dm_udev_get_sync_support(void) |
| { |
| return 0; |
| } |
| |
| void dm_udev_set_checking(int checking) |
| { |
| } |
| |
| int dm_udev_get_checking(void) |
| { |
| return 0; |
| } |
| |
| int dm_task_set_cookie(struct dm_task *dmt, uint32_t *cookie, uint16_t flags) |
| { |
| _set_cookie_flags(dmt, flags); |
| |
| *cookie = 0; |
| dmt->cookie_set = 1; |
| |
| return 1; |
| } |
| |
| int dm_udev_complete(uint32_t cookie) |
| { |
| return 1; |
| } |
| |
| int dm_udev_wait(uint32_t cookie) |
| { |
| update_devs(); |
| |
| return 1; |
| } |
| |
| #else /* UDEV_SYNC_SUPPORT */ |
| |
| static int _check_semaphore_is_supported(void) |
| { |
| int maxid; |
| union semun arg; |
| struct seminfo seminfo; |
| |
| arg.__buf = &seminfo; |
| maxid = semctl(0, 0, SEM_INFO, arg); |
| |
| if (maxid < 0) { |
| log_warn("Kernel not configured for semaphores (System V IPC). " |
| "Not using udev synchronisation code."); |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| static int _check_udev_is_running(void) |
| { |
| struct udev *udev; |
| struct udev_queue *udev_queue; |
| int r; |
| |
| if (!(udev = udev_new())) |
| goto_bad; |
| |
| if (!(udev_queue = udev_queue_new(udev))) { |
| udev_unref(udev); |
| goto_bad; |
| } |
| |
| if (!(r = udev_queue_get_udev_is_active(udev_queue))) |
| log_debug_activation("Udev is not running. " |
| "Not using udev synchronisation code."); |
| |
| udev_queue_unref(udev_queue); |
| udev_unref(udev); |
| |
| return r; |
| |
| bad: |
| log_error("Could not get udev state. Assuming udev is not running."); |
| return 0; |
| } |
| |
| static void _check_udev_sync_requirements_once(void) |
| { |
| if (_semaphore_supported < 0) |
| _semaphore_supported = _check_semaphore_is_supported(); |
| |
| if (_udev_running < 0) { |
| _udev_running = _check_udev_is_running(); |
| if (_udev_disabled && _udev_running) |
| log_warn("Udev is running and DM_DISABLE_UDEV environment variable is set. " |
| "Bypassing udev, device-mapper library will manage device " |
| "nodes in device directory."); |
| } |
| } |
| |
| void dm_udev_set_sync_support(int sync_with_udev) |
| { |
| _check_udev_sync_requirements_once(); |
| _sync_with_udev = sync_with_udev; |
| } |
| |
| int dm_udev_get_sync_support(void) |
| { |
| _check_udev_sync_requirements_once(); |
| |
| return !_udev_disabled && _semaphore_supported && |
| dm_cookie_supported() &&_udev_running && _sync_with_udev; |
| } |
| |
| void dm_udev_set_checking(int checking) |
| { |
| if ((_udev_checking = checking)) |
| log_debug_activation("DM udev checking enabled"); |
| else |
| log_debug_activation("DM udev checking disabled"); |
| } |
| |
| int dm_udev_get_checking(void) |
| { |
| return _udev_checking; |
| } |
| |
| static int _get_cookie_sem(uint32_t cookie, int *semid) |
| { |
| if (cookie >> 16 != DM_COOKIE_MAGIC) { |
| log_error("Could not continue to access notification " |
| "semaphore identified by cookie value %" |
| PRIu32 " (0x%x). Incorrect cookie prefix.", |
| cookie, cookie); |
| return 0; |
| } |
| |
| if ((*semid = semget((key_t) cookie, 1, 0)) >= 0) |
| return 1; |
| |
| switch (errno) { |
| case ENOENT: |
| log_error("Could not find notification " |
| "semaphore identified by cookie " |
| "value %" PRIu32 " (0x%x)", |
| cookie, cookie); |
| break; |
| case EACCES: |
| log_error("No permission to access " |
| "notificaton semaphore identified " |
| "by cookie value %" PRIu32 " (0x%x)", |
| cookie, cookie); |
| break; |
| default: |
| log_error("Failed to access notification " |
| "semaphore identified by cookie " |
| "value %" PRIu32 " (0x%x): %s", |
| cookie, cookie, strerror(errno)); |
| break; |
| } |
| |
| return 0; |
| } |
| |
| 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", |
| semid, cookie, strerror(errno)); |
| return 0; |
| } |
| |
| 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_activation("Udev cookie 0x%" PRIx32 " (semid %d) incremented to %d", |
| cookie, semid, val); |
| |
| return 1; |
| } |
| |
| 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) { |
| case EAGAIN: |
| log_error("semid %d: semop failed for cookie " |
| "0x%" PRIx32 ": " |
| "incorrect semaphore state", |
| semid, cookie); |
| break; |
| default: |
| log_error("semid %d: semop failed for cookie " |
| "0x%" PRIx32 ": %s", |
| semid, cookie, strerror(errno)); |
| break; |
| } |
| return 0; |
| } |
| |
| log_debug_activation("Udev cookie 0x%" PRIx32 " (semid %d) decremented to %d", |
| cookie, semid, val - 1); |
| |
| return 1; |
| } |
| |
| static int _udev_notify_sem_destroy(uint32_t cookie, int semid) |
| { |
| if (semctl(semid, 0, IPC_RMID, 0) < 0) { |
| log_error("Could not cleanup notification semaphore " |
| "identified by cookie value %" PRIu32 " (0x%x): %s", |
| cookie, cookie, strerror(errno)); |
| return 0; |
| } |
| |
| log_debug_activation("Udev cookie 0x%" PRIx32 " (semid %d) destroyed", cookie, |
| semid); |
| |
| return 1; |
| } |
| |
| 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; |
| |
| if ((fd = open("/dev/urandom", O_RDONLY)) < 0) { |
| log_error("Failed to open /dev/urandom " |
| "to create random cookie value"); |
| *cookie = 0; |
| return 0; |
| } |
| |
| /* Generate random cookie value. Be sure it is unique and non-zero. */ |
| do { |
| /* FIXME Handle non-error returns from read(). Move _io() into libdm? */ |
| if (read(fd, &base_cookie, sizeof(base_cookie)) != sizeof(base_cookie)) { |
| log_error("Failed to initialize notification cookie"); |
| goto bad; |
| } |
| |
| gen_cookie = DM_COOKIE_MAGIC << 16 | base_cookie; |
| |
| if (base_cookie && (gen_semid = semget((key_t) gen_cookie, |
| 1, 0600 | IPC_CREAT | IPC_EXCL)) < 0) { |
| switch (errno) { |
| case EEXIST: |
| /* if the semaphore key exists, we |
| * simply generate another random one */ |
| base_cookie = 0; |
| break; |
| case ENOMEM: |
| log_error("Not enough memory to create " |
| "notification semaphore"); |
| goto bad; |
| case ENOSPC: |
| log_error("Limit for the maximum number " |
| "of semaphores reached. You can " |
| "check and set the limits in " |
| "/proc/sys/kernel/sem."); |
| goto bad; |
| default: |
| log_error("Failed to create notification " |
| "semaphore: %s", strerror(errno)); |
| goto bad; |
| } |
| } |
| } while (!base_cookie); |
| |
| log_debug_activation("Udev cookie 0x%" PRIx32 " (semid %d) created", |
| gen_cookie, gen_semid); |
| |
| sem_arg.val = 1; |
| |
| if (semctl(gen_semid, 0, SETVAL, sem_arg) < 0) { |
| log_error("semid %d: semctl failed: %s", gen_semid, strerror(errno)); |
| /* We have to destroy just created semaphore |
| * so it won't stay in the system. */ |
| (void) _udev_notify_sem_destroy(gen_cookie, gen_semid); |
| goto bad; |
| } |
| |
| 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_activation("Udev cookie 0x%" PRIx32 " (semid %d) incremented to %d", |
| gen_cookie, gen_semid, val); |
| |
| if (close(fd)) |
| stack; |
| |
| *semid = gen_semid; |
| *cookie = gen_cookie; |
| |
| return 1; |
| |
| bad: |
| if (close(fd)) |
| stack; |
| |
| *cookie = 0; |
| |
| return 0; |
| } |
| |
| int dm_udev_create_cookie(uint32_t *cookie) |
| { |
| int semid; |
| |
| if (!dm_udev_get_sync_support()) { |
| *cookie = 0; |
| return 1; |
| } |
| |
| 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; |
| |
| _set_cookie_flags(dmt, flags); |
| |
| if (!dm_udev_get_sync_support()) { |
| *cookie = 0; |
| dmt->cookie_set = 1; |
| return 1; |
| } |
| |
| if (*cookie) { |
| if (!_get_cookie_sem(*cookie, &semid)) |
| goto_bad; |
| } else if (!_udev_notify_sem_create(cookie, &semid)) |
| goto_bad; |
| |
| if (!_udev_notify_sem_inc(*cookie, semid)) { |
| log_error("Could not set notification semaphore " |
| "identified by cookie value %" PRIu32 " (0x%x)", |
| *cookie, *cookie); |
| goto bad; |
| } |
| |
| dmt->event_nr |= ~DM_UDEV_FLAGS_MASK & *cookie; |
| dmt->cookie_set = 1; |
| |
| log_debug_activation("Udev cookie 0x%" PRIx32 " (semid %d) assigned to " |
| "%s task(%d) with flags%s%s%s%s%s%s%s%s%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 & DM_SUBSYSTEM_UDEV_FLAG0) ? " SUBSYSTEM_0" : " ", |
| (flags & DM_SUBSYSTEM_UDEV_FLAG1) ? " SUBSYSTEM_1" : " ", |
| (flags & DM_SUBSYSTEM_UDEV_FLAG2) ? " SUBSYSTEM_2" : " ", |
| (flags & DM_SUBSYSTEM_UDEV_FLAG3) ? " SUBSYSTEM_3" : " ", |
| (flags & DM_SUBSYSTEM_UDEV_FLAG4) ? " SUBSYSTEM_4" : " ", |
| (flags & DM_SUBSYSTEM_UDEV_FLAG5) ? " SUBSYSTEM_5" : " ", |
| (flags & DM_SUBSYSTEM_UDEV_FLAG6) ? " SUBSYSTEM_6" : " ", |
| (flags & DM_SUBSYSTEM_UDEV_FLAG7) ? " SUBSYSTEM_7" : " ", |
| flags); |
| |
| return 1; |
| |
| bad: |
| dmt->event_nr = 0; |
| return 0; |
| } |
| |
| int dm_udev_complete(uint32_t cookie) |
| { |
| int semid; |
| |
| if (!cookie || !dm_udev_get_sync_support()) |
| return 1; |
| |
| if (!_get_cookie_sem(cookie, &semid)) |
| return_0; |
| |
| if (!_udev_notify_sem_dec(cookie, semid)) { |
| log_error("Could not signal waiting process using notification " |
| "semaphore identified by cookie value %" PRIu32 " (0x%x)", |
| cookie, cookie); |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| static int _udev_wait(uint32_t cookie) |
| { |
| int semid; |
| struct sembuf sb = {0, 0, 0}; |
| |
| if (!cookie || !dm_udev_get_sync_support()) |
| return 1; |
| |
| if (!_get_cookie_sem(cookie, &semid)) |
| return_0; |
| |
| if (!_udev_notify_sem_dec(cookie, semid)) { |
| log_error("Failed to set a proper state for notification " |
| "semaphore identified by cookie value %" PRIu32 " (0x%x) " |
| "to initialize waiting for incoming notifications.", |
| cookie, cookie); |
| (void) _udev_notify_sem_destroy(cookie, semid); |
| return 0; |
| } |
| |
| log_debug_activation("Udev cookie 0x%" PRIx32 " (semid %d) waiting for zero", |
| cookie, semid); |
| |
| repeat_wait: |
| if (semop(semid, &sb, 1) < 0) { |
| if (errno == EINTR) |
| goto repeat_wait; |
| else if (errno == EIDRM) |
| return 1; |
| |
| log_error("Could not set wait state for notification semaphore " |
| "identified by cookie value %" PRIu32 " (0x%x): %s", |
| cookie, cookie, strerror(errno)); |
| (void) _udev_notify_sem_destroy(cookie, semid); |
| return 0; |
| } |
| |
| 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 */ |