mirror of
https://github.com/systemd/systemd.git
synced 2024-12-23 21:35:11 +03:00
priority based symlink handling
Symlinks can have priorities now, the priority is assigned to the device and specified with OPTIONS="link_priority=100". Devices with higher priorities overwrite the symlinks of devices with lower priorities. If the device, that currently owns the link goes away, the symlink will be removed, and recreated, pointing to the next device with the highest actual priority. This should solve the issue, that inserting an USB-stick may overwrite the /dev/disk/by-id/-link of another disk, and removes the entire link after the USB-stick is disconnected. If no priorities are specified, the new link will overwrite the current one, and if the device goes away, it will restore the old link. It should be possible to assign lower priorities to removable devices, if needed. In multipath setups, we see several devices, which all connect to the same volume, and therefore all try to create the same metadata-links. The different path-devices are combined into one device-mapper device, which also contains the same metadata. It should be possible, to assign multipath-table device-mapper devices a higher priority, so path-devices that appear and disappear, will not overwrite or delete the device-mapper device links.
This commit is contained in:
parent
31de3a2ba1
commit
24f0605c1f
5
udev.h
5
udev.h
@ -118,15 +118,14 @@ extern int sysfs_resolve_link(char *path, size_t size);
|
||||
|
||||
/* udev_node.c */
|
||||
extern int udev_node_mknod(struct udevice *udev, const char *file, dev_t devt, mode_t mode, uid_t uid, gid_t gid);
|
||||
extern int udev_node_add(struct udevice *udev, struct udevice *udev_old);
|
||||
extern void udev_node_remove_symlinks(struct udevice *udev);
|
||||
extern void udev_node_update_symlinks(struct udevice *udev, struct udevice *udev_old);
|
||||
extern int udev_node_add(struct udevice *udev);
|
||||
extern int udev_node_remove(struct udevice *udev);
|
||||
|
||||
/* udev_db.c */
|
||||
extern int udev_db_add_device(struct udevice *dev);
|
||||
extern int udev_db_delete_device(struct udevice *dev);
|
||||
extern int udev_db_get_device(struct udevice *udev, const char *devpath);
|
||||
extern int udev_db_lookup_name(const char *name, char *devpath, size_t len);
|
||||
extern int udev_db_get_devices_by_name(const char *name, struct list_head *name_list);
|
||||
extern int udev_db_get_all_entries(struct list_head *name_list);
|
||||
|
||||
|
62
udev_db.c
62
udev_db.c
@ -296,68 +296,6 @@ int udev_db_delete_device(struct udevice *udev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int udev_db_lookup_name(const char *name, char *devpath, size_t len)
|
||||
{
|
||||
char dirname[PATH_MAX];
|
||||
size_t start;
|
||||
DIR *dir;
|
||||
int found = 0;
|
||||
|
||||
strlcpy(dirname, udev_root, sizeof(dirname));
|
||||
start = strlcat(dirname, "/"DB_NAME_INDEX_DIR"/", sizeof(dirname));
|
||||
strlcat(dirname, name, sizeof(dirname));
|
||||
path_encode(&dirname[start], sizeof(dirname) - start);
|
||||
|
||||
dir = opendir(dirname);
|
||||
if (dir == NULL) {
|
||||
info("no index directory '%s': %s", dirname, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
info("found index directory '%s'", dirname);
|
||||
while (!found) {
|
||||
struct dirent *ent;
|
||||
char device[PATH_SIZE];
|
||||
struct udevice *udev;
|
||||
|
||||
ent = readdir(dir);
|
||||
if (ent == NULL || ent->d_name[0] == '\0')
|
||||
break;
|
||||
if (ent->d_name[0] == '.')
|
||||
continue;
|
||||
|
||||
strlcpy(device, ent->d_name, sizeof(device));
|
||||
path_decode(device);
|
||||
udev = udev_device_init(NULL);
|
||||
if (udev == NULL)
|
||||
break;
|
||||
if (udev_db_get_device(udev, device) == 0) {
|
||||
char filename[PATH_SIZE];
|
||||
struct stat statbuf;
|
||||
|
||||
info("found db entry '%s'", device);
|
||||
strlcpy(filename, udev_root, sizeof(filename));
|
||||
strlcat(filename, "/", sizeof(filename));
|
||||
strlcat(filename, name, sizeof(filename));
|
||||
/* make sure device entry matches dev_t */
|
||||
if (stat(filename, &statbuf) == 0) {
|
||||
if (statbuf.st_rdev == udev->devt) {
|
||||
info("node '%s' matches dev_t", udev->name);
|
||||
strlcpy(devpath, device, len);
|
||||
found = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
udev_device_cleanup(udev);
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
if (found)
|
||||
return 0;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
int udev_db_get_all_entries(struct list_head *name_list)
|
||||
{
|
||||
char dbpath[PATH_MAX];
|
||||
|
@ -173,28 +173,19 @@ int udev_device_event(struct udev_rules *rules, struct udevice *udev)
|
||||
}
|
||||
}
|
||||
|
||||
/* create node and symlinks */
|
||||
retval = udev_node_add(udev, udev_old);
|
||||
if (retval == 0)
|
||||
udev_db_add_device(udev);
|
||||
/* create node */
|
||||
retval = udev_node_add(udev);
|
||||
if (retval != 0)
|
||||
goto exit;
|
||||
|
||||
/* remove possibly left-over symlinks */
|
||||
if (udev_old != NULL) {
|
||||
struct name_entry *link_loop;
|
||||
struct name_entry *link_old_loop;
|
||||
struct name_entry *link_old_tmp_loop;
|
||||
/* store in database */
|
||||
udev_db_add_device(udev);
|
||||
|
||||
/* remove still valid symlinks from old list */
|
||||
list_for_each_entry_safe(link_old_loop, link_old_tmp_loop, &udev_old->symlink_list, node)
|
||||
list_for_each_entry(link_loop, &udev->symlink_list, node)
|
||||
if (strcmp(link_old_loop->name, link_loop->name) == 0) {
|
||||
dbg("symlink '%s' still valid, keep it", link_old_loop->name);
|
||||
list_del(&link_old_loop->node);
|
||||
free(link_old_loop);
|
||||
}
|
||||
udev_node_remove_symlinks(udev_old);
|
||||
/* create, replace, delete symlinks according to priority */
|
||||
udev_node_update_symlinks(udev, udev_old);
|
||||
|
||||
if (udev_old != NULL)
|
||||
udev_device_cleanup(udev_old);
|
||||
}
|
||||
goto exit;
|
||||
}
|
||||
|
||||
@ -251,7 +242,8 @@ int udev_device_event(struct udev_rules *rules, struct udevice *udev)
|
||||
list_for_each_entry(name_loop, &udev->env_list, node)
|
||||
putenv(name_loop->name);
|
||||
} else {
|
||||
dbg("'%s' not found in database, using kernel name '%s'", udev->dev->devpath, udev->dev->kernel);
|
||||
dbg("'%s' not found in database, using kernel name '%s'",
|
||||
udev->dev->devpath, udev->dev->kernel);
|
||||
strlcpy(udev->name, udev->dev->kernel, sizeof(udev->name));
|
||||
}
|
||||
|
||||
@ -261,7 +253,11 @@ int udev_device_event(struct udev_rules *rules, struct udevice *udev)
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* remove the node */
|
||||
retval = udev_node_remove(udev);
|
||||
|
||||
/* delete or restore symlinks according to priority */
|
||||
udev_node_update_symlinks(udev, NULL);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
|
235
udev_node.c
235
udev_node.c
@ -25,6 +25,7 @@
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <grp.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
@ -114,7 +115,7 @@ static int node_symlink(const char *node, const char *slink)
|
||||
if (len > 0) {
|
||||
buf[len] = '\0';
|
||||
if (strcmp(target, buf) == 0) {
|
||||
info("preserving symlink '%s' to '%s'", slink, target);
|
||||
info("preserve already existing symlink '%s' to '%s'", slink, target);
|
||||
selinux_setfilecon(slink, NULL, S_IFLNK);
|
||||
goto exit;
|
||||
}
|
||||
@ -133,7 +134,131 @@ exit:
|
||||
return 0;
|
||||
}
|
||||
|
||||
int udev_node_add(struct udevice *udev, struct udevice *udev_old)
|
||||
static int update_link(struct udevice *udev, const char *name)
|
||||
{
|
||||
LIST_HEAD(name_list);
|
||||
char slink[PATH_SIZE];
|
||||
char node[PATH_SIZE];
|
||||
struct udevice *udev_db;
|
||||
struct name_entry *device;
|
||||
char target[PATH_MAX] = "";
|
||||
int count;
|
||||
int priority = 0;
|
||||
int rc = 0;
|
||||
|
||||
strlcpy(slink, udev_root, sizeof(slink));
|
||||
strlcat(slink, "/", sizeof(slink));
|
||||
strlcat(slink, name, sizeof(slink));
|
||||
|
||||
count = udev_db_get_devices_by_name(name, &name_list);
|
||||
info("found %i devices with name '%s'", count, name);
|
||||
|
||||
/* if we don't have any reference, we can delete the link */
|
||||
if (count <= 0) {
|
||||
info("no reference left, remove '%s'", name);
|
||||
if (!udev->test_run) {
|
||||
unlink(slink);
|
||||
delete_path(slink);
|
||||
}
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* find the device with the highest priority */
|
||||
list_for_each_entry(device, &name_list, node) {
|
||||
info("found '%s' for '%s'", device->name, name);
|
||||
|
||||
/* did we find ourself? we win, if we have the same priority */
|
||||
if (strcmp(udev->dev->devpath, device->name) == 0) {
|
||||
info("compare (our own) priority of '%s' %i >= %i",
|
||||
udev->dev->devpath, udev->link_priority, priority);
|
||||
if (target[0] == '\0' || udev->link_priority >= priority) {
|
||||
priority = udev->link_priority;
|
||||
strlcpy(target, udev->name, sizeof(target));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
/* or something else, then read priority from database */
|
||||
udev_db = udev_device_init(NULL);
|
||||
if (udev_db == NULL)
|
||||
continue;
|
||||
if (udev_db_get_device(udev_db, device->name) == 0) {
|
||||
info("compare priority of '%s' %i > %i",
|
||||
udev_db->dev->devpath, udev_db->link_priority, priority);
|
||||
if (target[0] == '\0' || udev_db->link_priority > priority) {
|
||||
priority = udev_db->link_priority;
|
||||
strlcpy(target, udev_db->name, sizeof(target));
|
||||
}
|
||||
}
|
||||
udev_device_cleanup(udev_db);
|
||||
}
|
||||
name_list_cleanup(&name_list);
|
||||
|
||||
if (target[0] == '\0') {
|
||||
err("missing target for '%s'", name);
|
||||
rc = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* create symlink to the target with the highest priority */
|
||||
strlcpy(node, udev_root, sizeof(node));
|
||||
strlcat(node, "/", sizeof(node));
|
||||
strlcat(node, target, sizeof(node));
|
||||
info("'%s' with target '%s' has the highest priority %i, create it", name, target, priority);
|
||||
if (!udev->test_run) {
|
||||
create_path(slink);
|
||||
node_symlink(node, slink);
|
||||
}
|
||||
out:
|
||||
return rc;
|
||||
}
|
||||
|
||||
void udev_node_update_symlinks(struct udevice *udev, struct udevice *udev_old)
|
||||
{
|
||||
struct name_entry *name_loop;
|
||||
char symlinks[PATH_SIZE] = "";
|
||||
|
||||
list_for_each_entry(name_loop, &udev->symlink_list, node) {
|
||||
info("update symlink '%s' of '%s'", name_loop->name, udev->dev->devpath);
|
||||
update_link(udev, name_loop->name);
|
||||
strlcat(symlinks, udev_root, sizeof(symlinks));
|
||||
strlcat(symlinks, "/", sizeof(symlinks));
|
||||
strlcat(symlinks, name_loop->name, sizeof(symlinks));
|
||||
strlcat(symlinks, " ", sizeof(symlinks));
|
||||
}
|
||||
|
||||
/* export symlinks to environment */
|
||||
remove_trailing_chars(symlinks, ' ');
|
||||
if (symlinks[0] != '\0')
|
||||
setenv("DEVLINKS", symlinks, 1);
|
||||
|
||||
/* update possible left-over symlinks (device metadata changed) */
|
||||
if (udev_old != NULL) {
|
||||
struct name_entry *link_loop;
|
||||
struct name_entry *link_old_loop;
|
||||
struct name_entry *link_old_tmp_loop;
|
||||
int found;
|
||||
|
||||
/* remove current symlinks from old list */
|
||||
list_for_each_entry_safe(link_old_loop, link_old_tmp_loop, &udev_old->symlink_list, node) {
|
||||
found = 0;
|
||||
list_for_each_entry(link_loop, &udev->symlink_list, node) {
|
||||
if (strcmp(link_old_loop->name, link_loop->name) == 0) {
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
/* link does no longer belong to this device */
|
||||
info("update old symlink '%s' no longer belonging to '%s'",
|
||||
link_old_loop->name, udev->dev->devpath);
|
||||
update_link(udev, link_old_loop->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int udev_node_add(struct udevice *udev)
|
||||
{
|
||||
char filename[PATH_SIZE];
|
||||
uid_t uid;
|
||||
@ -141,10 +266,9 @@ int udev_node_add(struct udevice *udev, struct udevice *udev_old)
|
||||
int i;
|
||||
int retval = 0;
|
||||
|
||||
snprintf(filename, sizeof(filename), "%s/%s", udev_root, udev->name);
|
||||
filename[sizeof(filename)-1] = '\0';
|
||||
|
||||
/* create parent directories if needed */
|
||||
strlcpy(filename, udev_root, sizeof(filename));
|
||||
strlcat(filename, "/", sizeof(filename));
|
||||
strlcat(filename, udev->name, sizeof(filename));
|
||||
create_path(filename);
|
||||
|
||||
if (strcmp(udev->owner, "root") == 0)
|
||||
@ -209,102 +333,10 @@ int udev_node_add(struct udevice *udev, struct udevice *udev_old)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* create symlink(s) if requested */
|
||||
if (!list_empty(&udev->symlink_list)) {
|
||||
struct name_entry *name_loop;
|
||||
char symlinks[PATH_SIZE] = "";
|
||||
|
||||
list_for_each_entry(name_loop, &udev->symlink_list, node) {
|
||||
char slink[PATH_SIZE];
|
||||
|
||||
strlcpy(slink, udev_root, sizeof(slink));
|
||||
strlcat(slink, "/", sizeof(slink));
|
||||
strlcat(slink, name_loop->name, sizeof(slink));
|
||||
|
||||
info("creating symlink '%s' to node '%s'", slink, filename);
|
||||
if (!udev->test_run) {
|
||||
create_path(slink);
|
||||
node_symlink(filename, slink);
|
||||
}
|
||||
|
||||
strlcat(symlinks, slink, sizeof(symlinks));
|
||||
strlcat(symlinks, " ", sizeof(symlinks));
|
||||
}
|
||||
|
||||
remove_trailing_chars(symlinks, ' ');
|
||||
setenv("DEVLINKS", symlinks, 1);
|
||||
}
|
||||
|
||||
exit:
|
||||
return retval;
|
||||
}
|
||||
|
||||
void udev_node_remove_symlinks(struct udevice *udev)
|
||||
{
|
||||
char filename[PATH_SIZE];
|
||||
struct name_entry *name_loop;
|
||||
struct stat stats;
|
||||
|
||||
if (!list_empty(&udev->symlink_list)) {
|
||||
char symlinks[PATH_SIZE] = "";
|
||||
|
||||
list_for_each_entry(name_loop, &udev->symlink_list, node) {
|
||||
char devpath[PATH_SIZE];
|
||||
|
||||
snprintf(filename, sizeof(filename), "%s/%s", udev_root, name_loop->name);
|
||||
filename[sizeof(filename)-1] = '\0';
|
||||
|
||||
if (stat(filename, &stats) != 0) {
|
||||
dbg("symlink '%s' not found", filename);
|
||||
continue;
|
||||
}
|
||||
if (udev->devt && stats.st_rdev != udev->devt) {
|
||||
info("symlink '%s' points to a different device, skip removal", filename);
|
||||
continue;
|
||||
}
|
||||
|
||||
info("removing symlink '%s'", filename);
|
||||
if (!udev->test_run) {
|
||||
unlink(filename);
|
||||
delete_path(filename);
|
||||
}
|
||||
|
||||
/* see if another device wants this symlink */
|
||||
if (udev_db_lookup_name(name_loop->name, devpath, sizeof(devpath)) == 0) {
|
||||
struct udevice *old;
|
||||
|
||||
info("found overwritten symlink '%s' of '%s'", name_loop->name, devpath);
|
||||
old = udev_device_init(NULL);
|
||||
if (old != NULL) {
|
||||
if (udev_db_get_device(old, devpath) == 0) {
|
||||
char slink[PATH_SIZE];
|
||||
char node[PATH_SIZE];
|
||||
|
||||
strlcpy(slink, udev_root, sizeof(slink));
|
||||
strlcat(slink, "/", sizeof(slink));
|
||||
strlcat(slink, name_loop->name, sizeof(slink));
|
||||
strlcpy(node, udev_root, sizeof(node));
|
||||
strlcat(node, "/", sizeof(node));
|
||||
strlcat(node, old->name, sizeof(node));
|
||||
info("restore symlink '%s' to '%s'", slink, node);
|
||||
if (!udev->test_run)
|
||||
node_symlink(node, slink);
|
||||
}
|
||||
udev_device_cleanup(old);
|
||||
}
|
||||
}
|
||||
|
||||
strlcat(symlinks, filename, sizeof(symlinks));
|
||||
strlcat(symlinks, " ", sizeof(symlinks));
|
||||
}
|
||||
|
||||
remove_trailing_chars(symlinks, ' ');
|
||||
if (symlinks[0] != '\0')
|
||||
setenv("DEVLINKS", symlinks, 1);
|
||||
}
|
||||
}
|
||||
|
||||
int udev_node_remove(struct udevice *udev)
|
||||
{
|
||||
char filename[PATH_SIZE];
|
||||
@ -313,10 +345,9 @@ int udev_node_remove(struct udevice *udev)
|
||||
int retval;
|
||||
int num;
|
||||
|
||||
udev_node_remove_symlinks(udev);
|
||||
|
||||
snprintf(filename, sizeof(filename), "%s/%s", udev_root, udev->name);
|
||||
filename[sizeof(filename)-1] = '\0';
|
||||
strlcpy(filename, udev_root, sizeof(filename));
|
||||
strlcat(filename, "/", sizeof(filename));
|
||||
strlcat(filename, udev->name, sizeof(filename));
|
||||
if (stat(filename, &stats) != 0) {
|
||||
dbg("device node '%s' not found", filename);
|
||||
return -1;
|
||||
|
Loading…
Reference in New Issue
Block a user