]>
Commit | Line | Data |
---|---|---|
ea733a2f | 1 | /* |
ea733a2f | 2 | * Copyright (C) 2003 Greg Kroah-Hartman <greg@kroah.com> |
27b77df4 | 3 | * Copyright (C) 2004-2006 Kay Sievers <kay.sievers@vrfy.org> |
ea733a2f GKH |
4 | * |
5 | * This program is free software; you can redistribute it and/or modify it | |
6 | * under the terms of the GNU General Public License as published by the | |
7 | * Free Software Foundation version 2 of the License. | |
8 | * | |
9 | * This program is distributed in the hope that it will be useful, but | |
10 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
12 | * General Public License for more details. | |
13 | * | |
14 | * You should have received a copy of the GNU General Public License along | |
15 | * with this program; if not, write to the Free Software Foundation, Inc., | |
27b77df4 | 16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
ea733a2f GKH |
17 | * |
18 | */ | |
19 | ||
20 | #include <stdlib.h> | |
21 | #include <string.h> | |
22 | #include <stdio.h> | |
1aa1e248 | 23 | #include <stddef.h> |
ea733a2f GKH |
24 | #include <fcntl.h> |
25 | #include <unistd.h> | |
26 | #include <errno.h> | |
67f69ae1 | 27 | #include <grp.h> |
24f0605c | 28 | #include <dirent.h> |
32ff5bca | 29 | #include <sys/stat.h> |
10950dfe | 30 | #include <sys/types.h> |
ea733a2f GKH |
31 | |
32 | #include "udev.h" | |
e5e322bc | 33 | #include "udev_rules.h" |
fbda4a34 | 34 | #include "udev_selinux.h" |
9825617b | 35 | |
e54f0b4a | 36 | #define TMP_FILE_EXT ".udev-tmp" |
ea733a2f | 37 | |
a4d5ca64 | 38 | int udev_node_mknod(struct udevice *udev, const char *file, dev_t devt, mode_t mode, uid_t uid, gid_t gid) |
50e5de03 | 39 | { |
e54f0b4a | 40 | char file_tmp[PATH_SIZE + sizeof(TMP_FILE_EXT)]; |
49747bdc KS |
41 | struct stat stats; |
42 | int retval = 0; | |
43 | ||
1aa1e248 | 44 | if (major(devt) != 0 && strcmp(udev->dev->subsystem, "block") == 0) |
57663b36 | 45 | mode |= S_IFBLK; |
1aa1e248 | 46 | else |
57663b36 | 47 | mode |= S_IFCHR; |
57663b36 | 48 | |
e54f0b4a KS |
49 | if (lstat(file, &stats) == 0) { |
50 | if ((stats.st_mode & S_IFMT) == (mode & S_IFMT) && (stats.st_rdev == devt)) { | |
51 | info("preserve file '%s', because it has correct dev_t", file); | |
52 | selinux_setfilecon(file, udev->dev->kernel, stats.st_mode); | |
53 | goto perms; | |
54 | } | |
678484af KS |
55 | } else { |
56 | selinux_setfscreatecon(file, udev->dev->kernel, mode); | |
57 | retval = mknod(file, mode, devt); | |
58 | selinux_resetfscreatecon(); | |
59 | if (retval == 0) | |
60 | goto perms; | |
49747bdc KS |
61 | } |
62 | ||
e54f0b4a KS |
63 | info("atomically replace '%s'", file); |
64 | strlcpy(file_tmp, file, sizeof(file_tmp)); | |
65 | strlcat(file_tmp, TMP_FILE_EXT, sizeof(file_tmp)); | |
14c79942 | 66 | unlink(file_tmp); |
e54f0b4a KS |
67 | selinux_setfscreatecon(file_tmp, udev->dev->kernel, mode); |
68 | retval = mknod(file_tmp, mode, devt); | |
69 | selinux_resetfscreatecon(); | |
50e5de03 | 70 | if (retval != 0) { |
ff3e4bed | 71 | err("mknod(%s, %#o, %u, %u) failed: %s", |
e54f0b4a KS |
72 | file_tmp, mode, major(devt), minor(devt), strerror(errno)); |
73 | goto exit; | |
74 | } | |
75 | retval = rename(file_tmp, file); | |
76 | if (retval != 0) { | |
77 | err("rename(%s, %s) failed: %s", | |
78 | file_tmp, file, strerror(errno)); | |
79 | unlink(file_tmp); | |
bbbe503e | 80 | goto exit; |
50e5de03 KS |
81 | } |
82 | ||
49747bdc KS |
83 | perms: |
84 | dbg("chmod(%s, %#o)", file, mode); | |
85 | if (chmod(file, mode) != 0) { | |
df4e89bf | 86 | err("chmod(%s, %#o) failed: %s", file, mode, strerror(errno)); |
bbbe503e | 87 | goto exit; |
50e5de03 KS |
88 | } |
89 | ||
90 | if (uid != 0 || gid != 0) { | |
49747bdc KS |
91 | dbg("chown(%s, %u, %u)", file, uid, gid); |
92 | if (chown(file, uid, gid) != 0) { | |
df4e89bf | 93 | err("chown(%s, %u, %u) failed: %s", |
49747bdc | 94 | file, uid, gid, strerror(errno)); |
bbbe503e | 95 | goto exit; |
50e5de03 KS |
96 | } |
97 | } | |
bbbe503e KS |
98 | exit: |
99 | return retval; | |
50e5de03 KS |
100 | } |
101 | ||
27d4bf18 | 102 | static int node_symlink(const char *node, const char *slink) |
fa33d857 | 103 | { |
e54f0b4a | 104 | struct stat stats; |
27d4bf18 | 105 | char target[PATH_SIZE] = ""; |
e54f0b4a | 106 | char slink_tmp[PATH_SIZE + sizeof(TMP_FILE_EXT)]; |
27d4bf18 KS |
107 | int i = 0; |
108 | int tail = 0; | |
fa33d857 | 109 | int len; |
e54f0b4a | 110 | int retval = 0; |
fa33d857 | 111 | |
27d4bf18 KS |
112 | /* use relative link */ |
113 | while (node[i] && (node[i] == slink[i])) { | |
114 | if (node[i] == '/') | |
115 | tail = i+1; | |
116 | i++; | |
117 | } | |
118 | while (slink[i] != '\0') { | |
119 | if (slink[i] == '/') | |
120 | strlcat(target, "../", sizeof(target)); | |
121 | i++; | |
122 | } | |
123 | strlcat(target, &node[tail], sizeof(target)); | |
124 | ||
e54f0b4a KS |
125 | /* preserve link with correct target, do not replace node of other device */ |
126 | if (lstat(slink, &stats) == 0) { | |
127 | if (S_ISBLK(stats.st_mode) || S_ISCHR(stats.st_mode)) { | |
128 | struct stat stats2; | |
129 | ||
130 | info("found existing node instead of symlink '%s'", slink); | |
131 | if (lstat(node, &stats2) == 0) { | |
132 | if ((stats.st_mode & S_IFMT) == (stats2.st_mode & S_IFMT) && | |
133 | stats.st_rdev == stats2.st_rdev) { | |
134 | info("replace device node '%s' with symlink to our node '%s'", slink, node); | |
135 | } else { | |
89e0a022 | 136 | err("device node '%s' already exists, link to '%s' will not overwrite it", slink, node); |
e54f0b4a KS |
137 | goto exit; |
138 | } | |
139 | } | |
140 | } else if (S_ISLNK(stats.st_mode)) { | |
141 | char buf[PATH_SIZE]; | |
142 | ||
143 | info("found existing symlink '%s'", slink); | |
144 | len = readlink(slink, buf, sizeof(buf)); | |
145 | if (len > 0) { | |
146 | buf[len] = '\0'; | |
147 | if (strcmp(target, buf) == 0) { | |
148 | info("preserve already existing symlink '%s' to '%s'", slink, target); | |
149 | selinux_setfilecon(slink, NULL, S_IFLNK); | |
150 | goto exit; | |
151 | } | |
152 | } | |
fa33d857 | 153 | } |
678484af KS |
154 | } else { |
155 | info("creating symlink '%s' to '%s'", slink, target); | |
156 | selinux_setfscreatecon(slink, NULL, S_IFLNK); | |
157 | retval = symlink(target, slink); | |
158 | selinux_resetfscreatecon(); | |
159 | if (retval == 0) | |
160 | goto exit; | |
fa33d857 KS |
161 | } |
162 | ||
e54f0b4a KS |
163 | info("atomically replace '%s'", slink); |
164 | strlcpy(slink_tmp, slink, sizeof(slink_tmp)); | |
165 | strlcat(slink_tmp, TMP_FILE_EXT, sizeof(slink_tmp)); | |
14c79942 | 166 | unlink(slink_tmp); |
35ea39e2 | 167 | selinux_setfscreatecon(slink, NULL, S_IFLNK); |
e54f0b4a KS |
168 | retval = symlink(target, slink_tmp); |
169 | selinux_resetfscreatecon(); | |
170 | if (retval != 0) { | |
171 | err("symlink(%s, %s) failed: %s", target, slink_tmp, strerror(errno)); | |
172 | goto exit; | |
173 | } | |
174 | retval = rename(slink_tmp, slink); | |
175 | if (retval != 0) { | |
176 | err("rename(%s, %s) failed: %s", slink_tmp, slink, strerror(errno)); | |
177 | unlink(slink_tmp); | |
178 | goto exit; | |
179 | } | |
fa33d857 | 180 | exit: |
e54f0b4a | 181 | return retval; |
fa33d857 KS |
182 | } |
183 | ||
24f0605c KS |
184 | static int update_link(struct udevice *udev, const char *name) |
185 | { | |
186 | LIST_HEAD(name_list); | |
187 | char slink[PATH_SIZE]; | |
188 | char node[PATH_SIZE]; | |
189 | struct udevice *udev_db; | |
190 | struct name_entry *device; | |
191 | char target[PATH_MAX] = ""; | |
192 | int count; | |
193 | int priority = 0; | |
194 | int rc = 0; | |
195 | ||
196 | strlcpy(slink, udev_root, sizeof(slink)); | |
197 | strlcat(slink, "/", sizeof(slink)); | |
198 | strlcat(slink, name, sizeof(slink)); | |
199 | ||
200 | count = udev_db_get_devices_by_name(name, &name_list); | |
201 | info("found %i devices with name '%s'", count, name); | |
202 | ||
eff4a673 | 203 | /* if we don't have a reference, delete it */ |
24f0605c KS |
204 | if (count <= 0) { |
205 | info("no reference left, remove '%s'", name); | |
206 | if (!udev->test_run) { | |
207 | unlink(slink); | |
208 | delete_path(slink); | |
209 | } | |
210 | goto out; | |
211 | } | |
212 | ||
213 | /* find the device with the highest priority */ | |
214 | list_for_each_entry(device, &name_list, node) { | |
215 | info("found '%s' for '%s'", device->name, name); | |
216 | ||
217 | /* did we find ourself? we win, if we have the same priority */ | |
218 | if (strcmp(udev->dev->devpath, device->name) == 0) { | |
219 | info("compare (our own) priority of '%s' %i >= %i", | |
220 | udev->dev->devpath, udev->link_priority, priority); | |
b2d1ae72 KS |
221 | if (strcmp(udev->name, name) == 0) { |
222 | info("'%s' is our device node, database inconsistent, skip link update", udev->name); | |
223 | } else if (target[0] == '\0' || udev->link_priority >= priority) { | |
24f0605c KS |
224 | priority = udev->link_priority; |
225 | strlcpy(target, udev->name, sizeof(target)); | |
226 | } | |
227 | continue; | |
228 | } | |
229 | ||
b2d1ae72 | 230 | /* another device, read priority from database */ |
24f0605c KS |
231 | udev_db = udev_device_init(NULL); |
232 | if (udev_db == NULL) | |
233 | continue; | |
234 | if (udev_db_get_device(udev_db, device->name) == 0) { | |
25c208d6 KS |
235 | if (strcmp(udev_db->name, name) == 0) { |
236 | info("'%s' is a device node of '%s', skip link update", udev_db->name, device->name); | |
237 | } else { | |
238 | info("compare priority of '%s' %i > %i", | |
239 | udev_db->dev->devpath, udev_db->link_priority, priority); | |
240 | if (target[0] == '\0' || udev_db->link_priority > priority) { | |
241 | priority = udev_db->link_priority; | |
242 | strlcpy(target, udev_db->name, sizeof(target)); | |
243 | } | |
24f0605c KS |
244 | } |
245 | } | |
246 | udev_device_cleanup(udev_db); | |
247 | } | |
248 | name_list_cleanup(&name_list); | |
249 | ||
250 | if (target[0] == '\0') { | |
25c208d6 KS |
251 | info("no current target for '%s' found", name); |
252 | rc = 1; | |
24f0605c KS |
253 | goto out; |
254 | } | |
255 | ||
256 | /* create symlink to the target with the highest priority */ | |
257 | strlcpy(node, udev_root, sizeof(node)); | |
258 | strlcat(node, "/", sizeof(node)); | |
259 | strlcat(node, target, sizeof(node)); | |
260 | info("'%s' with target '%s' has the highest priority %i, create it", name, target, priority); | |
261 | if (!udev->test_run) { | |
262 | create_path(slink); | |
263 | node_symlink(node, slink); | |
264 | } | |
265 | out: | |
266 | return rc; | |
267 | } | |
268 | ||
269 | void udev_node_update_symlinks(struct udevice *udev, struct udevice *udev_old) | |
270 | { | |
271 | struct name_entry *name_loop; | |
272 | char symlinks[PATH_SIZE] = ""; | |
273 | ||
274 | list_for_each_entry(name_loop, &udev->symlink_list, node) { | |
275 | info("update symlink '%s' of '%s'", name_loop->name, udev->dev->devpath); | |
276 | update_link(udev, name_loop->name); | |
277 | strlcat(symlinks, udev_root, sizeof(symlinks)); | |
278 | strlcat(symlinks, "/", sizeof(symlinks)); | |
279 | strlcat(symlinks, name_loop->name, sizeof(symlinks)); | |
280 | strlcat(symlinks, " ", sizeof(symlinks)); | |
281 | } | |
282 | ||
283 | /* export symlinks to environment */ | |
284 | remove_trailing_chars(symlinks, ' '); | |
285 | if (symlinks[0] != '\0') | |
286 | setenv("DEVLINKS", symlinks, 1); | |
287 | ||
288 | /* update possible left-over symlinks (device metadata changed) */ | |
289 | if (udev_old != NULL) { | |
290 | struct name_entry *link_loop; | |
291 | struct name_entry *link_old_loop; | |
24f0605c KS |
292 | int found; |
293 | ||
294 | /* remove current symlinks from old list */ | |
eff4a673 | 295 | list_for_each_entry(link_old_loop, &udev_old->symlink_list, node) { |
24f0605c KS |
296 | found = 0; |
297 | list_for_each_entry(link_loop, &udev->symlink_list, node) { | |
298 | if (strcmp(link_old_loop->name, link_loop->name) == 0) { | |
299 | found = 1; | |
300 | break; | |
301 | } | |
302 | } | |
303 | if (!found) { | |
304 | /* link does no longer belong to this device */ | |
305 | info("update old symlink '%s' no longer belonging to '%s'", | |
306 | link_old_loop->name, udev->dev->devpath); | |
307 | update_link(udev, link_old_loop->name); | |
308 | } | |
309 | } | |
604f104a | 310 | |
eff4a673 KS |
311 | /* |
312 | * if the node name has changed, delete the node, | |
313 | * or possibly restore a symlink of another device | |
314 | */ | |
315 | if (strcmp(udev->name, udev_old->name) != 0) | |
316 | update_link(udev, udev_old->name); | |
24f0605c KS |
317 | } |
318 | } | |
319 | ||
320 | int udev_node_add(struct udevice *udev) | |
97853b4f | 321 | { |
63f61c5c | 322 | char filename[PATH_SIZE]; |
783272f0 KS |
323 | uid_t uid; |
324 | gid_t gid; | |
e48fc108 | 325 | int i; |
a4d5ca64 KS |
326 | int retval = 0; |
327 | ||
24f0605c KS |
328 | strlcpy(filename, udev_root, sizeof(filename)); |
329 | strlcat(filename, "/", sizeof(filename)); | |
330 | strlcat(filename, udev->name, sizeof(filename)); | |
27d4bf18 | 331 | create_path(filename); |
218eae87 | 332 | |
783272f0 KS |
333 | if (strcmp(udev->owner, "root") == 0) |
334 | uid = 0; | |
335 | else { | |
c19a6b30 | 336 | char *endptr; |
783272f0 | 337 | unsigned long id; |
6d564166 | 338 | |
783272f0 | 339 | id = strtoul(udev->owner, &endptr, 10); |
765cbd97 | 340 | if (endptr[0] == '\0') |
c19a6b30 | 341 | uid = (uid_t) id; |
57e1a277 KS |
342 | else |
343 | uid = lookup_user(udev->owner); | |
c19a6b30 KS |
344 | } |
345 | ||
783272f0 KS |
346 | if (strcmp(udev->group, "root") == 0) |
347 | gid = 0; | |
348 | else { | |
c19a6b30 | 349 | char *endptr; |
783272f0 | 350 | unsigned long id; |
6d564166 | 351 | |
783272f0 | 352 | id = strtoul(udev->group, &endptr, 10); |
765cbd97 | 353 | if (endptr[0] == '\0') |
c19a6b30 | 354 | gid = (gid_t) id; |
57e1a277 | 355 | else |
68c2c0b5 | 356 | gid = lookup_group(udev->group); |
c19a6b30 KS |
357 | } |
358 | ||
e54f0b4a | 359 | info("creating device node '%s', major=%d, minor=%d, mode=%#o, uid=%d, gid=%d", |
ad27f5b3 KS |
360 | filename, major(udev->devt), minor(udev->devt), udev->mode, uid, gid); |
361 | ||
362 | if (!udev->test_run) | |
a4d5ca64 KS |
363 | if (udev_node_mknod(udev, filename, udev->devt, udev->mode, uid, gid) != 0) { |
364 | retval = -1; | |
365 | goto exit; | |
366 | } | |
ad27f5b3 KS |
367 | |
368 | setenv("DEVNAME", filename, 1); | |
50e5de03 | 369 | |
bbbe503e | 370 | /* create all_partitions if requested */ |
fd9efc00 | 371 | if (udev->partitions) { |
fb6e4c28 | 372 | char partitionname[PATH_SIZE]; |
1aa1e248 | 373 | char *attr; |
6d564166 KS |
374 | int range; |
375 | ||
376 | /* take the maximum registered minor range */ | |
1aa1e248 | 377 | attr = sysfs_attr_get_value(udev->dev->devpath, "range"); |
953249a3 | 378 | if (attr != NULL) { |
1aa1e248 | 379 | range = atoi(attr); |
6d564166 KS |
380 | if (range > 1) |
381 | udev->partitions = range-1; | |
382 | } | |
7a947ce5 KS |
383 | info("creating device partition nodes '%s[1-%i]'", filename, udev->partitions); |
384 | if (!udev->test_run) { | |
385 | for (i = 1; i <= udev->partitions; i++) { | |
7e720bd4 KS |
386 | dev_t part_devt; |
387 | ||
63f61c5c KS |
388 | snprintf(partitionname, sizeof(partitionname), "%s%d", filename, i); |
389 | partitionname[sizeof(partitionname)-1] = '\0'; | |
15139b8a | 390 | part_devt = makedev(major(udev->devt), minor(udev->devt) + i); |
a4d5ca64 | 391 | udev_node_mknod(udev, partitionname, part_devt, udev->mode, uid, gid); |
ab2e5bd9 | 392 | } |
50e5de03 | 393 | } |
3d150dfb | 394 | } |
a4d5ca64 | 395 | exit: |
a4d5ca64 | 396 | return retval; |
ea733a2f GKH |
397 | } |
398 | ||
ff9a488d KS |
399 | int udev_node_remove(struct udevice *udev) |
400 | { | |
401 | char filename[PATH_SIZE]; | |
402 | char partitionname[PATH_SIZE]; | |
403 | struct stat stats; | |
eff4a673 | 404 | int retval = 0; |
ff9a488d KS |
405 | int num; |
406 | ||
24f0605c KS |
407 | strlcpy(filename, udev_root, sizeof(filename)); |
408 | strlcat(filename, "/", sizeof(filename)); | |
409 | strlcat(filename, udev->name, sizeof(filename)); | |
a4d5ca64 | 410 | if (stat(filename, &stats) != 0) { |
a0092d28 AA |
411 | info("device node '%s' not found", filename); |
412 | return 0; | |
a4d5ca64 KS |
413 | } |
414 | if (udev->devt && stats.st_rdev != udev->devt) { | |
415 | info("device node '%s' points to a different device, skip removal", filename); | |
416 | return -1; | |
417 | } | |
418 | ||
419 | info("removing device node '%s'", filename); | |
eff4a673 KS |
420 | if (!udev->test_run) |
421 | retval = unlink_secure(filename); | |
a4d5ca64 KS |
422 | if (retval) |
423 | return retval; | |
424 | ||
425 | setenv("DEVNAME", filename, 1); | |
a4d5ca64 KS |
426 | num = udev->partitions; |
427 | if (num > 0) { | |
ff9a488d KS |
428 | int i; |
429 | ||
a4d5ca64 | 430 | info("removing all_partitions '%s[1-%i]'", filename, num); |
d7fdcd61 | 431 | if (num > 255) |
a4d5ca64 | 432 | return -1; |
a4d5ca64 KS |
433 | for (i = 1; i <= num; i++) { |
434 | snprintf(partitionname, sizeof(partitionname), "%s%d", filename, i); | |
435 | partitionname[sizeof(partitionname)-1] = '\0'; | |
eff4a673 KS |
436 | if (!udev->test_run) |
437 | unlink_secure(partitionname); | |
5d24c6ca | 438 | } |
f61d732a | 439 | } |
27d4bf18 | 440 | delete_path(filename); |
ea733a2f GKH |
441 | return retval; |
442 | } |