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 */
62static di_node_t	di_root;	/* from di_init: for devid */
63static di_dim_t		di_dim;		/* from di_dim_init: for /dev names */
64static int		scsi_vhci_fd = -1; /* from scsi_vhci: for mpxio path */
65
66/* disk/tape/misc info */
67typedef struct {
68	char		*minor_name;
69	int		minor_isdisk;
70} minor_match_t;
71static minor_match_t	mm_disk = {"a", 1};
72static minor_match_t	mm_tape	= {"", 0};
73static minor_match_t	mm_misc	= {"0", 0};
74static char		md_minor_name[MAXPATHLEN];
75static minor_match_t	mm_md	= {md_minor_name, 0};
76static minor_match_t	*mma_disk_tape_misc[]	=
77			    {&mm_disk, &mm_tape, &mm_misc, NULL};
78static minor_match_t	*mma_md[]		= {&mm_md, NULL};
79static char *mdsetno2name(int setno);
80#define	DISKLIST_MOD	256		/* ^2 instunit mod hash */
81static disk_list_t	*disklist[DISKLIST_MOD];
82
83
84/* nfs info */
85extern kstat_ctl_t	*kc;
86extern mnt_t		*nfs;
87static int		nfs_tried;
88static char		*get_nfs_by_minor(uint_t);
89static char		*cur_hostname(uint_t, kstat_ctl_t *);
90static 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 */
96void
97cleanup_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 */
118static int
119drvinstunitpart2dev(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 */
290static int
291drvpid2port(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 */
339disk_list_t *
340lookup_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
450fail:
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 */
465static char *
466mdsetno2name(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
510char *
511lookup_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) {
520retry:
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
557static char *
558get_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 */
575static char *
576cur_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 */
610static char *
611cur_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	 */
620again:
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