1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <string.h>
4 #include <sys/stat.h>
5
6 #include "chase-symlinks.h"
7 #include "devnum-util.h"
8 #include "parse-util.h"
9 #include "path-util.h"
10 #include "string-util.h"
11
parse_devnum(const char * s,dev_t * ret)12 int parse_devnum(const char *s, dev_t *ret) {
13 const char *major;
14 unsigned x, y;
15 size_t n;
16 int r;
17
18 n = strspn(s, DIGITS);
19 if (n == 0)
20 return -EINVAL;
21 if (n > DECIMAL_STR_MAX(dev_t))
22 return -EINVAL;
23 if (s[n] != ':')
24 return -EINVAL;
25
26 major = strndupa_safe(s, n);
27 r = safe_atou(major, &x);
28 if (r < 0)
29 return r;
30
31 r = safe_atou(s + n + 1, &y);
32 if (r < 0)
33 return r;
34
35 if (!DEVICE_MAJOR_VALID(x) || !DEVICE_MINOR_VALID(y))
36 return -ERANGE;
37
38 *ret = makedev(x, y);
39 return 0;
40 }
41
device_path_make_major_minor(mode_t mode,dev_t devnum,char ** ret)42 int device_path_make_major_minor(mode_t mode, dev_t devnum, char **ret) {
43 const char *t;
44
45 /* Generates the /dev/{char|block}/MAJOR:MINOR path for a dev_t */
46
47 if (S_ISCHR(mode))
48 t = "char";
49 else if (S_ISBLK(mode))
50 t = "block";
51 else
52 return -ENODEV;
53
54 if (asprintf(ret, "/dev/%s/" DEVNUM_FORMAT_STR, t, DEVNUM_FORMAT_VAL(devnum)) < 0)
55 return -ENOMEM;
56
57 return 0;
58 }
59
device_path_make_canonical(mode_t mode,dev_t devnum,char ** ret)60 int device_path_make_canonical(mode_t mode, dev_t devnum, char **ret) {
61 _cleanup_free_ char *p = NULL;
62 int r;
63
64 /* Finds the canonical path for a device, i.e. resolves the /dev/{char|block}/MAJOR:MINOR path to the end. */
65
66 assert(ret);
67
68 if (major(devnum) == 0 && minor(devnum) == 0) {
69 char *s;
70
71 /* A special hack to make sure our 'inaccessible' device nodes work. They won't have symlinks in
72 * /dev/block/ and /dev/char/, hence we handle them specially here. */
73
74 if (S_ISCHR(mode))
75 s = strdup("/run/systemd/inaccessible/chr");
76 else if (S_ISBLK(mode))
77 s = strdup("/run/systemd/inaccessible/blk");
78 else
79 return -ENODEV;
80
81 if (!s)
82 return -ENOMEM;
83
84 *ret = s;
85 return 0;
86 }
87
88 r = device_path_make_major_minor(mode, devnum, &p);
89 if (r < 0)
90 return r;
91
92 return chase_symlinks(p, NULL, 0, ret, NULL);
93 }
94
device_path_parse_major_minor(const char * path,mode_t * ret_mode,dev_t * ret_devnum)95 int device_path_parse_major_minor(const char *path, mode_t *ret_mode, dev_t *ret_devnum) {
96 mode_t mode;
97 dev_t devnum;
98 int r;
99
100 /* Tries to extract the major/minor directly from the device path if we can. Handles /dev/block/ and /dev/char/
101 * paths, as well out synthetic inaccessible device nodes. Never goes to disk. Returns -ENODEV if the device
102 * path cannot be parsed like this. */
103
104 if (path_equal(path, "/run/systemd/inaccessible/chr")) {
105 mode = S_IFCHR;
106 devnum = makedev(0, 0);
107 } else if (path_equal(path, "/run/systemd/inaccessible/blk")) {
108 mode = S_IFBLK;
109 devnum = makedev(0, 0);
110 } else {
111 const char *w;
112
113 w = path_startswith(path, "/dev/block/");
114 if (w)
115 mode = S_IFBLK;
116 else {
117 w = path_startswith(path, "/dev/char/");
118 if (!w)
119 return -ENODEV;
120
121 mode = S_IFCHR;
122 }
123
124 r = parse_devnum(w, &devnum);
125 if (r < 0)
126 return r;
127 }
128
129 if (ret_mode)
130 *ret_mode = mode;
131 if (ret_devnum)
132 *ret_devnum = devnum;
133
134 return 0;
135 }
136