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