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 2006 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 <sys/stat.h> 29 #include <sys/types.h> 30 31 /* 32 * Dependent on types.h, but not including it... 33 */ 34 #include <stdio.h> 35 #include <sys/types.h> 36 #include <sys/dkio.h> 37 #include <sys/dktp/fdisk.h> 38 #include <sys/mnttab.h> 39 #include <sys/mntent.h> 40 #include <sys/sysmacros.h> 41 #include <sys/mkdev.h> 42 #include <sys/vfs.h> 43 #include <nfs/nfs.h> 44 #include <nfs/nfs_clnt.h> 45 #include <kstat.h> 46 #include <ctype.h> 47 #include <dirent.h> 48 #include <libdevinfo.h> 49 #include <limits.h> 50 #include <stdlib.h> 51 #include <string.h> 52 #include <unistd.h> 53 #include <errno.h> 54 #include <devid.h> 55 56 #include "dsr.h" 57 #include "statcommon.h" 58 59 /* disk/tape info */ 60 static di_node_t di_root; /* for devid */ 61 static di_dim_t di_dim; /* for /dev names */ 62 typedef struct { 63 char *minor_name; 64 int minor_isdisk; 65 } minor_match_t; 66 static minor_match_t mm_disk = {"a", 1}; 67 static minor_match_t mm_tape = {"", 0}; 68 static char md_minor_name[MAXPATHLEN]; 69 static minor_match_t mm_md = {md_minor_name, 0}; 70 static minor_match_t *mma_disk_tape[] = {&mm_disk, &mm_tape, NULL}; 71 static minor_match_t *mma_md[] = {&mm_md, NULL}; 72 static char *mdsetno2name(int setno); 73 #define DISKLIST_MOD 256 /* ^2 instunit mod hash */ 74 static disk_list_t *disklist[DISKLIST_MOD]; 75 76 /* nfs info */ 77 extern kstat_ctl_t *kc; 78 extern mnt_t *nfs; 79 static int nfs_tried; 80 static char *get_nfs_by_minor(uint_t); 81 static char *cur_hostname(uint_t, kstat_ctl_t *); 82 static char *cur_special(char *, char *); 83 84 /* 85 * Clear the snapshot so a cache miss in lookup_ks_name() will cause a fresh 86 * snapshot in drvinstunit2dev(). 87 */ 88 void 89 cleanup_iodevs_snapshot() 90 { 91 if (di_dim) { 92 di_dim_fini(di_dim); 93 di_dim = NULL; 94 } 95 96 if (di_root) { 97 di_fini(di_root); 98 di_root = DI_NODE_NIL; 99 } 100 101 nfs_tried = 0; 102 } 103 104 /* 105 * Find information for (driver, instunit) device: return zero on failure. 106 * 107 * NOTE: Failure of drvinstunit2dev works out OK for the caller if the kstat 108 * name is the same as public name: the caller will just use kstat name. 109 */ 110 static int 111 drvinstunit2dev(char *driver, int instunit, 112 char **devpathp, char **adevpathp, char **devidp, int *isdiskp) 113 { 114 int instance; 115 minor_match_t **mma; 116 minor_match_t *mm; 117 char *devpath; 118 char *devid; 119 char *a, *s; 120 int mdsetno; 121 char *mdsetname = NULL; 122 char amdsetname[MAXPATHLEN]; 123 char *devicespath; 124 di_node_t node; 125 126 127 /* setup "no result" return values */ 128 if (devpathp) 129 *devpathp = NULL; 130 if (adevpathp) 131 *adevpathp = NULL; 132 if (devidp) 133 *devidp = NULL; 134 if (isdiskp) 135 *isdiskp = 0; 136 137 /* take <driver><instance><minor_name> snapshot if not established */ 138 if (di_dim == NULL) { 139 di_dim = di_dim_init(); 140 if (di_dim == NULL) 141 return (0); 142 } 143 144 /* 145 * Determine if 'instunit' is an 'instance' or 'unit' based on the 146 * 'driver'. The current code only detects 'md' metadevice 'units', 147 * and defaults to 'instance' for everything else. 148 * 149 * For a metadevice, 'driver' is either "md" or "<setno>/md". 150 */ 151 s = strstr(driver, "/md"); 152 if ((strcmp(driver, "md") == 0) || 153 (s && isdigit(*driver) && (strcmp(s, "/md") == 0))) { 154 /* 155 * "md" unit: Special case translation of "md" kstat names. 156 * For the local set the kstat name is "md<unit>", and for 157 * a shared set the kstat name is "<setno>/md<unit>": we map 158 * these to the minor paths "/pseudo/md@0:<unit>,blk" and 159 * "/pseudo/md@0:<set>,<unit>,blk" respectively. 160 */ 161 if (isdigit(*driver)) { 162 mdsetno = atoi(driver); 163 164 /* convert setno to setname */ 165 mdsetname = mdsetno2name(mdsetno); 166 } else 167 mdsetno = 0; 168 169 driver = "md"; 170 instance = 0; 171 mma = mma_md; /* metadevice dynamic minor */ 172 (void) snprintf(md_minor_name, sizeof (md_minor_name), 173 "%d,%d,blk", mdsetno, instunit); 174 } else { 175 instance = instunit; 176 mma = mma_disk_tape; /* disk/tape minors */ 177 } 178 179 /* Try to find a minor_match that works */ 180 for (mm = *mma++; mm; mm = *mma++) { 181 if ((devpath = di_dim_path_dev(di_dim, 182 driver, instance, mm->minor_name)) != NULL) 183 break; 184 } 185 if (devpath == NULL) 186 return (0); 187 188 /* 189 * At this point we have a devpath result. Return the information about 190 * the result that the caller is asking for. 191 */ 192 if (devpathp) /* devpath */ 193 *devpathp = safe_strdup(devpath); 194 195 if (adevpathp) { /* abbreviated devpath */ 196 if (mm->minor_isdisk) { 197 /* 198 * For disks we return the last component (with 199 * trailing "s#" or "p#" stripped off for disks). 200 * For example for devpath of "/dev/dsk/c0t0d0s0" the 201 * abbreviated devpath would be "c0t0d0". 202 */ 203 a = strrchr(devpath, '/'); 204 if (a == NULL) { 205 free(devpath); 206 return (0); 207 } 208 a++; 209 s = strrchr(a, 's'); 210 if (s == NULL) { 211 s = strrchr(a, 'p'); 212 if (s == NULL) { 213 free(devpath); 214 return (0); 215 } 216 } 217 /* don't include slice information in devpath */ 218 *s = '\0'; 219 } else { 220 /* 221 * remove "/dev/", and "/dsk/", from 'devpath' (like 222 * "/dev/md/dsk/d0") to form the abbreviated devpath 223 * (like "md/d0"). 224 */ 225 if ((s = strstr(devpath, "/dev/")) != NULL) 226 (void) strcpy(s + 1, s + 5); 227 if ((s = strstr(devpath, "/dsk/")) != NULL) 228 (void) strcpy(s + 1, s + 5); 229 230 /* 231 * If we have an mdsetname, convert abbreviated setno 232 * notation (like "md/shared/1/d0" to abbreviated 233 * setname notation (like "md/red/d0"). 234 */ 235 if (mdsetname) { 236 a = strrchr(devpath, '/'); 237 (void) snprintf(amdsetname, sizeof (amdsetname), 238 "md/%s%s", mdsetname, a); 239 free(mdsetname); 240 a = amdsetname; 241 } else { 242 if (*devpath == '/') 243 a = devpath + 1; 244 else 245 a = devpath; 246 } 247 } 248 *adevpathp = safe_strdup(a); 249 } 250 251 if (devidp) { /* lookup the devid */ 252 /* take snapshots if not established */ 253 if (di_root == DI_NODE_NIL) { 254 di_root = di_init("/", DINFOCACHE); 255 } 256 if (di_root) { 257 /* get path to /devices devinfo node */ 258 devicespath = di_dim_path_devices(di_dim, 259 driver, instance, NULL); 260 if (devicespath) { 261 /* find the node in the snapshot */ 262 node = di_lookup_node(di_root, devicespath); 263 free(devicespath); 264 265 /* and lookup devid property on the node */ 266 if (di_prop_lookup_strings(DDI_DEV_T_ANY, node, 267 DEVID_PROP_NAME, &devid) != -1) 268 *devidp = devid; 269 } 270 } 271 } 272 273 if (isdiskp) 274 *isdiskp = mm->minor_isdisk; 275 276 free(devpath); 277 return (1); /* success */ 278 } 279 280 /* 281 * Find/create a disk_list entry for "<driver><instunit>" given a kstat name. 282 * The basic format of a kstat name is "<driver><instunit>,<partition>". The 283 * <instunit> is a base10 number, and the ",<partition>" part is optional. 284 * 285 * NOTE: In the case of non-local metadevices, the format of "<driver>" in 286 * a kstat name is acutally "<setno>/md". 287 */ 288 disk_list_t * 289 lookup_ks_name(char *ks_name, int want_devid) 290 { 291 char *p; 292 int len; 293 char driver[MAXNAMELEN]; 294 int instunit; 295 disk_list_t **dlhp; /* disklist head */ 296 disk_list_t *entry; 297 char *devpath; 298 char *adevpath = NULL; 299 char *devid = NULL; 300 int isdisk; 301 302 /* 303 * Extract <driver> and <instunit> from kstat name. 304 * Filter out illegal forms (like all digits). 305 */ 306 if ((ks_name == NULL) || (*ks_name == 0) || 307 (strspn(ks_name, "0123456789") == strlen(ks_name))) 308 return (NULL); 309 p = strrchr(ks_name, ','); /* start of ",partition" */ 310 if (p == NULL) 311 p = &ks_name[strlen(ks_name) - 1]; /* last char */ 312 else 313 p--; /* before ",partition" */ 314 315 while ((p >= ks_name) && isdigit(*p)) 316 p--; /* backwards over digits */ 317 p++; /* start of instunit */ 318 if ((*p == '\0') || (*p == ',')) 319 return (NULL); /* no <instunit> */ 320 len = p - ks_name; 321 (void) strncpy(driver, ks_name, len); 322 driver[len] = '\0'; 323 instunit = atoi(p); 324 325 /* hash and search for existing disklist entry */ 326 dlhp = &disklist[instunit & (DISKLIST_MOD - 1)]; 327 for (entry = *dlhp; entry; entry = entry->next) { 328 if ((strcmp(entry->dtype, driver) == 0) && 329 (entry->dnum == instunit)) { 330 return (entry); 331 } 332 } 333 334 /* not found, try to get dev information */ 335 if (drvinstunit2dev(driver, instunit, &devpath, &adevpath, 336 want_devid ? &devid : NULL, &isdisk) == 0) { 337 return (NULL); 338 } 339 340 /* and make a new disklist entry ... */ 341 entry = safe_alloc(sizeof (disk_list_t)); 342 entry->dtype = safe_strdup(driver); 343 entry->dnum = instunit; 344 entry->dname = devpath; 345 entry->dsk = adevpath; 346 entry->devidstr = devid; 347 entry->flags = 0; 348 if (isdisk) { 349 entry->flags |= SLICES_OK; 350 #if defined(__i386) 351 entry->flags |= PARTITIONS_OK; 352 #endif 353 } 354 entry->seen = 0; 355 356 /* add new entry to head of instunit hashed list */ 357 entry->next = *dlhp; 358 *dlhp = entry; 359 return (entry); 360 } 361 362 /* 363 * Convert metadevice setno to setname by looking in /dev/md for symlinks 364 * that point to "shared/setno" - the name of such a symlink is the setname. 365 * The caller is responsible for freeing the returned string. 366 */ 367 static char * 368 mdsetno2name(int setno) 369 { 370 char setlink[MAXPATHLEN + 1]; 371 char link[MAXPATHLEN + 1]; 372 char path[MAXPATHLEN + 1]; 373 char *p; 374 DIR *dirp; 375 struct dirent *dp; 376 size_t len; 377 char *mdsetname = NULL; 378 379 /* we are looking for a link to setlink */ 380 (void) snprintf(setlink, MAXPATHLEN, "shared/%d", setno); 381 382 /* in the directory /dev/md */ 383 (void) strcpy(path, "/dev/md/"); 384 p = path + strlen(path); 385 dirp = opendir(path); 386 if (dirp == NULL) 387 return (NULL); 388 389 /* loop through /dev/md directory entries */ 390 while ((dp = readdir(dirp)) != NULL) { 391 392 /* doing a readlink of entry (fails for non-symlinks) */ 393 *p = '\0'; 394 (void) strcpy(p, dp->d_name); 395 if ((len = readlink(path, link, MAXPATHLEN)) == (size_t)-1) 396 continue; 397 398 /* and looking for a link to setlink */ 399 link[len] = '\0'; 400 if (strcmp(setlink, link)) 401 continue; 402 403 /* found- name of link is the setname */ 404 mdsetname = safe_strdup(dp->d_name); 405 break; 406 } 407 408 (void) closedir(dirp); 409 return (mdsetname); 410 } 411 412 char * 413 lookup_nfs_name(char *ks, kstat_ctl_t *kc) 414 { 415 uint_t minor; 416 char *host, *path; 417 char *cp; 418 char *rstr = 0; 419 size_t len; 420 421 if (sscanf(ks, "nfs%u", &minor) == 1) { 422 retry: 423 cp = get_nfs_by_minor(minor); 424 if (cp) { 425 if (strchr(cp, ',') == NULL) { 426 rstr = safe_strdup(cp); 427 return (rstr); 428 } 429 host = cur_hostname(minor, kc); 430 if (host) { 431 if (*host) { 432 path = cur_special(host, cp); 433 if (path) { 434 len = strlen(host); 435 len += strlen(path); 436 len += 2; 437 rstr = safe_alloc(len); 438 (void) snprintf(rstr, len, 439 "%s:%s", host, path); 440 } else { 441 rstr = safe_strdup(cp); 442 } 443 } else { 444 rstr = safe_strdup(ks); 445 } 446 free(host); 447 } else { 448 rstr = safe_strdup(cp); 449 } 450 } else if (nfs_tried == 0) { 451 nfs_tried = 1; 452 do_mnttab(); 453 goto retry; 454 } 455 } 456 return (rstr); 457 } 458 459 static char * 460 get_nfs_by_minor(uint_t minor) 461 { 462 mnt_t *localnfs; 463 464 localnfs = nfs; 465 while (localnfs) { 466 if (localnfs->minor == minor) { 467 return (localnfs->device_name); 468 } 469 localnfs = localnfs->next; 470 } 471 return (0); 472 } 473 474 /* 475 * Read the cur_hostname from the mntinfo kstat 476 */ 477 static char * 478 cur_hostname(uint_t minor, kstat_ctl_t *kc) 479 { 480 kstat_t *ksp; 481 static struct mntinfo_kstat mik; 482 char *rstr; 483 484 for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) { 485 if (ksp->ks_type != KSTAT_TYPE_RAW) 486 continue; 487 if (ksp->ks_instance != minor) 488 continue; 489 if (strcmp(ksp->ks_module, "nfs")) 490 continue; 491 if (strcmp(ksp->ks_name, "mntinfo")) 492 continue; 493 if (ksp->ks_flags & KSTAT_FLAG_INVALID) 494 return (NULL); 495 if (kstat_read(kc, ksp, &mik) == -1) 496 return (NULL); 497 rstr = safe_strdup(mik.mik_curserver); 498 return (rstr); 499 } 500 return (NULL); 501 } 502 503 /* 504 * Given the hostname of the mounted server, extract the server 505 * mount point from the mnttab string. 506 * 507 * Common forms: 508 * server1,server2,server3:/path 509 * server1:/path,server2:/path 510 * or a hybrid of the two 511 */ 512 static char * 513 cur_special(char *hostname, char *special) 514 { 515 char *cp; 516 char *path; 517 size_t hlen = strlen(hostname); 518 519 /* 520 * find hostname in string 521 */ 522 again: 523 if ((cp = strstr(special, hostname)) == NULL) 524 return (NULL); 525 526 /* 527 * hostname must be followed by ',' or ':' 528 */ 529 if (cp[hlen] != ',' && cp[hlen] != ':') { 530 special = &cp[hlen]; 531 goto again; 532 } 533 534 /* 535 * If hostname is followed by a ',' eat all characters until a ':' 536 */ 537 cp = &cp[hlen]; 538 if (*cp == ',') { 539 cp++; 540 while (*cp != ':') { 541 if (*cp == NULL) 542 return (NULL); 543 cp++; 544 } 545 } 546 path = ++cp; /* skip ':' */ 547 548 /* 549 * path is terminated by either 0, or space or ',' 550 */ 551 while (*cp) { 552 if (isspace(*cp) || *cp == ',') { 553 *cp = NULL; 554 return (path); 555 } 556 cp++; 557 } 558 return (path); 559 } 560