diff options
author | Lennart Poettering <lennart@poettering.net> | 2018-06-29 15:57:49 +0200 |
---|---|---|
committer | Lennart Poettering <lennart@poettering.net> | 2018-11-29 20:21:39 +0100 |
commit | 846b3bd61e1d575b0b28f73c4d15385f94bb1662 (patch) | |
tree | 6c5a149cf229086e30ee76a0c9d0a55f67ea8e3a /src | |
parent | 8e8b5d2e6d91180a57844b09cdbdcbc1fa466bfa (diff) | |
download | systemd-846b3bd61e1d575b0b28f73c4d15385f94bb1662.tar.gz |
stat-util: add new APIs device_path_make_{major_minor|canonical}() and device_path_parse_major_minor()
device_path_make_{major_minor|canonical) generate device node paths
given a mode_t and a dev_t. We have similar code all over the place,
let's unify this in one place. The former will generate a "/dev/char/"
or "/dev/block" path, and never go to disk. The latter then goes to disk
and resolves that path to the actual path of the device node.
device_path_parse_major_minor() reverses device_path_make_major_minor(),
also withozut going to disk.
We have similar code doing something like this at various places, let's
unify this in a single set of functions. This also allows us to teach
them special tricks, for example handling of the
/run/systemd/inaccessible/{blk|chr} device nodes, which we use for
masking device nodes, and which do not exist in /dev/char/* and
/dev/block/*
Diffstat (limited to 'src')
-rw-r--r-- | src/basic/stat-util.c | 98 | ||||
-rw-r--r-- | src/basic/stat-util.h | 4 | ||||
-rw-r--r-- | src/core/cgroup.c | 52 | ||||
-rw-r--r-- | src/test/test-stat-util.c | 40 |
4 files changed, 144 insertions, 50 deletions
diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index 8b63eb360b..57700e2388 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -10,11 +10,13 @@ #include <sys/types.h> #include <unistd.h> +#include "alloc-util.h" #include "dirent-util.h" #include "fd-util.h" #include "fs-util.h" #include "macro.h" #include "missing.h" +#include "parse-util.h" #include "stat-util.h" #include "string-util.h" @@ -319,3 +321,99 @@ int fd_verify_directory(int fd) { return stat_verify_directory(&st); } + +int device_path_make_major_minor(mode_t mode, dev_t devno, char **ret) { + const char *t; + + /* Generates the /dev/{char|block}/MAJOR:MINOR path for a dev_t */ + + if (S_ISCHR(mode)) + t = "char"; + else if (S_ISBLK(mode)) + t = "block"; + else + return -ENODEV; + + if (asprintf(ret, "/dev/%s/%u:%u", t, major(devno), minor(devno)) < 0) + return -ENOMEM; + + return 0; + +} + +int device_path_make_canonical(mode_t mode, dev_t devno, char **ret) { + _cleanup_free_ char *p = NULL; + int r; + + /* Finds the canonical path for a device, i.e. resolves the /dev/{char|block}/MAJOR:MINOR path to the end. */ + + assert(ret); + + if (major(devno) == 0 && minor(devno) == 0) { + char *s; + + /* A special hack to make sure our 'inaccessible' device nodes work. They won't have symlinks in + * /dev/block/ and /dev/char/, hence we handle them specially here. */ + + if (S_ISCHR(mode)) + s = strdup("/run/systemd/inaccessible/chr"); + else if (S_ISBLK(mode)) + s = strdup("/run/systemd/inaccessible/blk"); + else + return -ENODEV; + + if (!s) + return -ENOMEM; + + *ret = s; + return 0; + } + + r = device_path_make_major_minor(mode, devno, &p); + if (r < 0) + return r; + + return chase_symlinks(p, NULL, 0, ret); +} + +int device_path_parse_major_minor(const char *path, mode_t *ret_mode, dev_t *ret_devno) { + mode_t mode; + dev_t devno; + int r; + + /* Tries to extract the major/minor directly from the device path if we can. Handles /dev/block/ and /dev/char/ + * paths, as well out synthetic inaccessible device nodes. Never goes to disk. Returns -ENODEV if the device + * path cannot be parsed like this. */ + + if (path_equal(path, "/run/systemd/inaccessible/chr")) { + mode = S_IFCHR; + devno = makedev(0, 0); + } else if (path_equal(path, "/run/systemd/inaccessible/blk")) { + mode = S_IFBLK; + devno = makedev(0, 0); + } else { + const char *w; + + w = path_startswith(path, "/dev/block/"); + if (w) + mode = S_IFBLK; + else { + w = path_startswith(path, "/dev/char/"); + if (!w) + return -ENODEV; + + mode = S_IFCHR; + } + + r = parse_dev(w, &devno); + if (r < 0) + return r; + } + + if (ret_mode) + *ret_mode = mode; + if (ret_devno) + *ret_devno = devno; + + return 0; +} diff --git a/src/basic/stat-util.h b/src/basic/stat-util.h index fe4a4bb717..0a08e642b5 100644 --- a/src/basic/stat-util.h +++ b/src/basic/stat-util.h @@ -81,3 +81,7 @@ int fd_verify_directory(int fd); typeof(x) _x = (x), _y = 0; \ _x >= _y && _x < (UINT32_C(1) << 20); \ }) + +int device_path_make_major_minor(mode_t mode, dev_t devno, char **ret); +int device_path_make_canonical(mode_t mode, dev_t devno, char **ret); +int device_path_parse_major_minor(const char *path, mode_t *ret_mode, dev_t *ret_devno); diff --git a/src/core/cgroup.c b/src/core/cgroup.c index dd9b992ef1..72af5e855f 100644 --- a/src/core/cgroup.c +++ b/src/core/cgroup.c @@ -408,56 +408,8 @@ static int lookup_block_device(const char *p, dev_t *ret) { return 0; } -static int shortcut_special_device_path(const char *p, struct stat *ret) { - const char *w; - mode_t mode; - dev_t devt; - int r; - - assert(p); - assert(ret); - - if (path_equal(p, "/run/systemd/inaccessible/chr")) { - *ret = (struct stat) { - .st_mode = S_IFCHR, - .st_rdev = makedev(0, 0), - }; - return 0; - } - - if (path_equal(p, "/run/systemd/inaccessible/blk")) { - *ret = (struct stat) { - .st_mode = S_IFBLK, - .st_rdev = makedev(0, 0), - }; - return 0; - } - - w = path_startswith(p, "/dev/block/"); - if (w) - mode = S_IFBLK; - else { - w = path_startswith(p, "/dev/char/"); - if (!w) - return -ENODEV; - - mode = S_IFCHR; - } - - r = parse_dev(w, &devt); - if (r < 0) - return r; - - *ret = (struct stat) { - .st_mode = mode, - .st_rdev = devt, - }; - - return 0; -} - static int whitelist_device(BPFProgram *prog, const char *path, const char *node, const char *acc) { - struct stat st; + struct stat st = {}; int r; assert(path); @@ -466,7 +418,7 @@ static int whitelist_device(BPFProgram *prog, const char *path, const char *node /* Some special handling for /dev/block/%u:%u, /dev/char/%u:%u, /run/systemd/inaccessible/chr and * /run/systemd/inaccessible/blk paths. Instead of stat()ing these we parse out the major/minor directly. This * means clients can use these path without the device node actually around */ - r = shortcut_special_device_path(node, &st); + r = device_path_parse_major_minor(node, &st.st_mode, &st.st_rdev); if (r < 0) { if (r != -ENODEV) return log_warning_errno(r, "Couldn't parse major/minor from device path '%s': %m", node); diff --git a/src/test/test-stat-util.c b/src/test/test-stat-util.c index 713fbc9a08..4201edac97 100644 --- a/src/test/test-stat-util.c +++ b/src/test/test-stat-util.c @@ -11,6 +11,7 @@ #include "missing.h" #include "mount-util.h" #include "stat-util.h" +#include "path-util.h" static void test_files_same(void) { _cleanup_close_ int fd = -1; @@ -116,6 +117,44 @@ static void test_device_major_minor_valid(void) { assert_se(DEVICE_MINOR_VALID(minor(0))); } +static void test_device_path_make_canonical_one(const char *path) { + _cleanup_free_ char *resolved = NULL, *raw = NULL; + struct stat st; + dev_t devno; + mode_t mode; + int r; + + assert_se(stat(path, &st) >= 0); + r = device_path_make_canonical(st.st_mode, st.st_rdev, &resolved); + if (r == -ENOENT) /* maybe /dev/char/x:y and /dev/block/x:y are missing in this test environment, because we + * run in a container or so? */ + return; + + assert_se(r >= 0); + assert_se(path_equal(path, resolved)); + + assert_se(device_path_make_major_minor(st.st_mode, st.st_rdev, &raw) >= 0); + assert_se(device_path_parse_major_minor(raw, &mode, &devno) >= 0); + + assert_se(st.st_rdev == devno); + assert_se((st.st_mode & S_IFMT) == (mode & S_IFMT)); +} + +static void test_device_path_make_canonical(void) { + + test_device_path_make_canonical_one("/dev/null"); + test_device_path_make_canonical_one("/dev/zero"); + test_device_path_make_canonical_one("/dev/full"); + test_device_path_make_canonical_one("/dev/random"); + test_device_path_make_canonical_one("/dev/urandom"); + test_device_path_make_canonical_one("/dev/tty"); + + if (is_device_node("/run/systemd/inaccessible/chr") > 0) { + test_device_path_make_canonical_one("/run/systemd/inaccessible/chr"); + test_device_path_make_canonical_one("/run/systemd/inaccessible/blk"); + } +} + int main(int argc, char *argv[]) { test_files_same(); test_is_symlink(); @@ -123,6 +162,7 @@ int main(int argc, char *argv[]) { test_path_is_temporary_fs(); test_fd_is_network_ns(); test_device_major_minor_valid(); + test_device_path_make_canonical(); return 0; } |