/*
* Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved.
* Copyright (C) 2004-2007 Red Hat, Inc. All rights reserved.
*
* This file is part of the device-mapper userspace tools.
*
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU Lesser General Public License v.2.1.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/* dm major version no for running kernel */
static unsigned _dm_version = DM_VERSION_MAJOR;
static unsigned _dm_version_minor = 0;
static unsigned _dm_version_patchlevel = 0;
static int _log_suppress = 0;
/*
* If the kernel dm driver only supports one major number
* we store it in _dm_device_major. Otherwise we indicate
* which major numbers have been claimed by device-mapper
* in _dm_bitset.
*/
static unsigned _dm_multiple_major_support = 1;
static dm_bitset_t _dm_bitset = NULL;
static uint32_t _dm_device_major = 0;
static int _control_fd = -1;
static int _version_checked = 0;
static int _version_ok = 1;
static unsigned _ioctl_buffer_double_factor = 0;
/*
* Support both old and new major numbers to ease the transition.
* Clumsy, but only temporary.
*/
#if DM_VERSION_MAJOR == 4 && defined(DM_COMPAT)
const int _dm_compat = 1;
#else
const int _dm_compat = 0;
#endif
#ifdef DM_IOCTLS
/*
* Set number to NULL to populate _dm_bitset - otherwise first
* match is returned.
*/
static int _get_proc_number(const char *file, const char *name,
uint32_t *number)
{
FILE *fl;
char nm[256];
int c;
uint32_t num;
/*
* Returns 1 if exists; 0 if it doesn't; -1 if it's wrong
*/
static int _control_exists(const char *control, uint32_t major, uint32_t minor)
{
struct stat buf;
if (stat(control, &buf) < 0) {
if (errno != ENOENT)
log_sys_error("stat", control);
return 0;
}
if (!S_ISCHR(buf.st_mode)) {
log_verbose("%s: Wrong inode type", control);
if (!unlink(control))
return 0;
log_sys_error("unlink", control);
return -1;
}
/*
* FIXME Update bitset in long-running process if dm claims new major numbers.
*/
static int _create_dm_bitset(void)
{
#ifdef DM_IOCTLS
struct utsname uts;
if (_dm_bitset || _dm_device_major)
return 1;
if (uname(&uts))
return 0;
/*
* 2.6 kernels are limited to one major number.
* Assume 2.4 kernels are patched not to.
* FIXME Check _dm_version and _dm_version_minor if 2.6 changes this.
*/
if (!strncmp(uts.release, "2.6.", 4))
_dm_multiple_major_support = 0;
if (!_dm_multiple_major_support) {
if (!_get_proc_number(PROC_DEVICES, DM_NAME, &_dm_device_major))
return 0;
return 1;
}
/* Multiple major numbers supported */
if (!(_dm_bitset = dm_bitset_create(NULL, NUMBER_OF_MAJORS)))
return 0;
if (dmi->flags & DM_BUFFER_FULL_FLAG)
/* FIXME Increase buffer size and retry operation (if query) */
log_error("WARNING: libdevmapper buffer too small for data");
case DM_DEVICE_REMOVE:
rm_dev_node(dmt->dev_name, 0);
break;
case DM_DEVICE_RENAME:
rename_dev_node(dmt->dev_name, dmt->newname, 0);
break;
case DM_DEVICE_MKNODES:
if (dmi->flags & DM_EXISTS_FLAG)
add_dev_node(dmt->dev_name, MAJOR(dmi->dev),
MINOR(dmi->dev), dmt->uid,
dmt->gid, dmt->mode, 0);
else
rm_dev_node(dmt->dev_name, 0);
break;
case DM_DEVICE_STATUS:
case DM_DEVICE_TABLE:
if (!_unmarshal_status_v1(dmt, dmi))
goto bad;
break;
case DM_DEVICE_SUSPEND:
case DM_DEVICE_RESUME:
dmt->type = DM_DEVICE_INFO;
if (!dm_task_run(dmt))
goto bad;
dm_free(dmi); /* We'll use what info returned */
return 1;
}
static int _check_version(char *version, size_t size, int log_suppress)
{
struct dm_task *task;
int r;
if (!(task = dm_task_create(DM_DEVICE_VERSION))) {
log_error("Failed to get device-mapper version");
version[0] = '\0';
return 0;
}
if (log_suppress)
_log_suppress = 1;
r = dm_task_run(task);
dm_task_get_driver_version(task, version, size);
dm_task_destroy(task);
_log_suppress = 0;
return r;
}
/*
* Find out device-mapper's major version number the first time
* this is called and whether or not we support it.
*/
int dm_check_version(void)
{
char libversion[64], dmversion[64];
const char *compat = "";
if (_version_checked)
return _version_ok;
_version_checked = 1;
if (_check_version(dmversion, sizeof(dmversion), _dm_compat))
return 1;
if (!_dm_compat)
goto bad;
log_verbose("device-mapper ioctl protocol version %u failed. "
"Trying protocol version 1.", _dm_version);
_dm_version = 1;
if (_check_version(dmversion, sizeof(dmversion), 0)) {
log_verbose("Using device-mapper ioctl protocol version 1");
return 1;
}
if (dmt->minor >= 0) {
if (dmt->major <= 0) {
log_error("Missing major number for persistent device.");
goto bad;
}
if (!_dm_multiple_major_support && dmt->allow_default_major_fallback &&
dmt->major != _dm_device_major) {
log_verbose("Overriding major number of %" PRIu32
" with %" PRIu32 " for persistent device.",
dmt->major, _dm_device_major);
dmt->major = _dm_device_major;
}
/* Does driver support device number referencing? */
if (_dm_version_minor < 3 && !dmt->dev_name && !dmt->uuid && dmi->dev) {
if (!_lookup_dev_name(dmi->dev, dmi->name, sizeof(dmi->name))) {
log_error("Unable to find name for device (%" PRIu32
":%" PRIu32 ")", dmt->major, dmt->minor);
goto bad;
}
log_verbose("device (%" PRIu32 ":%" PRIu32 ") is %s "
"for compatibility with old kernel",
dmt->major, dmt->minor, dmi->name);
}
/* FIXME Until resume ioctl supplies name, use dev_name for readahead */
if (dmt->dev_name && (dmt->type != DM_DEVICE_RESUME || dmt->minor < 0 ||
dmt->major < 0))
strncpy(dmi->name, dmt->dev_name, sizeof(dmi->name));
if (dmt->uuid)
strncpy(dmi->uuid, dmt->uuid, sizeof(dmi->uuid));
if (dmt->type == DM_DEVICE_SUSPEND)
dmi->flags |= DM_SUSPEND_FLAG;
if (dmt->no_flush)
dmi->flags |= DM_NOFLUSH_FLAG;
if (dmt->read_only)
dmi->flags |= DM_READONLY_FLAG;
if (dmt->skip_lockfs)
dmi->flags |= DM_SKIP_LOCKFS_FLAG;
if (dmt->query_inactive_table) {
if (_dm_version_minor < 16)
log_warn("WARNING: Inactive table query unsupported "
"by kernel. It will use live table.");
dmi->flags |= DM_QUERY_INACTIVE_TABLE_FLAG;
}
static int _process_mapper_dir(struct dm_task *dmt)
{
struct dirent *dirent;
DIR *d;
const char *dir;
int r = 1;
dir = dm_dir();
if (!(d = opendir(dir))) {
log_sys_error("opendir", dir);
return 0;
}
while ((dirent = readdir(d))) {
if (!strcmp(dirent->d_name, ".") ||
!strcmp(dirent->d_name, "..") ||
!strcmp(dirent->d_name, "control"))
continue;
dm_task_set_name(dmt, dirent->d_name);
dm_task_run(dmt);
}
if (closedir(d))
log_sys_error("closedir", dir);
return r;
}
static int _process_all_v4(struct dm_task *dmt)
{
struct dm_task *task;
struct dm_names *names;
unsigned next = 0;
int r = 1;
if (!(task = dm_task_create(DM_DEVICE_LIST)))
return 0;
if (!dm_task_run(task)) {
r = 0;
goto out;
}
if (!(names = dm_task_get_names(task))) {
r = 0;
goto out;
}
if (!names->dev)
goto out;
do {
names = (void *) names + next;
if (!dm_task_set_name(dmt, names->name)) {
r = 0;
goto out;
}
if (!dm_task_run(dmt))
r = 0;
next = names->next;
} while (next);
out:
dm_task_destroy(task);
return r;
}
static int _mknodes_v4(struct dm_task *dmt)
{
(void) _process_mapper_dir(dmt);
return _process_all_v4(dmt);
}
/*
* If an operation that uses a cookie fails, decrement the
* semaphore instead of udev.
*/
static int _udev_complete(struct dm_task *dmt)
{
uint32_t cookie;
if (dmt->cookie_set) {
/* strip flags from the cookie and use cookie magic instead */
cookie = (dmt->event_nr & ~DM_UDEV_FLAGS_MASK) |
(DM_COOKIE_MAGIC << DM_UDEV_FLAGS_SHIFT);
return dm_udev_complete(cookie);
}
return 1;
}
static int _create_and_load_v4(struct dm_task *dmt)
{
struct dm_task *task;
int r;
/* Use new task struct to create the device */
if (!(task = dm_task_create(DM_DEVICE_CREATE))) {
log_error("Failed to create device-mapper task struct");
_udev_complete(dmt);
return 0;
}
/* Copy across relevant fields */
if (dmt->dev_name && !dm_task_set_name(task, dmt->dev_name)) {
dm_task_destroy(task);
_udev_complete(dmt);
return 0;
}
static int _reload_with_suppression_v4(struct dm_task *dmt)
{
struct dm_task *task;
struct target *t1, *t2;
int r;
/* New task to get existing table information */
if (!(task = dm_task_create(DM_DEVICE_TABLE))) {
log_error("Failed to create device-mapper task struct");
return 0;
}
/* Copy across relevant fields */
if (dmt->dev_name && !dm_task_set_name(task, dmt->dev_name)) {
dm_task_destroy(task);
return 0;
}
if (dmt->uuid && !dm_task_set_uuid(task, dmt->uuid)) {
dm_task_destroy(task);
return 0;
}
if (dmt->type == DM_DEVICE_TABLE)
dmi->flags |= DM_STATUS_TABLE_FLAG;
dmi->flags |= DM_EXISTS_FLAG; /* FIXME */
if (dmt->no_open_count)
dmi->flags |= DM_SKIP_BDGET_FLAG;
/*
* Prevent udev vs. libdevmapper race when processing nodes and
* symlinks. This can happen when the udev rules are installed and
* udev synchronisation code is enabled in libdevmapper but the
* software using libdevmapper does not make use of it (by not calling
* dm_task_set_cookie before). We need to instruct the udev rules not
* to be applied at all in this situation so we can gracefully fallback
* to libdevmapper's node and symlink creation code.
*/
if (dm_udev_get_sync_support() && !dmt->cookie_set &&
(dmt->type == DM_DEVICE_RESUME ||
dmt->type == DM_DEVICE_REMOVE ||
dmt->type == DM_DEVICE_RENAME)) {
log_debug("Cookie value is not set while trying to call "
"DM_DEVICE_RESUME, DM_DEVICE_REMOVE or DM_DEVICE_RENAME "
"ioctl. Please, consider using libdevmapper's udev "
"synchronisation interface or disable it explicitly "
"by calling dm_udev_set_sync_support(0).");
log_debug("Switching off device-mapper and all subsystem related "
"udev rules. Falling back to libdevmapper node creation.");
/*
* Disable general dm and subsystem rules but keep dm disk rules
* if not flagged out explicitly before. We need /dev/disk content
* for the software that expects it.
*/
dmi->event_nr |= (DM_UDEV_DISABLE_DM_RULES_FLAG |
DM_UDEV_DISABLE_SUBSYSTEM_RULES_FLAG) <<
DM_UDEV_FLAGS_SHIFT;
}
if ((dmt->type == DM_DEVICE_RELOAD) && dmt->suppress_identical_reload)
return _reload_with_suppression_v4(dmt);
if (!_open_control()) {
_udev_complete(dmt);
return 0;
}
/* FIXME Detect and warn if cookie set but should not be. */
repeat_ioctl:
if (!(dmi = _do_dm_ioctl(dmt, command, _ioctl_buffer_double_factor))) {
_udev_complete(dmt);
return 0;
}
if (dmi->flags & DM_BUFFER_FULL_FLAG) {
switch (dmt->type) {
case DM_DEVICE_LIST_VERSIONS:
case DM_DEVICE_LIST:
case DM_DEVICE_DEPS:
case DM_DEVICE_STATUS:
case DM_DEVICE_TABLE:
case DM_DEVICE_WAITEVENT:
_ioctl_buffer_double_factor++;
dm_free(dmi);
goto repeat_ioctl;
default:
log_error("WARNING: libdevmapper buffer too small for data");
}
}
switch (dmt->type) {
case DM_DEVICE_CREATE:
if (dmt->dev_name && *dmt->dev_name)
add_dev_node(dmt->dev_name, MAJOR(dmi->dev),
MINOR(dmi->dev), dmt->uid, dmt->gid,
dmt->mode, check_udev);
break;
case DM_DEVICE_REMOVE:
/* FIXME Kernel needs to fill in dmi->name */
if (dmt->dev_name)
rm_dev_node(dmt->dev_name, check_udev);
break;
case DM_DEVICE_RENAME:
/* FIXME Kernel needs to fill in dmi->name */
if (dmt->dev_name)
rename_dev_node(dmt->dev_name, dmt->newname,
check_udev);
break;
case DM_DEVICE_RESUME:
/* FIXME Kernel needs to fill in dmi->name */
set_dev_node_read_ahead(dmt->dev_name, dmt->read_ahead,
dmt->read_ahead_flags);
break;
case DM_DEVICE_MKNODES:
if (dmi->flags & DM_EXISTS_FLAG)
add_dev_node(dmi->name, MAJOR(dmi->dev),
MINOR(dmi->dev), dmt->uid,
dmt->gid, dmt->mode, 0);
else if (dmt->dev_name)
rm_dev_node(dmt->dev_name, 0);
break;
case DM_DEVICE_STATUS:
case DM_DEVICE_TABLE:
case DM_DEVICE_WAITEVENT:
if (!_unmarshal_status(dmt, dmi))
goto bad;
break;
}
/* Was structure reused? */
if (dmt->dmi.v4)
dm_free(dmt->dmi.v4);
dmt->dmi.v4 = dmi;
return 1;