/*
* 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
*/
/*
* Find the name associated with a given device number by scanning _dm_dir.
*/
static char *_find_dm_name_of_device(dev_t st_rdev)
{
const char *name;
char path[PATH_MAX];
struct dirent *dirent;
DIR *d;
struct stat buf;
char *new_name = NULL;
if (!(d = opendir(_dm_dir))) {
log_sys_error("opendir", _dm_dir);
return NULL;
}
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, &buf))
continue;
if (buf.st_rdev == st_rdev) {
if (!(new_name = dm_strdup(name)))
log_error("dm_task_set_name: strdup(%s) failed",
name);
break;
}
}
if (closedir(d))
log_sys_error("closedir", _dm_dir);
return new_name;
}
int dm_task_set_name(struct dm_task *dmt, const char *name)
{
char *pos;
char *new_name = NULL;
char path[PATH_MAX];
struct stat st1, st2;
if (dmt->dev_name) {
dm_free(dmt->dev_name);
dmt->dev_name = NULL;
}
/*
* Path supplied for existing device?
*/
if ((pos = strrchr(name, '/'))) {
if (dmt->type == DM_DEVICE_CREATE) {
log_error("Name \"%s\" invalid. It contains \"/\".", name);
return 0;
}
if (stat(name, &st1)) {
log_error("Device %s not found", name);
return 0;
}
/*
* If supplied path points to same device as last component
* under /dev/mapper, use that name directly. Otherwise call
* _find_dm_name_of_device() to scan _dm_dir for a match.
*/
if (dm_snprintf(path, sizeof(path), "%s/%s", _dm_dir,
pos + 1) == -1) {
log_error("Couldn't create path for %s", pos + 1);
return 0;
}
if (!stat(path, &st2) && (st1.st_rdev == st2.st_rdev))
name = pos + 1;
else if ((new_name = _find_dm_name_of_device(st1.st_rdev)))
name = new_name;
else {
log_error("Device %s not found", name);
return 0;
}
}
if (strlen(name) >= DM_NAME_LEN) {
log_error("Name \"%s\" too long", name);
if (new_name)
dm_free(new_name);
return 0;
}
if (new_name)
dmt->dev_name = new_name;
else if (!(dmt->dev_name = dm_strdup(name))) {
log_error("dm_task_set_name: strdup(%s) failed", name);
return 0;
}
return 1;
}
int dm_task_set_uuid(struct dm_task *dmt, const char *uuid)
{
if (dmt->uuid) {
dm_free(dmt->uuid);
dmt->uuid = NULL;
}
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;
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 (dm_udev_get_sync_support() && check_udev)
log_warn("%s not set up by udev: Falling back to direct "
"node creation.", path);
old_mask = umask(0);
if (mknod(path, S_IFBLK | mode, dev) < 0) {
umask(old_mask);
log_error("Unable to make device node for '%s'", dev_name);
return 0;
}
umask(old_mask);
if (unlink(rpath) < 0) {
log_error("Unable to unlink device node for '%s'", raw_devname);
return 0;
}
log_debug("Removed %s", rpath);
#endif
_build_dev_path(path, sizeof(path), dev_name);
if (stat(path, &info) < 0)
return 1;
else if (dm_udev_get_sync_support() && check_udev)
log_warn("Node %s was not removed by udev. "
"Falling back to direct node removal.", path);
if (unlink(path) < 0) {
log_error("Unable to unlink device node for '%s'", dev_name);
return 0;
}
log_debug("Removed %s", path);
return 1;
}
static int _rename_dev_node(const char *old_name, const char *new_name,
int check_udev)
{
char oldpath[PATH_MAX];
char newpath[PATH_MAX];
struct stat info;
#ifdef __NetBSD__
char rpath[PATH_MAX];
char nrpath[PATH_MAX];
char raw_devname[DM_NAME_LEN+1]; /* r + other device name */
char nraw_devname[DM_NAME_LEN+1]; /* r + other device name */
if (stat(nrpath, &info) == 0) {
if (S_ISBLK(info.st_mode)) {
log_error("A block device file at '%s' "
"is present where raw device should be.", newpath);
return 0;
}
if (unlink(nrpath) < 0) {
log_error("Unable to unlink device node for '%s'",
nraw_devname);
return 0;
}
}
if (rename(rpath, nrpath) < 0) {
log_error("Unable to rename device node from '%s' to '%s'",
raw_devname, nraw_devname);
return 0;
}
if (stat(newpath, &info) == 0) {
if (!S_ISBLK(info.st_mode)) {
log_error("A non-block device file at '%s' "
"is already present", newpath);
return 0;
}
else if (dm_udev_get_sync_support() && check_udev) {
if (stat(oldpath, &info) < 0 &&
errno == ENOENT)
/* assume udev already deleted this */
return 1;
else {
log_warn("The node %s should have been renamed to %s "
"by udev but old node is still present. "
"Falling back to direct old node removal.",
oldpath, newpath);
return _rm_dev_node(old_name, 0);
}
}
if (unlink(newpath) < 0) {
if (errno == EPERM) {
/* devfs, entry has already been renamed */
return 1;
}
log_error("Unable to unlink device node for '%s'",
new_name);
return 0;
}
}
else if (dm_udev_get_sync_support() && check_udev)
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);
if (rename(oldpath, newpath) < 0) {
log_error("Unable to rename device node from '%s' to '%s'",
old_name, new_name);
return 0;
}
log_debug("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];
_build_dev_path(path, sizeof(path), dev_name);
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 *read_ahead)
{
int r = 1;
int fd;
long read_ahead_long;
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("%s: read ahead is %" PRIu32, dev_name, *read_ahead);
}
if (close(fd))
stack;
return r;
}
static int _set_read_ahead(const char *dev_name, uint32_t read_ahead)
{
int r = 1;
int fd;
long read_ahead_long = (long) read_ahead;
if (!*dev_name) {
log_error("Empty device name passed to BLKRAGET");
return 0;
}
if ((fd = _open_dev_node(dev_name)) < 0)
return_0;
log_debug("%s: Setting read ahead to %" PRIu32, dev_name, read_ahead);
if (ioctl(fd, BLKRASET, read_ahead_long)) {
log_sys_error("BLKRASET", dev_name);
r = 0;
}
static int _udev_notify_sem_create(uint32_t *cookie, int *semid)
{
int fd;
int gen_semid;
uint16_t base_cookie;
uint32_t gen_cookie;
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);
if (semctl(gen_semid, 0, SETVAL, 1) < 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;
}
int dm_task_set_cookie(struct dm_task *dmt, uint32_t *cookie, uint16_t flags)
{
int semid;
if (dm_cookie_supported())
dmt->event_nr = flags << DM_UDEV_FLAGS_SHIFT;
if (!dm_udev_get_sync_support()) {
*cookie = 0;
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;
}
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;
}
int dm_udev_wait(uint32_t cookie)
{
int semid;
struct sembuf sb = {0, 0, 0};
if (!cookie || !dm_udev_get_sync_support())
return 1;
if (!_get_cookie_sem(cookie, &semid))
return_0;
if (!_udev_notify_sem_dec(cookie, semid)) {
log_error("Failed to set a proper state for notification "
"semaphore identified by cookie value %" PRIu32 " (0x%x) "
"to initialize waiting for incoming notifications.",
cookie, cookie);
(void) _udev_notify_sem_destroy(cookie, semid);
return 0;
}
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;
}