xref: /illumos-gate/usr/src/cmd/stat/common/dsr.c (revision fec509a05ddbf645268fe2e537314def7d1b67c8)
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
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
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
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 *
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 *
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 *
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 *
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 *
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 *
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