]>
Commit | Line | Data |
---|---|---|
ea733a2f | 1 | /* |
22582bb2 | 2 | * Copyright (C) 2003-2013 Kay Sievers <kay@vrfy.org> |
ea733a2f | 3 | * |
55e9959b KS |
4 | * This program is free software: you can redistribute it and/or modify |
5 | * it under the terms of the GNU General Public License as published by | |
6 | * the Free Software Foundation, either version 2 of the License, or | |
7 | * (at your option) any later version. | |
ea733a2f | 8 | * |
55e9959b KS |
9 | * This program is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | * GNU General Public License for more details. | |
13 | * | |
14 | * You should have received a copy of the GNU General Public License | |
15 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
ea733a2f GKH |
16 | */ |
17 | ||
18 | #include <stdlib.h> | |
19 | #include <string.h> | |
20 | #include <stdio.h> | |
1aa1e248 | 21 | #include <stddef.h> |
6c29f2b9 | 22 | #include <stdbool.h> |
ea733a2f GKH |
23 | #include <fcntl.h> |
24 | #include <unistd.h> | |
25 | #include <errno.h> | |
67f69ae1 | 26 | #include <grp.h> |
24f0605c | 27 | #include <dirent.h> |
56073914 | 28 | #include <sys/time.h> |
32ff5bca | 29 | #include <sys/stat.h> |
10950dfe | 30 | #include <sys/types.h> |
ea733a2f GKH |
31 | |
32 | #include "udev.h" | |
9825617b | 33 | |
38d70045 | 34 | static int node_symlink(struct udev_device *dev, const char *node, const char *slink) |
fa33d857 | 35 | { |
912541b0 KS |
36 | struct stat stats; |
37 | char target[UTIL_PATH_SIZE]; | |
38 | char *s; | |
39 | size_t l; | |
38d70045 | 40 | char slink_tmp[UTIL_PATH_SIZE + 32]; |
912541b0 KS |
41 | int i = 0; |
42 | int tail = 0; | |
43 | int err = 0; | |
44 | ||
45 | /* use relative link */ | |
46 | target[0] = '\0'; | |
47 | while (node[i] && (node[i] == slink[i])) { | |
48 | if (node[i] == '/') | |
49 | tail = i+1; | |
50 | i++; | |
51 | } | |
52 | s = target; | |
53 | l = sizeof(target); | |
54 | while (slink[i] != '\0') { | |
55 | if (slink[i] == '/') | |
d5a89d7d | 56 | l = strpcpy(&s, l, "../"); |
912541b0 KS |
57 | i++; |
58 | } | |
d5a89d7d | 59 | l = strscpy(s, l, &node[tail]); |
912541b0 KS |
60 | if (l == 0) { |
61 | err = -EINVAL; | |
62 | goto exit; | |
63 | } | |
64 | ||
65 | /* preserve link with correct target, do not replace node of other device */ | |
66 | if (lstat(slink, &stats) == 0) { | |
67 | if (S_ISBLK(stats.st_mode) || S_ISCHR(stats.st_mode)) { | |
2be7287b KS |
68 | log_error("conflicting device node '%s' found, link to '%s' will not be created\n", slink, node); |
69 | goto exit; | |
912541b0 KS |
70 | } else if (S_ISLNK(stats.st_mode)) { |
71 | char buf[UTIL_PATH_SIZE]; | |
72 | int len; | |
73 | ||
912541b0 KS |
74 | len = readlink(slink, buf, sizeof(buf)); |
75 | if (len > 0 && len < (int)sizeof(buf)) { | |
76 | buf[len] = '\0'; | |
090be865 | 77 | if (streq(target, buf)) { |
baa30fbc | 78 | log_debug("preserve already existing symlink '%s' to '%s'\n", slink, target); |
c9bc0764 | 79 | label_fix(slink, true, false); |
912541b0 KS |
80 | utimensat(AT_FDCWD, slink, NULL, AT_SYMLINK_NOFOLLOW); |
81 | goto exit; | |
82 | } | |
83 | } | |
84 | } | |
85 | } else { | |
baa30fbc | 86 | log_debug("creating symlink '%s' to '%s'\n", slink, target); |
912541b0 | 87 | do { |
d2e54fae | 88 | err = mkdir_parents_label(slink, 0755); |
912541b0 KS |
89 | if (err != 0 && err != -ENOENT) |
90 | break; | |
e9a5ef7c | 91 | label_context_set(slink, S_IFLNK); |
912541b0 KS |
92 | err = symlink(target, slink); |
93 | if (err != 0) | |
94 | err = -errno; | |
e9a5ef7c | 95 | label_context_clear(); |
912541b0 KS |
96 | } while (err == -ENOENT); |
97 | if (err == 0) | |
98 | goto exit; | |
99 | } | |
100 | ||
baa30fbc | 101 | log_debug("atomically replace '%s'\n", slink); |
38d70045 | 102 | strscpyl(slink_tmp, sizeof(slink_tmp), slink, ".tmp-", udev_device_get_id_filename(dev), NULL); |
912541b0 KS |
103 | unlink(slink_tmp); |
104 | do { | |
d2e54fae | 105 | err = mkdir_parents_label(slink_tmp, 0755); |
912541b0 KS |
106 | if (err != 0 && err != -ENOENT) |
107 | break; | |
e9a5ef7c | 108 | label_context_set(slink_tmp, S_IFLNK); |
912541b0 KS |
109 | err = symlink(target, slink_tmp); |
110 | if (err != 0) | |
111 | err = -errno; | |
e9a5ef7c | 112 | label_context_clear(); |
912541b0 KS |
113 | } while (err == -ENOENT); |
114 | if (err != 0) { | |
baa30fbc | 115 | log_error("symlink '%s' '%s' failed: %m\n", target, slink_tmp); |
912541b0 KS |
116 | goto exit; |
117 | } | |
118 | err = rename(slink_tmp, slink); | |
119 | if (err != 0) { | |
baa30fbc | 120 | log_error("rename '%s' '%s' failed: %m\n", slink_tmp, slink); |
912541b0 KS |
121 | unlink(slink_tmp); |
122 | } | |
fa33d857 | 123 | exit: |
912541b0 | 124 | return err; |
fa33d857 KS |
125 | } |
126 | ||
6c29f2b9 KS |
127 | /* find device node of device with highest priority */ |
128 | static const char *link_find_prioritized(struct udev_device *dev, bool add, const char *stackdir, char *buf, size_t bufsize) | |
24f0605c | 129 | { |
912541b0 KS |
130 | struct udev *udev = udev_device_get_udev(dev); |
131 | DIR *dir; | |
132 | int priority = 0; | |
133 | const char *target = NULL; | |
134 | ||
135 | if (add) { | |
136 | priority = udev_device_get_devlink_priority(dev); | |
d5a89d7d | 137 | strscpy(buf, bufsize, udev_device_get_devnode(dev)); |
912541b0 KS |
138 | target = buf; |
139 | } | |
140 | ||
141 | dir = opendir(stackdir); | |
142 | if (dir == NULL) | |
143 | return target; | |
144 | for (;;) { | |
145 | struct udev_device *dev_db; | |
146 | struct dirent *dent; | |
147 | ||
148 | dent = readdir(dir); | |
149 | if (dent == NULL || dent->d_name[0] == '\0') | |
150 | break; | |
151 | if (dent->d_name[0] == '.') | |
152 | continue; | |
153 | ||
baa30fbc | 154 | log_debug("found '%s' claiming '%s'\n", dent->d_name, stackdir); |
912541b0 KS |
155 | |
156 | /* did we find ourself? */ | |
090be865 | 157 | if (streq(dent->d_name, udev_device_get_id_filename(dev))) |
912541b0 KS |
158 | continue; |
159 | ||
dbf61afb | 160 | dev_db = udev_device_new_from_device_id(udev, dent->d_name); |
912541b0 KS |
161 | if (dev_db != NULL) { |
162 | const char *devnode; | |
163 | ||
164 | devnode = udev_device_get_devnode(dev_db); | |
165 | if (devnode != NULL) { | |
912541b0 | 166 | if (target == NULL || udev_device_get_devlink_priority(dev_db) > priority) { |
baa30fbc KS |
167 | log_debug("'%s' claims priority %i for '%s'\n", |
168 | udev_device_get_syspath(dev_db), udev_device_get_devlink_priority(dev_db), stackdir); | |
912541b0 | 169 | priority = udev_device_get_devlink_priority(dev_db); |
d5a89d7d | 170 | strscpy(buf, bufsize, devnode); |
912541b0 KS |
171 | target = buf; |
172 | } | |
173 | } | |
174 | udev_device_unref(dev_db); | |
175 | } | |
176 | } | |
177 | closedir(dir); | |
178 | return target; | |
aa8734ff KS |
179 | } |
180 | ||
6c29f2b9 KS |
181 | /* manage "stack of names" with possibly specified device priorities */ |
182 | static void link_update(struct udev_device *dev, const char *slink, bool add) | |
aa8734ff | 183 | { |
912541b0 KS |
184 | struct udev *udev = udev_device_get_udev(dev); |
185 | char name_enc[UTIL_PATH_SIZE]; | |
186 | char filename[UTIL_PATH_SIZE * 2]; | |
187 | char dirname[UTIL_PATH_SIZE]; | |
188 | const char *target; | |
189 | char buf[UTIL_PATH_SIZE]; | |
190 | ||
4cb72937 | 191 | util_path_encode(slink + strlen("/dev"), name_enc, sizeof(name_enc)); |
d5a89d7d KS |
192 | strscpyl(dirname, sizeof(dirname), "/run/udev/links/", name_enc, NULL); |
193 | strscpyl(filename, sizeof(filename), dirname, "/", udev_device_get_id_filename(dev), NULL); | |
912541b0 | 194 | |
baa30fbc KS |
195 | if (!add && unlink(filename) == 0) |
196 | rmdir(dirname); | |
912541b0 KS |
197 | |
198 | target = link_find_prioritized(dev, add, dirname, buf, sizeof(buf)); | |
199 | if (target == NULL) { | |
baa30fbc | 200 | log_debug("no reference left, remove '%s'\n", slink); |
912541b0 KS |
201 | if (unlink(slink) == 0) |
202 | util_delete_path(udev, slink); | |
203 | } else { | |
baa30fbc | 204 | log_debug("creating link '%s' to '%s'\n", slink, target); |
38d70045 | 205 | node_symlink(dev, target, slink); |
912541b0 KS |
206 | } |
207 | ||
208 | if (add) { | |
209 | int err; | |
210 | ||
912541b0 KS |
211 | do { |
212 | int fd; | |
213 | ||
3cbd5f6b | 214 | err = mkdir_parents(filename, 0755); |
912541b0 KS |
215 | if (err != 0 && err != -ENOENT) |
216 | break; | |
217 | fd = open(filename, O_WRONLY|O_CREAT|O_CLOEXEC|O_TRUNC|O_NOFOLLOW, 0444); | |
218 | if (fd >= 0) | |
219 | close(fd); | |
220 | else | |
221 | err = -errno; | |
222 | } while (err == -ENOENT); | |
223 | } | |
24f0605c KS |
224 | } |
225 | ||
ec2dd02e | 226 | void udev_node_update_old_links(struct udev_device *dev, struct udev_device *dev_old) |
24f0605c | 227 | { |
912541b0 KS |
228 | struct udev_list_entry *list_entry; |
229 | ||
230 | /* update possible left-over symlinks */ | |
231 | udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(dev_old)) { | |
232 | const char *name = udev_list_entry_get_name(list_entry); | |
233 | struct udev_list_entry *list_entry_current; | |
234 | int found; | |
235 | ||
236 | /* check if old link name still belongs to this device */ | |
237 | found = 0; | |
238 | udev_list_entry_foreach(list_entry_current, udev_device_get_devlinks_list_entry(dev)) { | |
239 | const char *name_current = udev_list_entry_get_name(list_entry_current); | |
240 | ||
090be865 | 241 | if (streq(name, name_current)) { |
912541b0 KS |
242 | found = 1; |
243 | break; | |
244 | } | |
245 | } | |
246 | if (found) | |
247 | continue; | |
248 | ||
baa30fbc | 249 | log_debug("update old name, '%s' no longer belonging to '%s'\n", |
912541b0 | 250 | name, udev_device_get_devpath(dev)); |
8a173387 | 251 | link_update(dev, name, false); |
912541b0 | 252 | } |
24f0605c KS |
253 | } |
254 | ||
22582bb2 | 255 | static int node_permissions_apply(struct udev_device *dev, bool apply, mode_t mode, uid_t uid, gid_t gid) |
220893b3 | 256 | { |
912541b0 KS |
257 | const char *devnode = udev_device_get_devnode(dev); |
258 | dev_t devnum = udev_device_get_devnum(dev); | |
259 | struct stat stats; | |
260 | int err = 0; | |
261 | ||
090be865 | 262 | if (streq(udev_device_get_subsystem(dev), "block")) |
912541b0 KS |
263 | mode |= S_IFBLK; |
264 | else | |
265 | mode |= S_IFCHR; | |
266 | ||
267 | if (lstat(devnode, &stats) != 0) { | |
268 | err = -errno; | |
baa30fbc | 269 | log_debug("can not stat() node '%s' (%m)\n", devnode); |
912541b0 KS |
270 | goto out; |
271 | } | |
272 | ||
273 | if (((stats.st_mode & S_IFMT) != (mode & S_IFMT)) || (stats.st_rdev != devnum)) { | |
274 | err = -EEXIST; | |
baa30fbc KS |
275 | log_debug("found node '%s' with non-matching devnum %s, skip handling\n", |
276 | udev_device_get_devnode(dev), udev_device_get_id_filename(dev)); | |
912541b0 KS |
277 | goto out; |
278 | } | |
279 | ||
22582bb2 | 280 | if (apply) { |
48a849ee KS |
281 | if ((stats.st_mode & 0777) != (mode & 0777) || stats.st_uid != uid || stats.st_gid != gid) { |
282 | log_debug("set permissions %s, %#o, uid=%u, gid=%u\n", devnode, mode, uid, gid); | |
283 | chmod(devnode, mode); | |
284 | chown(devnode, uid, gid); | |
285 | } else { | |
286 | log_debug("preserve permissions %s, %#o, uid=%u, gid=%u\n", devnode, mode, uid, gid); | |
287 | } | |
48a849ee KS |
288 | label_fix(devnode, true, false); |
289 | } | |
912541b0 KS |
290 | |
291 | /* always update timestamp when we re-use the node, like on media change events */ | |
292 | utimensat(AT_FDCWD, devnode, NULL, 0); | |
220893b3 | 293 | out: |
912541b0 | 294 | return err; |
220893b3 KS |
295 | } |
296 | ||
22582bb2 | 297 | void udev_node_add(struct udev_device *dev, bool apply, mode_t mode, uid_t uid, gid_t gid) |
97853b4f | 298 | { |
912541b0 KS |
299 | char filename[UTIL_PATH_SIZE]; |
300 | struct udev_list_entry *list_entry; | |
912541b0 | 301 | |
baa30fbc KS |
302 | log_debug("handling device node '%s', devnum=%s, mode=%#o, uid=%d, gid=%d\n", |
303 | udev_device_get_devnode(dev), udev_device_get_id_filename(dev), mode, uid, gid); | |
912541b0 | 304 | |
22582bb2 | 305 | if (node_permissions_apply(dev, apply, mode, uid, gid) < 0) |
e7f32890 | 306 | return; |
912541b0 KS |
307 | |
308 | /* always add /dev/{block,char}/$major:$minor */ | |
4cb72937 | 309 | snprintf(filename, sizeof(filename), "/dev/%s/%u:%u", |
090be865 | 310 | streq(udev_device_get_subsystem(dev), "block") ? "block" : "char", |
912541b0 | 311 | major(udev_device_get_devnum(dev)), minor(udev_device_get_devnum(dev))); |
38d70045 | 312 | node_symlink(dev, udev_device_get_devnode(dev), filename); |
912541b0 KS |
313 | |
314 | /* create/update symlinks, add symlinks to name index */ | |
8a173387 KS |
315 | udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(dev)) |
316 | link_update(dev, udev_list_entry_get_name(list_entry), true); | |
ea733a2f GKH |
317 | } |
318 | ||
e7f32890 | 319 | void udev_node_remove(struct udev_device *dev) |
ff9a488d | 320 | { |
912541b0 | 321 | struct udev_list_entry *list_entry; |
912541b0 | 322 | char filename[UTIL_PATH_SIZE]; |
912541b0 KS |
323 | |
324 | /* remove/update symlinks, remove symlinks from name index */ | |
325 | udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(dev)) | |
8a173387 | 326 | link_update(dev, udev_list_entry_get_name(list_entry), false); |
912541b0 KS |
327 | |
328 | /* remove /dev/{block,char}/$major:$minor */ | |
4cb72937 | 329 | snprintf(filename, sizeof(filename), "/dev/%s/%u:%u", |
090be865 | 330 | streq(udev_device_get_subsystem(dev), "block") ? "block" : "char", |
912541b0 KS |
331 | major(udev_device_get_devnum(dev)), minor(udev_device_get_devnum(dev))); |
332 | unlink(filename); | |
ea733a2f | 333 | } |