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 <strings.h>
53 #include <unistd.h>
54 #include <errno.h>
55 #include <devid.h>
56 #include <sys/scsi/adapters/scsi_vhci.h>
57
58 #include "dsr.h"
59 #include "statcommon.h"
60
61 /* where we get kstat name translation information from */
62 static di_node_t di_root; /* from di_init: for devid */
63 static di_dim_t di_dim; /* from di_dim_init: for /dev names */
64 static int scsi_vhci_fd = -1; /* from scsi_vhci: for mpxio path */
65
66 /* disk/tape/misc info */
67 typedef struct {
68 char *minor_name;
69 int minor_isdisk;
70 } minor_match_t;
71 static minor_match_t mm_disk = {"a", 1};
72 static minor_match_t mm_tape = {"", 0};
73 static minor_match_t mm_misc = {"0", 0};
74 static char md_minor_name[MAXPATHLEN];
75 static minor_match_t mm_md = {md_minor_name, 0};
76 static minor_match_t *mma_disk_tape_misc[] =
77 {&mm_disk, &mm_tape, &mm_misc, NULL};
78 static minor_match_t *mma_md[] = {&mm_md, NULL};
79 static char *mdsetno2name(int setno);
80 #define DISKLIST_MOD 256 /* ^2 instunit mod hash */
81 static disk_list_t *disklist[DISKLIST_MOD];
82
83
84 /* nfs info */
85 extern kstat_ctl_t *kc;
86 extern mnt_t *nfs;
87 static int nfs_tried;
88 static char *get_nfs_by_minor(uint_t);
89 static char *cur_hostname(uint_t, kstat_ctl_t *);
90 static char *cur_special(char *, char *);
91
92 /*
93 * Clear the snapshot so a cache miss in lookup_ks_name() will cause a fresh
94 * snapshot in drvinstunit2dev().
95 */
96 void
cleanup_iodevs_snapshot()97 cleanup_iodevs_snapshot()
98 {
99 if (di_dim) {
100 di_dim_fini(di_dim);
101 di_dim = NULL;
102 }
103
104 if (di_root) {
105 di_fini(di_root);
106 di_root = DI_NODE_NIL;
107 }
108
109 nfs_tried = 0;
110 }
111
112 /*
113 * Find information for (driver, instunit) device: return zero on failure.
114 *
115 * NOTE: Failure of drvinstunit2dev works out OK for the caller if the kstat
116 * name is the same as public name: the caller will just use kstat name.
117 */
118 static int
drvinstunitpart2dev(char * driver,int instunit,char * part,char ** devpathp,char ** adevpathp,char ** devidp)119 drvinstunitpart2dev(char *driver, int instunit, char *part,
120 char **devpathp, char **adevpathp, char **devidp)
121 {
122 int instance;
123 minor_match_t **mma;
124 minor_match_t *mm;
125 char *devpath;
126 char *devid;
127 char *a, *s;
128 int mdsetno;
129 char *mdsetname = NULL;
130 char amdsetname[MAXPATHLEN];
131 char *devicespath;
132 di_node_t node;
133
134 /* setup "no result" return values */
135 if (devpathp)
136 *devpathp = NULL;
137 if (adevpathp)
138 *adevpathp = NULL;
139 if (devidp)
140 *devidp = NULL;
141
142 /* take <driver><instance><minor_name> snapshot if not established */
143 if (di_dim == NULL) {
144 di_dim = di_dim_init();
145 if (di_dim == NULL)
146 return (0);
147 }
148
149 /*
150 * Determine if 'instunit' is an 'instance' or 'unit' based on the
151 * 'driver'. The current code only detects 'md' metadevice 'units',
152 * and defaults to 'instance' for everything else.
153 *
154 * For a metadevice, 'driver' is either "md" or "<setno>/md".
155 */
156 s = strstr(driver, "/md");
157 if ((strcmp(driver, "md") == 0) ||
158 (s && isdigit(*driver) && (strcmp(s, "/md") == 0))) {
159 /*
160 * "md" unit: Special case translation of "md" kstat names.
161 * For the local set the kstat name is "md<unit>", and for
162 * a shared set the kstat name is "<setno>/md<unit>": we map
163 * these to the minor paths "/pseudo/md@0:<unit>,blk" and
164 * "/pseudo/md@0:<set>,<unit>,blk" respectively.
165 */
166 if (isdigit(*driver)) {
167 mdsetno = atoi(driver);
168
169 /* convert setno to setname */
170 mdsetname = mdsetno2name(mdsetno);
171 } else
172 mdsetno = 0;
173
174 driver = "md";
175 instance = 0;
176 mma = mma_md; /* metadevice dynamic minor */
177 (void) snprintf(md_minor_name, sizeof (md_minor_name),
178 "%d,%d,blk", mdsetno, instunit);
179 } else {
180 instance = instunit;
181 mma = mma_disk_tape_misc; /* disk/tape/misc minors */
182 }
183
184 if (part) {
185 devpath = di_dim_path_dev(di_dim, driver, instance, part);
186 } else {
187 /* Try to find a minor_match that works */
188 for (mm = *mma++; mm; mm = *mma++) {
189 if ((devpath = di_dim_path_dev(di_dim,
190 driver, instance, mm->minor_name)) != NULL)
191 break;
192 }
193 }
194 if (devpath == NULL)
195 return (0);
196
197 /*
198 * At this point we have a devpath result. Return the information about
199 * the result that the caller is asking for.
200 */
201 if (devpathp) /* devpath */
202 *devpathp = safe_strdup(devpath);
203
204 if (adevpathp) { /* abbreviated devpath */
205 if ((part == NULL) && mm->minor_isdisk) {
206 /*
207 * For disk kstats without a partition we return the
208 * last component with trailing "s#" or "p#" stripped
209 * off (i.e. partition/slice information is removed).
210 * For example for devpath of "/dev/dsk/c0t0d0s0" the
211 * abbreviated devpath would be "c0t0d0".
212 */
213 a = strrchr(devpath, '/');
214 if (a == NULL) {
215 free(devpath);
216 return (0);
217 }
218 a++;
219 s = strrchr(a, 's');
220 if (s == NULL) {
221 s = strrchr(a, 'p');
222 if (s == NULL) {
223 free(devpath);
224 return (0);
225 }
226 }
227 /* don't include slice information in devpath */
228 *s = '\0';
229 } else {
230 /*
231 * remove "/dev/", and "/dsk/", from 'devpath' (like
232 * "/dev/md/dsk/d0") to form the abbreviated devpath
233 * (like "md/d0").
234 */
235 if ((s = strstr(devpath, "/dev/")) != NULL)
236 (void) strcpy(s + 1, s + 5);
237 if ((s = strstr(devpath, "/dsk/")) != NULL)
238 (void) strcpy(s + 1, s + 5);
239
240 /*
241 * If we have an mdsetname, convert abbreviated setno
242 * notation (like "md/shared/1/d0" to abbreviated
243 * setname notation (like "md/red/d0").
244 */
245 if (mdsetname) {
246 a = strrchr(devpath, '/');
247 (void) snprintf(amdsetname, sizeof (amdsetname),
248 "md/%s%s", mdsetname, a);
249 free(mdsetname);
250 a = amdsetname;
251 } else {
252 if (*devpath == '/')
253 a = devpath + 1;
254 else
255 a = devpath;
256 }
257 }
258 *adevpathp = safe_strdup(a);
259 }
260
261 if (devidp) { /* lookup the devid */
262 /* take snapshot if not established */
263 if (di_root == DI_NODE_NIL) {
264 di_root = di_init("/", DINFOCACHE);
265 }
266 if (di_root) {
267 /* get path to /devices devinfo node */
268 devicespath = di_dim_path_devices(di_dim,
269 driver, instance, NULL);
270 if (devicespath) {
271 /* find the node in the snapshot */
272 node = di_lookup_node(di_root, devicespath);
273 free(devicespath);
274
275 /* and lookup devid property on the node */
276 if (di_prop_lookup_strings(DDI_DEV_T_ANY, node,
277 DEVID_PROP_NAME, &devid) != -1)
278 *devidp = devid;
279 }
280 }
281 }
282
283 free(devpath);
284 return (1); /* success */
285 }
286
287 /*
288 * Do <pid> to 'target-port' translation
289 */
290 static int
drvpid2port(uint_t pid,char ** target_portp)291 drvpid2port(uint_t pid, char **target_portp)
292 {
293 sv_iocdata_t ioc;
294 char target_port[MAXNAMELEN];
295
296 /* setup "no result" return values */
297 *target_portp = NULL;
298
299 /* open scsi_vhci if not already done */
300 if (scsi_vhci_fd == -1) {
301 scsi_vhci_fd = open("/devices/scsi_vhci:devctl", O_RDONLY);
302 if (scsi_vhci_fd == -1)
303 return (0); /* failure */
304 }
305
306 /*
307 * Perform ioctl for <pid> -> 'target-port' translation.
308 *
309 * NOTE: it is legimite for this ioctl to fail for transports
310 * that use mpxio, but don't set a 'target-port' pathinfo property.
311 * On failure we return the the "<pid>" as the target port string.
312 */
313 bzero(&ioc, sizeof (sv_iocdata_t));
314 ioc.buf_elem = pid;
315 ioc.addr = target_port;
316 if (ioctl(scsi_vhci_fd, SCSI_VHCI_GET_TARGET_LONGNAME, &ioc) < 0) {
317 (void) snprintf(target_port, sizeof (target_port), "%d", pid);
318 }
319
320 *target_portp = safe_strdup(target_port);
321 return (1); /* success */
322 }
323
324 /*
325 * Find/create a disk_list entry for given a kstat name.
326 * The basic format of a kstat name is
327 *
328 * "<driver><instunit>.<pid>.<phci-driver><instance>,<partition>".
329 *
330 * The <instunit> is a decimal number. The ".<pid>.<phci-driver><instance>",
331 * which describes mpxio path stat information, and ",<partition>" parts are
332 * optional. The <pid> consists of the letter 't' followed by a decimal number.
333 * When available, we use the <pid> to find the 'target-port' via ioctls to
334 * the scsi_vhci driver.
335 *
336 * NOTE: In the case of non-local metadevices, the format of "<driver>" in
337 * a kstat name is acutally "<setno>/md".
338 */
339 disk_list_t *
lookup_ks_name(char * ks_name,int want_devid)340 lookup_ks_name(char *ks_name, int want_devid)
341 {
342 char *pidp; /* ".<pid>... */
343 char *part; /* ",partition... */
344 char *initiator; /* ".<phci-driver>... */
345 char *p;
346 int len;
347 char driver[KSTAT_STRLEN];
348 int instunit;
349 disk_list_t **dlhp; /* disklist head */
350 disk_list_t *entry;
351 char *devpath = NULL;
352 char *adevpath = NULL;
353 char *devid = NULL;
354 int pid;
355 char *target_port = NULL;
356 char portform[MAXPATHLEN];
357
358 /* Filter out illegal forms (like all digits). */
359 if ((ks_name == NULL) || (*ks_name == 0) ||
360 (strspn(ks_name, "0123456789") == strlen(ks_name)))
361 goto fail;
362
363 /* parse ks_name to create new entry */
364 pidp = strchr(ks_name, '.'); /* start of ".<pid>" */
365 initiator = strrchr(ks_name, '.'); /* start of ".<pHCI-driver>" */
366 if (pidp && (pidp == initiator)) /* can't have same start */
367 goto fail;
368
369 part = strchr(ks_name, ','); /* start of ",<partition>" */
370 p = strchr(ks_name, ':'); /* start of ":<partition>" */
371 if (part && p)
372 goto fail; /* can't have both */
373 if (p)
374 part = p;
375 if (part && pidp)
376 goto fail; /* <pid> and partition: bad */
377
378 p = part ? part : pidp;
379 if (p == NULL)
380 p = &ks_name[strlen(ks_name) - 1]; /* last char */
381 else
382 p--; /* before ',' or '.' */
383
384 while ((p >= ks_name) && isdigit(*p))
385 p--; /* backwards over digits */
386 p++; /* start of instunit */
387 if ((*p == '\0') || (*p == ',') || (*p == '.') || (*p == ':'))
388 goto fail; /* no <instunit> */
389 len = p - ks_name;
390 (void) strncpy(driver, ks_name, len);
391 driver[len] = '\0';
392 instunit = atoi(p);
393 if (part)
394 part++; /* skip ',' */
395
396 /* hash by instunit and search for existing entry */
397 dlhp = &disklist[instunit & (DISKLIST_MOD - 1)];
398 for (entry = *dlhp; entry; entry = entry->next) {
399 if (strcmp(entry->ks_name, ks_name) == 0) {
400 return (entry);
401 }
402 }
403
404 /* not found, translate kstat_name components and create new entry */
405
406 /* translate kstat_name dev information */
407 if (drvinstunitpart2dev(driver, instunit, part,
408 &devpath, &adevpath, want_devid ? &devid : NULL) == 0) {
409 goto fail;
410 }
411
412 /* parse and translate path information */
413 if (pidp) {
414 /* parse path information: ".t#.<phci-driver><instance>" */
415 pidp++; /* skip '.' */
416 initiator++; /* skip '.' */
417 if ((*pidp != 't') || !isdigit(pidp[1]))
418 goto fail; /* not ".t#" */
419 pid = atoi(&pidp[1]);
420
421 /* translate <pid> to 'target-port' */
422 if (drvpid2port(pid, &target_port) == 0)
423 goto fail;
424
425 /* Establish 'target-port' form. */
426 (void) snprintf(portform, sizeof (portform),
427 "%s.t%s.%s", adevpath, target_port, initiator);
428 free(target_port);
429 free(adevpath);
430 adevpath = strdup(portform);
431 }
432
433 /* make a new entry ... */
434 entry = safe_alloc(sizeof (disk_list_t));
435 entry->ks_name = safe_strdup(ks_name);
436 entry->dname = devpath;
437 entry->dsk = adevpath;
438 entry->devidstr = devid;
439
440 #ifdef DEBUG
441 (void) printf("lookup_ks_name: new: %s %s\n",
442 ks_name, entry->dsk ? entry->dsk : "NULL");
443 #endif /* DEBUG */
444
445 /* add new entry to head of hashed list */
446 entry->next = *dlhp;
447 *dlhp = entry;
448 return (entry);
449
450 fail:
451 free(devpath);
452 free(adevpath);
453 free(devid);
454 #ifdef DEBUG
455 (void) printf("lookup_ks_name: failed: %s\n", ks_name);
456 #endif /* DEBUG */
457 return (NULL);
458 }
459
460 /*
461 * Convert metadevice setno to setname by looking in /dev/md for symlinks
462 * that point to "shared/setno" - the name of such a symlink is the setname.
463 * The caller is responsible for freeing the returned string.
464 */
465 static char *
mdsetno2name(int setno)466 mdsetno2name(int setno)
467 {
468 char setlink[MAXPATHLEN + 1];
469 char link[MAXPATHLEN + 1];
470 char path[MAXPATHLEN + 1];
471 char *p;
472 DIR *dirp;
473 struct dirent *dp;
474 size_t len;
475 char *mdsetname = NULL;
476
477 /* we are looking for a link to setlink */
478 (void) snprintf(setlink, MAXPATHLEN, "shared/%d", setno);
479
480 /* in the directory /dev/md */
481 (void) strcpy(path, "/dev/md/");
482 p = path + strlen(path);
483 dirp = opendir(path);
484 if (dirp == NULL)
485 return (NULL);
486
487 /* loop through /dev/md directory entries */
488 while ((dp = readdir(dirp)) != NULL) {
489
490 /* doing a readlink of entry (fails for non-symlinks) */
491 *p = '\0';
492 (void) strcpy(p, dp->d_name);
493 if ((len = readlink(path, link, MAXPATHLEN)) == (size_t)-1)
494 continue;
495
496 /* and looking for a link to setlink */
497 link[len] = '\0';
498 if (strcmp(setlink, link))
499 continue;
500
501 /* found- name of link is the setname */
502 mdsetname = safe_strdup(dp->d_name);
503 break;
504 }
505
506 (void) closedir(dirp);
507 return (mdsetname);
508 }
509
510 char *
lookup_nfs_name(char * ks,kstat_ctl_t * kc)511 lookup_nfs_name(char *ks, kstat_ctl_t *kc)
512 {
513 uint_t minor;
514 char *host, *path;
515 char *cp;
516 char *rstr = 0;
517 size_t len;
518
519 if (sscanf(ks, "nfs%u", &minor) == 1) {
520 retry:
521 cp = get_nfs_by_minor(minor);
522 if (cp) {
523 if (strchr(cp, ',') == NULL) {
524 rstr = safe_strdup(cp);
525 return (rstr);
526 }
527 host = cur_hostname(minor, kc);
528 if (host) {
529 if (*host) {
530 path = cur_special(host, cp);
531 if (path) {
532 len = strlen(host);
533 len += strlen(path);
534 len += 2;
535 rstr = safe_alloc(len);
536 (void) snprintf(rstr, len,
537 "%s:%s", host, path);
538 } else {
539 rstr = safe_strdup(cp);
540 }
541 } else {
542 rstr = safe_strdup(ks);
543 }
544 free(host);
545 } else {
546 rstr = safe_strdup(cp);
547 }
548 } else if (nfs_tried == 0) {
549 nfs_tried = 1;
550 do_mnttab();
551 goto retry;
552 }
553 }
554 return (rstr);
555 }
556
557 static char *
get_nfs_by_minor(uint_t minor)558 get_nfs_by_minor(uint_t minor)
559 {
560 mnt_t *localnfs;
561
562 localnfs = nfs;
563 while (localnfs) {
564 if (localnfs->minor == minor) {
565 return (localnfs->device_name);
566 }
567 localnfs = localnfs->next;
568 }
569 return (0);
570 }
571
572 /*
573 * Read the cur_hostname from the mntinfo kstat
574 */
575 static char *
cur_hostname(uint_t minor,kstat_ctl_t * kc)576 cur_hostname(uint_t minor, kstat_ctl_t *kc)
577 {
578 kstat_t *ksp;
579 static struct mntinfo_kstat mik;
580 char *rstr;
581
582 for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) {
583 if (ksp->ks_type != KSTAT_TYPE_RAW)
584 continue;
585 if (ksp->ks_instance != minor)
586 continue;
587 if (strcmp(ksp->ks_module, "nfs"))
588 continue;
589 if (strcmp(ksp->ks_name, "mntinfo"))
590 continue;
591 if (ksp->ks_flags & KSTAT_FLAG_INVALID)
592 return (NULL);
593 if (kstat_read(kc, ksp, &mik) == -1)
594 return (NULL);
595 rstr = safe_strdup(mik.mik_curserver);
596 return (rstr);
597 }
598 return (NULL);
599 }
600
601 /*
602 * Given the hostname of the mounted server, extract the server
603 * mount point from the mnttab string.
604 *
605 * Common forms:
606 * server1,server2,server3:/path
607 * server1:/path,server2:/path
608 * or a hybrid of the two
609 */
610 static char *
cur_special(char * hostname,char * special)611 cur_special(char *hostname, char *special)
612 {
613 char *cp;
614 char *path;
615 size_t hlen = strlen(hostname);
616
617 /*
618 * find hostname in string
619 */
620 again:
621 if ((cp = strstr(special, hostname)) == NULL)
622 return (NULL);
623
624 /*
625 * hostname must be followed by ',' or ':'
626 */
627 if (cp[hlen] != ',' && cp[hlen] != ':') {
628 special = &cp[hlen];
629 goto again;
630 }
631
632 /*
633 * If hostname is followed by a ',' eat all characters until a ':'
634 */
635 cp = &cp[hlen];
636 if (*cp == ',') {
637 cp++;
638 while (*cp != ':') {
639 if (*cp == NULL)
640 return (NULL);
641 cp++;
642 }
643 }
644 path = ++cp; /* skip ':' */
645
646 /*
647 * path is terminated by either 0, or space or ','
648 */
649 while (*cp) {
650 if (isspace(*cp) || *cp == ',') {
651 *cp = NULL;
652 return (path);
653 }
654 cp++;
655 }
656 return (path);
657 }
658