1
0
mirror of https://github.com/systemd/systemd.git synced 2024-10-31 16:21:26 +03:00

Merge pull request #19796 from yuwata/udev-node-cleanups

udev: several cleanups about creating device symlink
This commit is contained in:
Yu Watanabe 2021-06-04 21:46:43 +09:00 committed by GitHub
commit 16f9036471
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 241 additions and 165 deletions

View File

@ -19,7 +19,6 @@
#include "btrfs-util.h"
#include "chattr-util.h"
#include "copy.h"
#include "device-nodes.h"
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"

View File

@ -1,16 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <stddef.h>
#include <sys/types.h>
#include "macro.h"
#include "stdio-util.h"
int encode_devnode_name(const char *str, char *str_enc, size_t len);
int allow_listed_char_for_devnode(char c, const char *additional);
#define DEV_NUM_PATH_MAX \
(STRLEN("/dev/block/") + DECIMAL_STR_MAX(dev_t) + 1 + DECIMAL_STR_MAX(dev_t))
#define xsprintf_dev_num_path(buf, type, devno) \
xsprintf(buf, "/dev/%s/%u:%u", type, major(devno), minor(devno))

View File

@ -526,6 +526,16 @@ char base64char(int x) {
return table[x & 63];
}
/* This is almost base64char(), but not entirely, as it uses the "url and filename safe" alphabet,
* since we don't want "/" appear in interface names (since interfaces appear in sysfs as filenames).
* See section #5 of RFC 4648. */
char urlsafe_base64char(int x) {
static const char table[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789-_";
return table[x & 63];
}
int unbase64char(char c) {
unsigned offset;

View File

@ -27,6 +27,7 @@ char base32hexchar(int x) _const_;
int unbase32hexchar(char c) _const_;
char base64char(int x) _const_;
char urlsafe_base64char(int x) _const_;
int unbase64char(char c) _const_;
char *base32hexmem(const void *p, size_t l, bool padding);

View File

@ -38,8 +38,6 @@ basic_sources = files('''
creds-util.c
creds-util.h
def.h
device-nodes.c
device-nodes.h
dirent-util.c
dirent-util.h
dlfcn-util.c

View File

@ -11,6 +11,7 @@
#include "alloc-util.h"
#include "ether-addr-util.h"
#include "hexdecoct.h"
#include "lockfile-util.h"
#include "missing_network.h"
#include "netif-naming-scheme.h"
@ -200,16 +201,6 @@ static int add_veth(
return 0;
}
/* This is almost base64char(), but not entirely, as it uses the "url and filename safe" alphabet, since we
* don't want "/" appear in interface names (since interfaces appear in sysfs as filenames). See section #5
* of RFC 4648. */
static char urlsafe_base64char(int x) {
static const char table[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789-_";
return table[x & 63];
}
static int shorten_ifname(char *ifname) {
char new_ifname[IFNAMSIZ];

View File

@ -0,0 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <stddef.h>
int encode_devnode_name(const char *str, char *str_enc, size_t len);
int allow_listed_char_for_devnode(char c, const char *additional);

View File

@ -80,6 +80,8 @@ shared_sources = files('''
daemon-util.h
dev-setup.c
dev-setup.h
device-nodes.c
device-nodes.h
devnode-acl.h
discover-image.c
discover-image.h

View File

@ -1,11 +1,11 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <stdio.h>
#include <sys/types.h>
#include "alloc-util.h"
#include "device-nodes.h"
#include "string-util.h"
#include "util.h"
/* helpers for test_encode_devnode_name */
static char *do_encode_string(const char *in) {

View File

@ -200,6 +200,12 @@ tests += [
[threads,
libacl]],
[['src/udev/test-udev-node.c'],
[libudevd_core,
libshared],
[threads,
libacl]],
[['src/udev/test-udev-builtin.c'],
[libudevd_core,
libshared],

44
src/udev/test-udev-node.c Normal file
View File

@ -0,0 +1,44 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "tests.h"
#include "udev-node.h"
static void test_udev_node_escape_path_one(const char *path, const char *expected) {
char buf[NAME_MAX+1];
size_t r;
r = udev_node_escape_path(path, buf, sizeof buf);
log_debug("udev_node_escape_path(%s) -> %s (expected: %s)", path, buf, expected);
assert_se(r == strlen(expected));
assert_se(streq(buf, expected));
}
static void test_udev_node_escape_path(void) {
char a[NAME_MAX+1], b[NAME_MAX+1];
test_udev_node_escape_path_one("/disk/by-id/nvme-eui.1922908022470001001b448b44ccb9d6", "\\x2fdisk\\x2fby-id\\x2fnvme-eui.1922908022470001001b448b44ccb9d6");
test_udev_node_escape_path_one("/disk/by-id/nvme-eui.1922908022470001001b448b44ccb9d6-part1", "\\x2fdisk\\x2fby-id\\x2fnvme-eui.1922908022470001001b448b44ccb9d6-part1");
test_udev_node_escape_path_one("/disk/by-id/nvme-eui.1922908022470001001b448b44ccb9d6-part2", "\\x2fdisk\\x2fby-id\\x2fnvme-eui.1922908022470001001b448b44ccb9d6-part2");
test_udev_node_escape_path_one("/disk/by-id/nvme-WDC_PC_SN720_SDAQNTW-512G-1001_192290802247", "\\x2fdisk\\x2fby-id\\x2fnvme-WDC_PC_SN720_SDAQNTW-512G-1001_192290802247");
test_udev_node_escape_path_one("/disk/by-id/nvme-WDC_PC_SN720_SDAQNTW-512G-1001_192290802247-part1", "\\x2fdisk\\x2fby-id\\x2fnvme-WDC_PC_SN720_SDAQNTW-512G-1001_192290802247-part1");
test_udev_node_escape_path_one("/disk/by-id/nvme-WDC_PC_SN720_SDAQNTW-512G-1001_192290802247-part2", "\\x2fdisk\\x2fby-id\\x2fnvme-WDC_PC_SN720_SDAQNTW-512G-1001_192290802247-part2");
test_udev_node_escape_path_one("/disk/by-id/usb-Generic-_SD_MMC_20120501030900000-0:0", "\\x2fdisk\\x2fby-id\\x2fusb-Generic-_SD_MMC_20120501030900000-0:0");
memset(a, 'a', sizeof(a) - 1);
memcpy(a, "/disk/by-id/", strlen("/disk/by-id/"));
char_array_0(a);
memset(b, 'a', sizeof(b) - 1);
memcpy(b, "\\x2fdisk\\x2fby-id\\x2f", strlen("\\x2fdisk\\x2fby-id\\x2f"));
strcpy(b + sizeof(b) - 12, "N3YhcCqFeID");
test_udev_node_escape_path_one(a, b);
}
int main(int argc, char *argv[]) {
test_setup_logging(LOG_INFO);
test_udev_node_escape_path();
return 0;
}

View File

@ -7,14 +7,16 @@
#include <sys/stat.h>
#include <unistd.h>
#include "sd-id128.h"
#include "alloc-util.h"
#include "device-nodes.h"
#include "device-private.h"
#include "device-util.h"
#include "dirent-util.h"
#include "fd-util.h"
#include "format-util.h"
#include "fs-util.h"
#include "hexdecoct.h"
#include "mkdir.h"
#include "path-util.h"
#include "selinux-util.h"
@ -26,7 +28,36 @@
#include "udev-node.h"
#include "user-util.h"
#define CREATE_LINK_MAX_RETRIES 128
#define LINK_UPDATE_MAX_RETRIES 128
#define TOUCH_FILE_MAX_RETRIES 128
#define UDEV_NODE_HASH_KEY SD_ID128_MAKE(b9,6a,f1,ce,40,31,44,1a,9e,19,ec,8b,ae,f3,e3,2f)
static int create_symlink(const char *target, const char *slink) {
int r;
assert(target);
assert(slink);
for (unsigned i = 0; i < CREATE_LINK_MAX_RETRIES; i++) {
r = mkdir_parents_label(slink, 0755);
if (r == -ENOENT)
continue;
if (r < 0)
return r;
mac_selinux_create_file_prepare(slink, S_IFLNK);
if (symlink(target, slink) < 0)
r = -errno;
else
r = 0;
mac_selinux_create_file_clear();
if (r != -ENOENT)
return r;
}
return r;
}
static int node_symlink(sd_device *dev, const char *node, const char *slink) {
_cleanup_free_ char *slink_dirname = NULL, *target = NULL;
@ -38,87 +69,80 @@ static int node_symlink(sd_device *dev, const char *node, const char *slink) {
assert(node);
assert(slink);
slink_dirname = dirname_malloc(slink);
if (!slink_dirname)
return log_oom();
r = path_extract_directory(slink, &slink_dirname);
if (r < 0)
return log_device_debug_errno(dev, r, "Failed to get parent directory of '%s': %m", slink);
/* use relative link */
r = path_make_relative(slink_dirname, node, &target);
if (r < 0)
return log_device_error_errno(dev, r, "Failed to get relative path from '%s' to '%s': %m", slink, node);
return log_device_debug_errno(dev, r, "Failed to get relative path from '%s' to '%s': %m", slink, node);
/* preserve link with correct target, do not replace node of other device */
if (lstat(slink, &stats) == 0) {
if (S_ISBLK(stats.st_mode) || S_ISCHR(stats.st_mode))
return log_device_error_errno(dev, SYNTHETIC_ERRNO(EOPNOTSUPP),
"Conflicting device node '%s' found, link to '%s' will not be created.", slink, node);
else if (S_ISLNK(stats.st_mode)) {
_cleanup_free_ char *buf = NULL;
if (lstat(slink, &stats) >= 0) {
_cleanup_free_ char *buf = NULL;
if (!S_ISLNK(stats.st_mode))
return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EEXIST),
"Conflicting inode '%s' found, link to '%s' will not be created.", slink, node);
if (readlink_malloc(slink, &buf) >= 0 &&
path_equal(target, buf)) {
/* preserve link with correct target, do not replace node of other device */
log_device_debug(dev, "Preserve already existing symlink '%s' to '%s'", slink, target);
(void) label_fix(slink, LABEL_IGNORE_ENOENT);
(void) utimensat(AT_FDCWD, slink, NULL, AT_SYMLINK_NOFOLLOW);
if (readlink_malloc(slink, &buf) >= 0 &&
streq(target, buf)) {
log_device_debug(dev, "Preserve already existing symlink '%s' to '%s'", slink, target);
(void) label_fix(slink, LABEL_IGNORE_ENOENT);
(void) utimensat(AT_FDCWD, slink, NULL, AT_SYMLINK_NOFOLLOW);
return 0;
}
}
} else {
log_device_debug(dev, "Creating symlink '%s' to '%s'", slink, target);
do {
r = mkdir_parents_label(slink, 0755);
if (!IN_SET(r, 0, -ENOENT))
break;
mac_selinux_create_file_prepare(slink, S_IFLNK);
if (symlink(target, slink) < 0)
r = -errno;
mac_selinux_create_file_clear();
} while (r == -ENOENT);
if (r == 0)
return 0;
if (r < 0)
log_device_debug_errno(dev, r, "Failed to create symlink '%s' to '%s', trying to replace '%s': %m", slink, target, slink);
}
}
} else if (errno == ENOENT) {
log_device_debug(dev, "Creating symlink '%s' to '%s'", slink, target);
r = create_symlink(target, slink);
if (r >= 0)
return 0;
log_device_debug_errno(dev, r, "Failed to create symlink '%s' to '%s', trying to replace '%s': %m", slink, target, slink);
} else
return log_device_debug_errno(dev, errno, "Failed to lstat() '%s': %m", slink);
log_device_debug(dev, "Atomically replace '%s'", slink);
r = device_get_device_id(dev, &id);
if (r < 0)
return log_device_error_errno(dev, r, "Failed to get device id: %m");
return log_device_debug_errno(dev, r, "Failed to get device id: %m");
slink_tmp = strjoina(slink, ".tmp-", id);
(void) unlink(slink_tmp);
do {
r = mkdir_parents_label(slink_tmp, 0755);
if (!IN_SET(r, 0, -ENOENT))
break;
mac_selinux_create_file_prepare(slink_tmp, S_IFLNK);
if (symlink(target, slink_tmp) < 0)
r = -errno;
mac_selinux_create_file_clear();
} while (r == -ENOENT);
r = create_symlink(target, slink_tmp);
if (r < 0)
return log_device_error_errno(dev, r, "Failed to create symlink '%s' to '%s': %m", slink_tmp, target);
return log_device_debug_errno(dev, r, "Failed to create symlink '%s' to '%s': %m", slink_tmp, target);
if (rename(slink_tmp, slink) < 0) {
r = log_device_error_errno(dev, errno, "Failed to rename '%s' to '%s': %m", slink_tmp, slink);
r = log_device_debug_errno(dev, errno, "Failed to rename '%s' to '%s': %m", slink_tmp, slink);
(void) unlink(slink_tmp);
} else
/* Tell caller that we replaced already existing symlink. */
r = 1;
return r;
}
return r;
/* Tell caller that we replaced already existing symlink. */
return 1;
}
/* find device node of device with highest priority */
static int link_find_prioritized(sd_device *dev, bool add, const char *stackdir, char **ret) {
_cleanup_closedir_ DIR *dir = NULL;
_cleanup_free_ char *target = NULL;
struct dirent *dent;
int r, priority = 0;
const char *id;
assert(!add || dev);
assert(dev);
assert(stackdir);
assert(ret);
/* Find device node of device with highest priority. This returns 1 if a device found, 0 if no
* device found, or a negative errno. */
if (add) {
const char *devnode;
@ -137,17 +161,21 @@ static int link_find_prioritized(sd_device *dev, bool add, const char *stackdir,
dir = opendir(stackdir);
if (!dir) {
if (target) {
if (errno == ENOENT) {
*ret = TAKE_PTR(target);
return 0;
return !!*ret;
}
return -errno;
}
r = device_get_device_id(dev, &id);
if (r < 0)
return r;
FOREACH_DIRENT_ALL(dent, dir, break) {
_cleanup_(sd_device_unrefp) sd_device *dev_db = NULL;
const char *devnode, *id;
const char *devnode;
int db_prio = 0;
if (dent->d_name[0] == '\0')
@ -157,9 +185,6 @@ static int link_find_prioritized(sd_device *dev, bool add, const char *stackdir,
log_device_debug(dev, "Found '%s' claiming '%s'", dent->d_name, stackdir);
if (device_get_device_id(dev, &id) < 0)
continue;
/* did we find ourself? */
if (streq(dent->d_name, id))
continue;
@ -184,59 +209,73 @@ static int link_find_prioritized(sd_device *dev, bool add, const char *stackdir,
priority = db_prio;
}
if (!target)
return -ENOENT;
*ret = TAKE_PTR(target);
return 0;
return !!*ret;
}
static size_t escape_path(const char *src, char *dest, size_t size) {
size_t udev_node_escape_path(const char *src, char *dest, size_t size) {
size_t i, j;
uint64_t h;
assert(src);
assert(dest);
for (i = 0, j = 0; src[i] != '\0'; i++) {
if (src[i] == '/') {
if (j+4 >= size) {
j = 0;
break;
}
if (j+4 >= size)
goto toolong;
memcpy(&dest[j], "\\x2f", 4);
j += 4;
} else if (src[i] == '\\') {
if (j+4 >= size) {
j = 0;
break;
}
if (j+4 >= size)
goto toolong;
memcpy(&dest[j], "\\x5c", 4);
j += 4;
} else {
if (j+1 >= size) {
j = 0;
break;
}
if (j+1 >= size)
goto toolong;
dest[j] = src[i];
j++;
}
}
dest[j] = '\0';
return j;
toolong:
/* If the input path is too long to encode as a filename, then let's suffix with a string
* generated from the hash of the path. */
h = siphash24_string(src, UDEV_NODE_HASH_KEY.bytes);
assert(size >= 12);
for (unsigned k = 0; k <= 10; k++)
dest[size - k - 2] = urlsafe_base64char((h >> (k * 6)) & 63);
dest[size - 1] = '\0';
return size - 1;
}
/* manage "stack of names" with possibly specified device priorities */
static int link_update(sd_device *dev, const char *slink, bool add) {
_cleanup_free_ char *filename = NULL, *dirname = NULL;
static int link_update(sd_device *dev, const char *slink_in, bool add) {
_cleanup_free_ char *slink = NULL, *filename = NULL, *dirname = NULL;
const char *slink_name, *id;
char name_enc[PATH_MAX];
char name_enc[NAME_MAX+1];
int i, r, retries;
assert(dev);
assert(slink);
assert(slink_in);
slink = strdup(slink_in);
if (!slink)
return log_oom_debug();
path_simplify(slink);
slink_name = path_startswith(slink, "/dev");
if (!slink_name)
if (!slink_name ||
empty_or_root(slink_name) ||
!path_is_normalized(slink_name))
return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL),
"Invalid symbolic link of device node: %s", slink);
@ -244,31 +283,31 @@ static int link_update(sd_device *dev, const char *slink, bool add) {
if (r < 0)
return log_device_debug_errno(dev, r, "Failed to get device id: %m");
escape_path(slink_name, name_enc, sizeof(name_enc));
(void) udev_node_escape_path(slink_name, name_enc, sizeof(name_enc));
dirname = path_join("/run/udev/links/", name_enc);
if (!dirname)
return log_oom();
return log_oom_debug();
filename = path_join(dirname, id);
if (!filename)
return log_oom();
return log_oom_debug();
if (!add) {
if (unlink(filename) == 0)
(void) rmdir(dirname);
} else
for (;;) {
_cleanup_close_ int fd = -1;
if (unlink(filename) < 0 && errno != ENOENT)
log_device_debug_errno(dev, errno, "Failed to remove %s, ignoring: %m", filename);
r = mkdir_parents(filename, 0755);
if (!IN_SET(r, 0, -ENOENT))
return r;
fd = open(filename, O_WRONLY|O_CREAT|O_CLOEXEC|O_TRUNC|O_NOFOLLOW, 0444);
if (fd >= 0)
(void) rmdir(dirname);
} else {
for (unsigned j = 0; j < TOUCH_FILE_MAX_RETRIES; j++) {
/* This may fail with -ENOENT when the parent directory is removed during
* creating the file by another udevd worker. */
r = touch_file(filename, /* parents= */ true, USEC_INFINITY, UID_INVALID, GID_INVALID, 0444);
if (r != -ENOENT)
break;
if (errno != ENOENT)
return -errno;
}
if (r < 0)
return log_device_debug_errno(dev, r, "Failed to create %s: %m", filename);
}
/* If the database entry is not written yet we will just do one iteration and possibly wrong symlink
* will be fixed in the second invocation. */
@ -280,23 +319,25 @@ static int link_update(sd_device *dev, const char *slink, bool add) {
r = stat(dirname, &st1);
if (r < 0 && errno != ENOENT)
return -errno;
return log_device_debug_errno(dev, errno, "Failed to stat %s: %m", dirname);
r = link_find_prioritized(dev, add, dirname, &target);
if (r == -ENOENT) {
log_device_debug(dev, "No reference left, removing '%s'", slink);
if (unlink(slink) == 0)
(void) rmdir_parents(slink, "/");
if (r < 0)
return log_device_debug_errno(dev, r, "Failed to determine highest priority for symlink '%s': %m", slink);
if (r == 0) {
log_device_debug(dev, "No reference left for '%s', removing", slink);
if (unlink(slink) < 0 && errno != ENOENT)
log_device_debug_errno(dev, errno, "Failed to remove '%s', ignoring: %m", slink);
(void) rmdir_parents(slink, "/dev");
break;
} else if (r < 0)
return log_device_error_errno(dev, r, "Failed to determine highest priority symlink: %m");
}
r = node_symlink(dev, target, slink);
if (r < 0) {
(void) unlink(filename);
break;
} else if (r == 1)
if (r < 0)
return r;
if (r == 1)
/* We have replaced already existing symlink, possibly there is some other device trying
* to claim the same symlink. Let's do one more iteration to give us a chance to fix
* the error if other device actually claims the symlink with higher priority. */
@ -306,7 +347,7 @@ static int link_update(sd_device *dev, const char *slink, bool add) {
if ((st1.st_mode & S_IFMT) != 0) {
r = stat(dirname, &st2);
if (r < 0 && errno != ENOENT)
return -errno;
return log_device_debug_errno(dev, errno, "Failed to stat %s: %m", dirname);
if (stat_inode_unmodified(&st1, &st2))
break;
@ -317,16 +358,12 @@ static int link_update(sd_device *dev, const char *slink, bool add) {
}
int udev_node_update_old_links(sd_device *dev, sd_device *dev_old) {
const char *name, *devpath;
const char *name;
int r;
assert(dev);
assert(dev_old);
r = sd_device_get_devpath(dev, &devpath);
if (r < 0)
return log_device_debug_errno(dev, r, "Failed to get devpath: %m");
/* update possible left-over symlinks */
FOREACH_DEVICE_DEVLINK(dev_old, name) {
const char *name_current;
@ -342,8 +379,10 @@ int udev_node_update_old_links(sd_device *dev, sd_device *dev_old) {
if (found)
continue;
log_device_debug(dev, "Updating old name, '%s' no longer belonging to '%s'",
name, devpath);
log_device_debug(dev,
"Updating old device symlink '%s', which is no longer belonging to this device.",
name);
r = link_update(dev, name, false);
if (r < 0)
log_device_warning_errno(dev, r,
@ -477,7 +516,6 @@ static int node_permissions_apply(sd_device *dev, bool apply_mac,
}
static int xsprintf_dev_num_path_from_sd_device(sd_device *dev, char **ret) {
char filename[DEV_NUM_PATH_MAX], *s;
const char *subsystem;
dev_t devnum;
int r;
@ -492,16 +530,7 @@ static int xsprintf_dev_num_path_from_sd_device(sd_device *dev, char **ret) {
if (r < 0)
return r;
xsprintf_dev_num_path(filename,
streq(subsystem, "block") ? "block" : "char",
devnum);
s = strdup(filename);
if (!s)
return -ENOMEM;
*ret = s;
return 0;
return device_path_make_major_minor(streq(subsystem, "block") ? S_IFBLK : S_IFCHR, devnum, ret);
}
int udev_node_add(sd_device *dev, bool apply,
@ -528,13 +557,6 @@ int udev_node_add(sd_device *dev, bool apply,
if (r < 0)
return r;
r = xsprintf_dev_num_path_from_sd_device(dev, &filename);
if (r < 0)
return log_device_debug_errno(dev, r, "Failed to get device path: %m");
/* always add /dev/{block,char}/$major:$minor */
(void) node_symlink(dev, devnode, filename);
/* create/update symlinks, add symlinks to name index */
FOREACH_DEVICE_DEVLINK(dev, devlink) {
r = link_update(dev, devlink, true);
@ -544,6 +566,15 @@ int udev_node_add(sd_device *dev, bool apply,
devlink);
}
r = xsprintf_dev_num_path_from_sd_device(dev, &filename);
if (r < 0)
return log_device_debug_errno(dev, r, "Failed to get device path: %m");
/* always add /dev/{block,char}/$major:$minor */
r = node_symlink(dev, devnode, filename);
if (r < 0)
return log_device_warning_errno(dev, r, "Failed to create device symlink '%s': %m", filename);
return 0;
}
@ -568,7 +599,8 @@ int udev_node_remove(sd_device *dev) {
return log_device_debug_errno(dev, r, "Failed to get device path: %m");
/* remove /dev/{block,char}/$major:$minor */
(void) unlink(filename);
if (unlink(filename) < 0 && errno != ENOENT)
return log_device_debug_errno(dev, errno, "Failed to remove '%s': %m", filename);
return 0;
}

View File

@ -13,3 +13,5 @@ int udev_node_add(sd_device *dev, bool apply,
OrderedHashmap *seclabel_list);
int udev_node_remove(sd_device *dev);
int udev_node_update_old_links(sd_device *dev, sd_device *dev_old);
size_t udev_node_escape_path(const char *src, char *dest, size_t size);