mirror of
git://sourceware.org/git/lvm2.git
synced 2024-12-21 13:34:40 +03:00
ee13f265f0
Instead of compiling 2 log call for 2 different logging functions, and runtime decide which version to use - use only 'newer' function and when user sets his own OLD dm_log logging translate it runtime for old arg list set. The positive part is - we get shorter generated library, on the negative part this translation means, we always have evaluate all args and print the message into local on stack buffer, before we can pass this buffer to the users' logging function with proper expected parameters (and such function may later decide to discard logging based on message level so whole printing was unnecessary).
2649 lines
62 KiB
C
2649 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, ...)
|
|
{
|
|
va_list ap;
|
|
char buf[2 * PATH_MAX + 256]; /* big enough for most messages */
|
|
|
|
va_start(ap, f);
|
|
vsnprintf(buf, sizeof(buf), f, ap);
|
|
va_end(ap);
|
|
|
|
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, ...)
|
|
{
|
|
va_list ap;
|
|
char buf[2 * PATH_MAX + 256]; /* big enough for most messages */
|
|
|
|
va_start(ap, f);
|
|
vsnprintf(buf, sizeof(buf), f, ap);
|
|
va_end(ap);
|
|
|
|
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;
|
|
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;
|
|
}
|
|
|
|
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 */
|