diff options
Diffstat (limited to 'src/basic/devnum-util.c')
-rw-r--r-- | src/basic/devnum-util.c | 138 |
1 files changed, 138 insertions, 0 deletions
diff --git a/src/basic/devnum-util.c b/src/basic/devnum-util.c new file mode 100644 index 0000000..f82e13b --- /dev/null +++ b/src/basic/devnum-util.c @@ -0,0 +1,138 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <string.h> +#include <sys/stat.h> + +#include "chase.h" +#include "devnum-util.h" +#include "parse-util.h" +#include "path-util.h" +#include "string-util.h" + +int parse_devnum(const char *s, dev_t *ret) { + const char *major; + unsigned x, y; + size_t n; + int r; + + n = strspn(s, DIGITS); + if (n == 0) + return -EINVAL; + if (n > DECIMAL_STR_MAX(dev_t)) + return -EINVAL; + if (s[n] != ':') + return -EINVAL; + + major = strndupa_safe(s, n); + r = safe_atou(major, &x); + if (r < 0) + return r; + + r = safe_atou(s + n + 1, &y); + if (r < 0) + return r; + + if (!DEVICE_MAJOR_VALID(x) || !DEVICE_MINOR_VALID(y)) + return -ERANGE; + + *ret = makedev(x, y); + return 0; +} + +int device_path_make_major_minor(mode_t mode, dev_t devnum, 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/" DEVNUM_FORMAT_STR, t, DEVNUM_FORMAT_VAL(devnum)) < 0) + return -ENOMEM; + + return 0; +} + +int device_path_make_inaccessible(mode_t mode, char **ret) { + char *s; + + assert(ret); + + 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; +} + +int device_path_make_canonical(mode_t mode, dev_t devnum, 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 (devnum_is_zero(devnum)) + /* 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. */ + return device_path_make_inaccessible(mode, ret); + + r = device_path_make_major_minor(mode, devnum, &p); + if (r < 0) + return r; + + return chase(p, NULL, 0, ret, NULL); +} + +int device_path_parse_major_minor(const char *path, mode_t *ret_mode, dev_t *ret_devnum) { + mode_t mode; + dev_t devnum; + 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; + devnum = makedev(0, 0); + } else if (path_equal(path, "/run/systemd/inaccessible/blk")) { + mode = S_IFBLK; + devnum = 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_devnum(w, &devnum); + if (r < 0) + return r; + } + + if (ret_mode) + *ret_mode = mode; + if (ret_devnum) + *ret_devnum = devnum; + + return 0; +} |