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