summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2018-06-29 15:57:49 +0200
committerLennart Poettering <lennart@poettering.net>2018-11-29 20:21:39 +0100
commit846b3bd61e1d575b0b28f73c4d15385f94bb1662 (patch)
tree6c5a149cf229086e30ee76a0c9d0a55f67ea8e3a /src
parent8e8b5d2e6d91180a57844b09cdbdcbc1fa466bfa (diff)
downloadsystemd-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.c98
-rw-r--r--src/basic/stat-util.h4
-rw-r--r--src/core/cgroup.c52
-rw-r--r--src/test/test-stat-util.c40
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;
}