1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2008 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #pragma ident "%Z%%M% %I% %E% SMI" 27 28 #include <devfsadm.h> 29 #include <stdio.h> 30 #include <strings.h> 31 #include <stdlib.h> 32 #include <limits.h> 33 #include <ctype.h> 34 #include <sys/stat.h> 35 #include <bsm/devalloc.h> 36 37 #define DISK_SUBPATH_MAX 100 38 #define RM_STALE 0x01 39 #define DISK_LINK_RE "^r?dsk/c[0-9]+(t[0-9A-F]+)?d[0-9]+(((s|p))[0-9]+)?$" 40 #define DISK_LINK_TO_UPPER(ch)\ 41 (((ch) >= 'a' && (ch) <= 'z') ? (ch - 'a' + 'A') : ch) 42 43 #define SLICE_SMI "s7" 44 #define SLICE_EFI "" 45 46 #define MN_SMI "h" 47 #define MN_EFI "wd" 48 #define ASCIIWWNSIZE 255 49 50 extern int system_labeled; 51 52 static int disk_callback_chan(di_minor_t minor, di_node_t node); 53 static int disk_callback_nchan(di_minor_t minor, di_node_t node); 54 static int disk_callback_wwn(di_minor_t minor, di_node_t node); 55 static int disk_callback_xvmd(di_minor_t minor, di_node_t node); 56 static int disk_callback_fabric(di_minor_t minor, di_node_t node); 57 static void disk_common(di_minor_t minor, di_node_t node, char *disk, 58 int flags); 59 static char *diskctrl(di_node_t node, di_minor_t minor); 60 static int reserved_links_exist(di_node_t node, di_minor_t minor, int nflags); 61 62 63 static devfsadm_create_t disk_cbt[] = { 64 { "disk", "ddi_block", NULL, 65 TYPE_EXACT, ILEVEL_0, disk_callback_nchan 66 }, 67 { "disk", "ddi_block:channel", NULL, 68 TYPE_EXACT, ILEVEL_0, disk_callback_chan 69 }, 70 { "disk", "ddi_block:fabric", NULL, 71 TYPE_EXACT, ILEVEL_0, disk_callback_fabric 72 }, 73 { "disk", "ddi_block:wwn", NULL, 74 TYPE_EXACT, ILEVEL_0, disk_callback_wwn 75 }, 76 { "disk", "ddi_block:cdrom", NULL, 77 TYPE_EXACT, ILEVEL_0, disk_callback_nchan 78 }, 79 { "disk", "ddi_block:cdrom:channel", NULL, 80 TYPE_EXACT, ILEVEL_0, disk_callback_chan 81 }, 82 { "disk", "ddi_block:xvmd", NULL, 83 TYPE_EXACT, ILEVEL_0, disk_callback_xvmd 84 }, 85 { "disk", "ddi_block:cdrom:xvmd", NULL, 86 TYPE_EXACT, ILEVEL_0, disk_callback_xvmd 87 }, 88 }; 89 90 DEVFSADM_CREATE_INIT_V0(disk_cbt); 91 92 /* 93 * HOT auto cleanup of disks not desired. 94 */ 95 static devfsadm_remove_t disk_remove_cbt[] = { 96 { "disk", DISK_LINK_RE, RM_POST, 97 ILEVEL_0, devfsadm_rm_all 98 } 99 }; 100 101 DEVFSADM_REMOVE_INIT_V0(disk_remove_cbt); 102 103 static devlink_re_t disks_re_array[] = { 104 {"^r?dsk/c([0-9]+)", 1}, 105 {"^cfg/c([0-9]+)$", 1}, 106 {"^scsi/.+/c([0-9]+)", 1}, 107 {NULL} 108 }; 109 110 static char *disk_mid = "disk_mid"; 111 static char *modname = "disk_link"; 112 113 int 114 minor_init() 115 { 116 devfsadm_print(disk_mid, 117 "%s: minor_init(): Creating disks reserved ID cache\n", 118 modname); 119 return (devfsadm_reserve_id_cache(disks_re_array, NULL)); 120 } 121 122 static int 123 disk_callback_chan(di_minor_t minor, di_node_t node) 124 { 125 char *addr; 126 char disk[20]; 127 uint_t targ; 128 uint_t lun; 129 130 addr = di_bus_addr(node); 131 (void) sscanf(addr, "%X,%X", &targ, &lun); 132 (void) sprintf(disk, "t%dd%d", targ, lun); 133 disk_common(minor, node, disk, 0); 134 return (DEVFSADM_CONTINUE); 135 136 } 137 138 static int 139 disk_callback_nchan(di_minor_t minor, di_node_t node) 140 { 141 char *addr; 142 char disk[10]; 143 uint_t lun; 144 145 addr = di_bus_addr(node); 146 (void) sscanf(addr, "%X", &lun); 147 (void) sprintf(disk, "d%d", lun); 148 disk_common(minor, node, disk, 0); 149 return (DEVFSADM_CONTINUE); 150 151 } 152 153 static int 154 disk_callback_wwn(di_minor_t minor, di_node_t node) 155 { 156 char disk[10]; 157 int lun; 158 int targ; 159 int *intp; 160 161 if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "target", &intp) <= 0) { 162 return (DEVFSADM_CONTINUE); 163 } 164 targ = *intp; 165 if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "lun", &intp) <= 0) { 166 lun = 0; 167 } else { 168 lun = *intp; 169 } 170 (void) sprintf(disk, "t%dd%d", targ, lun); 171 172 disk_common(minor, node, disk, RM_STALE); 173 174 return (DEVFSADM_CONTINUE); 175 } 176 177 static int 178 disk_callback_fabric(di_minor_t minor, di_node_t node) 179 { 180 char disk[DISK_SUBPATH_MAX]; 181 int lun; 182 int count; 183 int *intp; 184 uchar_t *str; 185 uchar_t *wwn; 186 uchar_t ascii_wwn[ASCIIWWNSIZE]; 187 188 if (di_prop_lookup_strings(DDI_DEV_T_ANY, node, 189 "client-guid", (char **)&wwn) > 0) { 190 if (strlcpy((char *)ascii_wwn, (char *)wwn, 191 sizeof (ascii_wwn)) >= sizeof (ascii_wwn)) { 192 devfsadm_errprint("SUNW_disk_link: GUID too long:%d", 193 strlen((char *)wwn)); 194 return (DEVFSADM_CONTINUE); 195 } 196 lun = 0; 197 } else if (di_prop_lookup_bytes(DDI_DEV_T_ANY, node, 198 "port-wwn", &wwn) > 0) { 199 if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, 200 "lun", &intp) > 0) { 201 lun = *intp; 202 } else { 203 lun = 0; 204 } 205 206 for (count = 0, str = ascii_wwn; count < 8; count++, str += 2) { 207 (void) sprintf((caddr_t)str, "%02x", wwn[count]); 208 } 209 *str = '\0'; 210 } else { 211 return (DEVFSADM_CONTINUE); 212 } 213 214 for (str = ascii_wwn; *str != '\0'; str++) { 215 *str = DISK_LINK_TO_UPPER(*str); 216 } 217 218 (void) snprintf(disk, DISK_SUBPATH_MAX, "t%sd%d", ascii_wwn, lun); 219 220 disk_common(minor, node, disk, RM_STALE); 221 222 return (DEVFSADM_CONTINUE); 223 } 224 225 /* 226 * xVM virtual block device 227 * 228 * VBDs are enumerated into xenstore by xend and named using 229 * the linux dev_t values for 'hd' and 'xvd' devices. Linux 230 * dev_t's are 16-bit values. The upper 8 bits identify the major # 231 * of the device (hd, xvd) and the lower 8 bits the instance and partition 232 * 233 * For PV guests, VBDs are named by the virt-tools using 234 * the form xvd[a-p][1-15]. The corresponding Solaris /dev/dsk name 235 * created by this generator will be c0t[0-15]d[0-15]sN, 236 * were the target (t) value represents [a-p] and the 237 * disk (d) value is either 0 (e.g. xvda) or contains the partition 238 * information if it has been specified [1-15] (e.g. xvda1) 239 * 240 * For PV guests using the legacy naming (0, 1, 2, ...) 241 * the Solaris disk names created will be c0d[0..767]sN 242 * The Solaris version of virt-install based on virtinst.101 243 * named PV disks as sequential integers. With virtinst.300_1 and 244 * beyond, the virt-* tools will no longer create legacy disk 245 * names. 246 */ 247 static int 248 disk_callback_xvmd(di_minor_t minor, di_node_t node) 249 { 250 #define HD_BASE (3 << 8) 251 #define XVBDMAJ 202 252 253 char *addr; 254 char disk[16]; 255 uint_t targ; 256 uint_t lun = 0; 257 uint_t fmaj; 258 259 addr = di_bus_addr(node); 260 targ = strtol(addr, (char **)NULL, 10); 261 fmaj = targ >> 8; 262 263 /* legacy device address */ 264 if (targ < HD_BASE) 265 (void) snprintf(disk, sizeof (disk), "d%d", targ); 266 /* PV VBD */ 267 else if (fmaj == XVBDMAJ) { 268 lun = targ & 0xf; 269 targ = (targ & 0xff) >> 4; 270 (void) snprintf(disk, sizeof (disk), "t%dd%d", targ, lun); 271 /* HVM device names are generated using the standard generator */ 272 } else { 273 devfsadm_errprint("%s: invalid disk device number (%s)\n", 274 modname, addr); 275 return (DEVFSADM_CONTINUE); 276 } 277 disk_common(minor, node, disk, 0); 278 return (DEVFSADM_CONTINUE); 279 280 } 281 282 /* 283 * This function is called for every disk minor node. 284 * Calls enumerate to assign a logical controller number, and 285 * then devfsadm_mklink to make the link. 286 */ 287 static void 288 disk_common(di_minor_t minor, di_node_t node, char *disk, int flags) 289 { 290 char l_path[PATH_MAX + 1]; 291 char sec_path[PATH_MAX + 1]; 292 char stale_re[DISK_SUBPATH_MAX]; 293 char *dir; 294 char slice[4]; 295 char *mn; 296 char *ctrl; 297 char *nt = NULL; 298 int *int_prop; 299 int nflags = 0; 300 301 if (strstr(mn = di_minor_name(minor), ",raw")) { 302 dir = "rdsk"; 303 } else { 304 dir = "dsk"; 305 } 306 307 if (mn[0] < 113) { 308 (void) sprintf(slice, "s%d", mn[0] - 'a'); 309 } else if (strncmp(mn, MN_EFI, 2) != 0) { 310 (void) sprintf(slice, "p%d", mn[0] - 'q'); 311 } else { 312 /* For EFI label */ 313 (void) sprintf(slice, SLICE_EFI); 314 } 315 316 nflags = 0; 317 if (system_labeled) { 318 nt = di_minor_nodetype(minor); 319 if ((nt != NULL) && 320 ((strcmp(nt, DDI_NT_CD) == 0) || 321 (strcmp(nt, DDI_NT_CD_CHAN) == 0) || 322 (strcmp(nt, DDI_NT_BLOCK_CHAN) == 0))) { 323 nflags = DA_ADD|DA_CD; 324 } 325 } 326 327 if (reserved_links_exist(node, minor, nflags) == DEVFSADM_SUCCESS) { 328 devfsadm_print(disk_mid, "Reserved link exists. Not " 329 "creating links for slice %s\n", slice); 330 return; 331 } 332 333 if (NULL == (ctrl = diskctrl(node, minor))) 334 return; 335 336 (void) strcpy(l_path, dir); 337 (void) strcat(l_path, "/c"); 338 (void) strcat(l_path, ctrl); 339 (void) strcat(l_path, disk); 340 341 /* 342 * If switching between SMI and EFI label or vice versa 343 * cleanup the previous label's devlinks. 344 */ 345 if (*mn == *(MN_SMI) || (strncmp(mn, MN_EFI, 2) == 0)) { 346 char *s, tpath[PATH_MAX + 1]; 347 struct stat sb; 348 349 s = l_path + strlen(l_path); 350 (void) strcat(l_path, (*mn == *(MN_SMI)) 351 ? SLICE_EFI : SLICE_SMI); 352 /* 353 * Attempt the remove only if the stale link exists 354 */ 355 (void) snprintf(tpath, sizeof (tpath), "%s/dev/%s", 356 devfsadm_root_path(), l_path); 357 if (lstat(tpath, &sb) != -1) 358 devfsadm_rm_all(l_path); 359 *s = '\0'; 360 } 361 (void) strcat(l_path, slice); 362 363 (void) devfsadm_mklink(l_path, node, minor, nflags); 364 365 /* secondary links for removable and hotpluggable devices */ 366 if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "removable-media", 367 &int_prop) >= 0) { 368 (void) strcpy(sec_path, "removable-media/"); 369 (void) strcat(sec_path, l_path); 370 (void) devfsadm_secondary_link(sec_path, l_path, 0); 371 } 372 if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "hotpluggable", 373 &int_prop) >= 0) { 374 (void) strcpy(sec_path, "hotpluggable/"); 375 (void) strcat(sec_path, l_path); 376 (void) devfsadm_secondary_link(sec_path, l_path, 0); 377 } 378 379 if ((flags & RM_STALE) == RM_STALE) { 380 (void) strcpy(stale_re, "^"); 381 (void) strcat(stale_re, dir); 382 (void) strcat(stale_re, "/c"); 383 (void) strcat(stale_re, ctrl); 384 (void) strcat(stale_re, "t[0-9A-F]+d[0-9]+(s[0-9]+)?$"); 385 /* 386 * optimizations are made inside of devfsadm_rm_stale_links 387 * instead of before calling the function, as it always 388 * needs to add the valid link to the cache. 389 */ 390 devfsadm_rm_stale_links(stale_re, l_path, node, minor); 391 } 392 393 free(ctrl); 394 } 395 396 397 /* index of enumeration rule applicable to this module */ 398 #define RULE_INDEX 0 399 400 static char * 401 diskctrl(di_node_t node, di_minor_t minor) 402 { 403 char path[PATH_MAX + 1]; 404 char *devfspath; 405 char *buf, *mn; 406 407 devfsadm_enumerate_t rules[3] = { 408 {"^r?dsk$/^c([0-9]+)", 1, MATCH_PARENT}, 409 {"^cfg$/^c([0-9]+)$", 1, MATCH_ADDR}, 410 {"^scsi$/^.+$/^c([0-9]+)", 1, MATCH_PARENT} 411 }; 412 413 mn = di_minor_name(minor); 414 415 if ((devfspath = di_devfs_path(node)) == NULL) { 416 return (NULL); 417 } 418 (void) strcpy(path, devfspath); 419 (void) strcat(path, ":"); 420 (void) strcat(path, mn); 421 di_devfs_path_free(devfspath); 422 423 /* 424 * Use controller component of disk path 425 */ 426 if (disk_enumerate_int(path, RULE_INDEX, &buf, rules, 3) == 427 DEVFSADM_MULTIPLE) { 428 429 /* 430 * We failed because there are multiple logical controller 431 * numbers for a single physical controller. If we use node 432 * name also in the match it should fix this and only find one 433 * logical controller. (See 4045879). 434 * NOTE: Rules for controllers are not changed, as there is 435 * no unique controller number for them in this case. 436 * 437 * MATCH_UNCACHED flag is private to the "disks" and "sgen" 438 * modules. NOT to be used by other modules. 439 */ 440 441 rules[0].flags = MATCH_NODE | MATCH_UNCACHED; /* disks */ 442 rules[2].flags = MATCH_NODE | MATCH_UNCACHED; /* generic scsi */ 443 if (devfsadm_enumerate_int(path, RULE_INDEX, &buf, rules, 3)) { 444 return (NULL); 445 } 446 } 447 448 return (buf); 449 } 450 451 typedef struct dvlist { 452 char *dv_link; 453 struct dvlist *dv_next; 454 } dvlist_t; 455 456 static void 457 free_dvlist(dvlist_t **pp) 458 { 459 dvlist_t *entry; 460 461 while (*pp) { 462 entry = *pp; 463 *pp = entry->dv_next; 464 assert(entry->dv_link); 465 free(entry->dv_link); 466 free(entry); 467 } 468 } 469 static int 470 dvlink_cb(di_devlink_t devlink, void *arg) 471 { 472 char *path; 473 char *can_path; 474 dvlist_t **pp = (dvlist_t **)arg; 475 dvlist_t *entry = NULL; 476 477 entry = calloc(1, sizeof (dvlist_t)); 478 if (entry == NULL) { 479 devfsadm_errprint("%s: calloc failed\n", modname); 480 goto error; 481 } 482 483 path = (char *)di_devlink_path(devlink); 484 assert(path); 485 if (path == NULL) { 486 devfsadm_errprint("%s: di_devlink_path() returned NULL\n", 487 modname); 488 goto error; 489 } 490 491 devfsadm_print(disk_mid, "%s: found link %s in reverse link cache\n", 492 modname, path); 493 494 /* 495 * Return linkname in canonical form i.e. without the 496 * "/dev/" prefix 497 */ 498 can_path = strstr(path, "/dev/"); 499 if (can_path == NULL) { 500 devfsadm_errprint("%s: devlink path %s has no /dev/\n", 501 modname, path); 502 goto error; 503 } 504 505 entry->dv_link = s_strdup(can_path + strlen("/dev/")); 506 entry->dv_next = *pp; 507 *pp = entry; 508 509 return (DI_WALK_CONTINUE); 510 511 error: 512 free(entry); 513 free_dvlist(pp); 514 *pp = NULL; 515 return (DI_WALK_TERMINATE); 516 } 517 518 /* 519 * Returns success only if all goes well. If there is no matching reserved link 520 * or if there is an error, we assume no match. It is better to err on the side 521 * of caution by creating extra links than to miss out creating a required link. 522 */ 523 static int 524 reserved_links_exist(di_node_t node, di_minor_t minor, int nflags) 525 { 526 di_devlink_handle_t dvlink_cache = devfsadm_devlink_cache(); 527 char phys_path[PATH_MAX]; 528 char *minor_path; 529 dvlist_t *head; 530 dvlist_t *entry; 531 char *s; 532 char l[PATH_MAX]; 533 int switch_link = 0; 534 char *mn = di_minor_name(minor); 535 536 if (dvlink_cache == NULL || mn == NULL) { 537 devfsadm_errprint("%s: No minor or devlink cache\n", modname); 538 return (DEVFSADM_FAILURE); 539 } 540 541 if (!devfsadm_have_reserved()) { 542 devfsadm_print(disk_mid, "%s: No reserved links\n", modname); 543 return (DEVFSADM_FAILURE); 544 } 545 546 minor_path = di_devfs_minor_path(minor); 547 if (minor_path == NULL) { 548 devfsadm_errprint("%s: di_devfs_minor_path failed\n", modname); 549 return (DEVFSADM_FAILURE); 550 } 551 552 (void) strlcpy(phys_path, minor_path, sizeof (phys_path)); 553 554 di_devfs_path_free(minor_path); 555 556 head = NULL; 557 (void) di_devlink_cache_walk(dvlink_cache, DISK_LINK_RE, phys_path, 558 DI_PRIMARY_LINK, &head, dvlink_cb); 559 560 /* 561 * We may be switching between EFI label and SMI label in which case 562 * we only have minors of the other type. 563 */ 564 if (head == NULL && (*mn == *(MN_SMI) || 565 (strncmp(mn, MN_EFI, 2) == 0))) { 566 devfsadm_print(disk_mid, "%s: No links for minor %s in /dev. " 567 "Trying another label\n", modname, mn); 568 s = strrchr(phys_path, ':'); 569 if (s == NULL) { 570 devfsadm_errprint("%s: invalid minor path: %s\n", 571 modname, phys_path); 572 return (DEVFSADM_FAILURE); 573 } 574 (void) snprintf(s+1, sizeof (phys_path) - (s + 1 - phys_path), 575 "%s%s", *mn == *(MN_SMI) ? MN_EFI : MN_SMI, 576 strstr(s, ",raw") ? ",raw" : ""); 577 (void) di_devlink_cache_walk(dvlink_cache, DISK_LINK_RE, 578 phys_path, DI_PRIMARY_LINK, &head, dvlink_cb); 579 } 580 581 if (head == NULL) { 582 devfsadm_print(disk_mid, "%s: minor %s has no links in /dev\n", 583 modname, phys_path); 584 /* no links on disk */ 585 return (DEVFSADM_FAILURE); 586 } 587 588 /* 589 * It suffices to use 1 link to this minor, since 590 * we are matching with reserved IDs on the basis of 591 * the controller number which will be the same for 592 * all links to this minor. 593 */ 594 if (!devfsadm_is_reserved(disks_re_array, head->dv_link)) { 595 /* not reserved links */ 596 devfsadm_print(disk_mid, "%s: devlink %s and its minor " 597 "are NOT reserved\n", modname, head->dv_link); 598 free_dvlist(&head); 599 return (DEVFSADM_FAILURE); 600 } 601 602 devfsadm_print(disk_mid, "%s: devlink %s and its minor are on " 603 "reserved list\n", modname, head->dv_link); 604 605 /* 606 * Switch between SMI and EFI labels if required 607 */ 608 switch_link = 0; 609 if (*mn == *(MN_SMI) || (strncmp(mn, MN_EFI, 2) == 0)) { 610 for (entry = head; entry; entry = entry->dv_next) { 611 s = strrchr(entry->dv_link, '/'); 612 assert(s); 613 if (s == NULL) { 614 devfsadm_errprint("%s: disk link %s has no " 615 "directory\n", modname, entry->dv_link); 616 continue; 617 } 618 if (*mn == *(MN_SMI) && strchr(s, 's') == NULL) { 619 (void) snprintf(l, sizeof (l), "%s%s", 620 entry->dv_link, SLICE_SMI); 621 switch_link = 1; 622 devfsadm_print(disk_mid, "%s: switching " 623 "reserved link from EFI to SMI label. " 624 "New link is %s\n", modname, l); 625 } else if (strncmp(mn, MN_EFI, 2) == 0 && 626 (s = strchr(s, 's'))) { 627 *s = '\0'; 628 (void) snprintf(l, sizeof (l), "%s", 629 entry->dv_link); 630 *s = 's'; 631 switch_link = 1; 632 devfsadm_print(disk_mid, "%s: switching " 633 "reserved link from SMI to EFI label. " 634 "New link is %s\n", modname, l); 635 } 636 if (switch_link) { 637 devfsadm_print(disk_mid, "%s: switching " 638 "link: deleting %s and creating %s\n", 639 modname, entry->dv_link, l); 640 devfsadm_rm_link(entry->dv_link); 641 (void) devfsadm_mklink(l, node, minor, nflags); 642 } 643 } 644 } 645 free_dvlist(&head); 646 647 /* 648 * return SUCCESS to indicate that new links to this minor should not 649 * be created so that only compatibility links to this minor remain. 650 */ 651 return (DEVFSADM_SUCCESS); 652 } 653