mirror of
git://sourceware.org/git/lvm2.git
synced 2024-12-30 17:18:21 +03:00
0bf836aa14
clang-tidy: avoid using 'else' after return - give more readable code, and also saves indention level.
2652 lines
62 KiB
C
2652 lines
62 KiB
C
/*
|
|
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 = log_stderr(level) ? stderr : stdout;
|
|
|
|
level = log_level(level);
|
|
|
|
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, int line, 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;
|
|
|
|
/*
|
|
* Wrapper function to reformat new messages to and
|
|
* old style logging which had not used errno parameter
|
|
*
|
|
* As we cannot simply pass '...' to old function we
|
|
* need to process arg list locally and just pass '%s' + buffer
|
|
*/
|
|
__attribute__((format(printf, 5, 6)))
|
|
static void _log_to_default_log(int level,
|
|
const char *file, int line, int dm_errno_or_class,
|
|
const char *f, ...)
|
|
{
|
|
int n;
|
|
va_list ap;
|
|
char buf[2 * PATH_MAX + 256]; /* big enough for most messages */
|
|
|
|
va_start(ap, f);
|
|
n = vsnprintf(buf, sizeof(buf), f, ap);
|
|
va_end(ap);
|
|
|
|
if (n > 0) /* Could be truncated */
|
|
dm_log(level, file, line, "%s", buf);
|
|
}
|
|
|
|
/*
|
|
* Wrapper function take 'old' style message without errno
|
|
* and log it via new logging function with errno arg
|
|
*
|
|
* This minor case may happen if new libdm is used with old
|
|
* recompiled tool that would decided to use new logging,
|
|
* but still would like to use old binary plugins.
|
|
*/
|
|
__attribute__((format(printf, 4, 5)))
|
|
static void _log_to_default_log_with_errno(int level,
|
|
const char *file, int line, const char *f, ...)
|
|
{
|
|
int n;
|
|
va_list ap;
|
|
char buf[2 * PATH_MAX + 256]; /* big enough for most messages */
|
|
|
|
va_start(ap, f);
|
|
n = vsnprintf(buf, sizeof(buf), f, ap);
|
|
va_end(ap);
|
|
|
|
if (n > 0) /* Could be truncated */
|
|
dm_log_with_errno(level, file, line, 0, "%s", buf);
|
|
}
|
|
|
|
void dm_log_init(dm_log_fn fn)
|
|
{
|
|
if (fn) {
|
|
dm_log = fn;
|
|
dm_log_with_errno = _log_to_default_log;
|
|
} 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 && dm_log_with_errno == _default_log_with_errno) ? 0 : 1;
|
|
}
|
|
|
|
void dm_log_with_errno_init(dm_log_with_errno_fn fn)
|
|
{
|
|
if (fn) {
|
|
dm_log = _log_to_default_log_with_errno;
|
|
dm_log_with_errno = fn;
|
|
} else {
|
|
dm_log = _default_log;
|
|
dm_log_with_errno = _default_log_with_errno;
|
|
}
|
|
}
|
|
|
|
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 (lstat(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, info2;
|
|
struct stat *info_block_dev;
|
|
|
|
if (!_build_dev_path(oldpath, sizeof(oldpath), old_name) ||
|
|
!_build_dev_path(newpath, sizeof(newpath), new_name))
|
|
return_0;
|
|
|
|
if (lstat(newpath, &info) == 0) {
|
|
if (S_ISLNK(info.st_mode)) {
|
|
if (stat(newpath, &info2) == 0)
|
|
info_block_dev = &info2;
|
|
else {
|
|
log_sys_error("stat", newpath);
|
|
return 0;
|
|
}
|
|
} else
|
|
info_block_dev = &info;
|
|
|
|
if (!S_ISBLK(info_block_dev->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 (lstat(oldpath, &info) < 0 &&
|
|
errno == ENOENT)
|
|
/* assume udev already deleted this */
|
|
return 1;
|
|
|
|
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;
|
|
}
|
|
|
|
int dm_udev_wait_immediate(uint32_t cookie, int *ready)
|
|
{
|
|
update_devs();
|
|
*ready = 1;
|
|
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* If *nowait is set, return immediately leaving it set if the semaphore
|
|
* is not ready to be decremented to 0. *nowait is cleared if the wait
|
|
* succeeds.
|
|
*/
|
|
static int _udev_wait(uint32_t cookie, int *nowait)
|
|
{
|
|
int semid;
|
|
struct sembuf sb = {0, 0, 0};
|
|
int val;
|
|
|
|
if (!cookie || !dm_udev_get_sync_support())
|
|
return 1;
|
|
|
|
if (!_get_cookie_sem(cookie, &semid))
|
|
return_0;
|
|
|
|
/* Return immediately if the semaphore value exceeds 1? */
|
|
if (*nowait) {
|
|
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 (val > 1)
|
|
return 1;
|
|
|
|
*nowait = 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 nowait = 0;
|
|
int r = _udev_wait(cookie, &nowait);
|
|
|
|
update_devs();
|
|
|
|
return r;
|
|
}
|
|
|
|
int dm_udev_wait_immediate(uint32_t cookie, int *ready)
|
|
{
|
|
int nowait = 1;
|
|
int r = _udev_wait(cookie, &nowait);
|
|
|
|
if (r && nowait) {
|
|
*ready = 0;
|
|
return 1;
|
|
}
|
|
|
|
update_devs();
|
|
*ready = 1;
|
|
|
|
return r;
|
|
}
|
|
#endif /* UDEV_SYNC_SUPPORT */
|