xref: /illumos-gate/usr/src/cmd/stat/common/dsr.c (revision f9409f99581bedf7777548eccf1310c6995764a0)
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 /*
23  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  * Copyright 2016 Nexenta Systems, Inc.
26  */
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 minor_match_t	*mma_disk_tape_misc[] =
75 			    {&mm_disk, &mm_tape, &mm_misc, NULL};
76 #define	DISKLIST_MOD	256		/* ^2 instance mod hash */
77 static disk_list_t	*disklist[DISKLIST_MOD];
78 
79 
80 /* nfs info */
81 extern kstat_ctl_t	*kc;
82 extern mnt_t		*nfs;
83 static int		nfs_tried;
84 static char		*get_nfs_by_minor(uint_t);
85 static char		*cur_hostname(uint_t, kstat_ctl_t *);
86 static char		*cur_special(char *, char *);
87 
88 /*
89  * Clear the snapshot so a cache miss in lookup_ks_name() will cause a fresh
90  * snapshot in drvinstpart2dev().
91  */
92 void
93 cleanup_iodevs_snapshot()
94 {
95 	if (di_dim) {
96 		di_dim_fini(di_dim);
97 		di_dim = NULL;
98 	}
99 
100 	if (di_root) {
101 		di_fini(di_root);
102 		di_root = DI_NODE_NIL;
103 	}
104 
105 	nfs_tried = 0;
106 }
107 
108 /*
109  * Find information for (driver, instance) device: return zero on failure.
110  *
111  * NOTE: Failure of drvinstpart2dev works out OK for the caller if the kstat
112  * name is the same as public name: the caller will just use kstat name.
113  */
114 static int
115 drvinstpart2dev(char *driver, int instance, char *part,
116     char **devpathp, char **adevpathp, char **devidp)
117 {
118 	minor_match_t	*mm, **mma = mma_disk_tape_misc;
119 	char		*devpath;
120 	char		*devid;
121 	char		*devicespath;
122 	di_node_t	node;
123 
124 	/* setup "no result" return values */
125 	if (devpathp != NULL)
126 		*devpathp = NULL;
127 	if (adevpathp != NULL)
128 		*adevpathp = NULL;
129 	if (devidp != NULL)
130 		*devidp = NULL;
131 
132 	/* take <driver><instance><minor> snapshot if not established */
133 	if (di_dim == NULL) {
134 		di_dim = di_dim_init();
135 		if (di_dim == NULL)
136 			return (0);
137 	}
138 
139 	if (part != NULL) {
140 		devpath = di_dim_path_dev(di_dim, driver, instance, part);
141 	} else  {
142 		/* Try to find a minor_match that works */
143 		for (mm = *mma++; mm != NULL; mm = *mma++) {
144 			if ((devpath = di_dim_path_dev(di_dim,
145 			    driver, instance, mm->minor_name)) != NULL)
146 				break;
147 		}
148 	}
149 	if (devpath == NULL)
150 		return (0);
151 
152 	/*
153 	 * At this point we have a devpath result. Return the information about
154 	 * the result that the caller is asking for.
155 	 */
156 	if (devpathp != NULL)			/* devpath */
157 		*devpathp = safe_strdup(devpath);
158 
159 	if (adevpathp != NULL) {		/* abbreviated devpath */
160 		char	*a;
161 
162 		a = strrchr(devpath, '/');
163 		if (a == NULL) {
164 			free(devpath);
165 			return (0);
166 		}
167 		a++;
168 		if (part == NULL && mm->minor_isdisk) {
169 			/*
170 			 * For disk kstats without a partition we return the
171 			 * last component with trailing "s#" or "p#" stripped
172 			 * off (i.e. partition/slice information is removed).
173 			 * For example for devpath of "/dev/dsk/c0t0d0s0" the
174 			 * abbreviated devpath would be "c0t0d0".
175 			 */
176 			char	*s;
177 
178 			if ((s = strrchr(a, 's')) == NULL &&
179 			    (s = strrchr(a, 'p')) == NULL) {
180 				free(devpath);
181 				return (0);
182 			}
183 			/* don't include slice information in devpath */
184 			*s = '\0';
185 		}
186 		*adevpathp = safe_strdup(a);
187 	}
188 
189 	if (devidp != NULL) {		/* lookup the devid */
190 		/* take snapshot if not established */
191 		if (di_root == DI_NODE_NIL)
192 			di_root = di_init("/", DINFOCACHE);
193 		if (di_root != NULL) {
194 			/* get path to /devices devinfo node */
195 			devicespath = di_dim_path_devices(di_dim,
196 			    driver, instance, NULL);
197 			if (devicespath) {
198 				/* find the node in the snapshot */
199 				node = di_lookup_node(di_root, devicespath);
200 				free(devicespath);
201 
202 				/* and lookup devid property on the node */
203 				if (di_prop_lookup_strings(DDI_DEV_T_ANY, node,
204 				    DEVID_PROP_NAME, &devid) != -1)
205 					*devidp = devid;
206 			}
207 		}
208 	}
209 
210 	free(devpath);
211 	return (1);				/* success */
212 }
213 
214 /*
215  * Do <pid> to 'target-port' translation
216  */
217 static int
218 drvpid2port(uint_t pid, char **target_portp)
219 {
220 	sv_iocdata_t	ioc;
221 	char		target_port[MAXNAMELEN];
222 
223 	/* setup "no result" return values */
224 	*target_portp = NULL;
225 
226 	/* open scsi_vhci if not already done */
227 	if (scsi_vhci_fd == -1) {
228 		scsi_vhci_fd = open("/devices/scsi_vhci:devctl", O_RDONLY);
229 		if (scsi_vhci_fd == -1)
230 			return (0);		/* failure */
231 	}
232 
233 	/*
234 	 * Perform ioctl for <pid> -> 'target-port' translation.
235 	 *
236 	 * NOTE: it is legimite for this ioctl to fail for transports
237 	 * that use mpxio, but don't set a 'target-port' pathinfo property.
238 	 * On failure we return the the "<pid>" as the target port string.
239 	 */
240 	bzero(&ioc, sizeof (sv_iocdata_t));
241 	ioc.buf_elem = pid;
242 	ioc.addr = target_port;
243 	if (ioctl(scsi_vhci_fd, SCSI_VHCI_GET_TARGET_LONGNAME, &ioc) < 0) {
244 		(void) snprintf(target_port, sizeof (target_port), "%d", pid);
245 	}
246 
247 	*target_portp = safe_strdup(target_port);
248 	return (1);				/* success */
249 }
250 
251 /*
252  * Find/create a disk_list entry for the given kstat name.
253  * The basic format of a kstat name is
254  *
255  *	"<driver><instance>[.<pid>.<phci-driver><instance>][,<partition>]".
256  *
257  * The <instance> is a decimal number. The ".<pid>.<phci-driver><instance>",
258  * which describes mpxio path stat information, and ",<partition>" parts are
259  * optional. The <pid> consists of the letter 't' followed by a decimal number.
260  * When available, we use the <pid> to find the 'target-port' via ioctls to
261  * the scsi_vhci driver.
262  */
263 disk_list_t *
264 lookup_ks_name(char *ks_name, int want_devid)
265 {
266 	char		*pidp;		/* ".<pid>... */
267 	char		*part;		/* ",partition... */
268 	char		*initiator;	/* ".<phci-driver>... */
269 	char		*p;
270 	int		len;
271 	char		driver[KSTAT_STRLEN];
272 	int		instance;
273 	disk_list_t	**dlhp;		/* disklist head */
274 	disk_list_t	*entry;
275 	char		*devpath = NULL;
276 	char		*adevpath = NULL;
277 	char		*devid = NULL;
278 	int		pid;
279 	char		*target_port = NULL;
280 	char		portform[MAXPATHLEN];
281 
282 	/* Filter out illegal forms (like all digits) */
283 	if (ks_name == NULL || *ks_name == '\0' ||
284 	    strspn(ks_name, "0123456789") == strlen(ks_name))
285 		goto fail;
286 
287 	/* parse ks_name to create new entry */
288 	pidp = strchr(ks_name, '.');		/* start of ".<pid>" */
289 	initiator = strrchr(ks_name, '.');	/* start of ".<pHCI-driver>" */
290 	if (pidp != NULL && pidp == initiator)	/* can't have same start */
291 		goto fail;
292 
293 	part = strchr(ks_name, ',');		/* start of ",<partition>" */
294 	p = strchr(ks_name, ':');		/* start of ":<partition>" */
295 	if (part != NULL && p != NULL)
296 		goto fail;			/* can't have both */
297 	if (p != NULL)
298 		part = p;
299 	if (part != NULL && pidp != NULL)
300 		goto fail;			/* <pid> and partition: bad */
301 
302 	p = (part != NULL) ? part : pidp;
303 	if (p == NULL)
304 		p = &ks_name[strlen(ks_name) - 1];	/* last char */
305 	else
306 		p--;				/* before ',' or '.' */
307 
308 	while ((p >= ks_name) && isdigit(*p))
309 		p--;				/* backwards over digits */
310 	p++;					/* start of instance */
311 	if ((*p == '\0') || (*p == ',') || (*p == '.') || (*p == ':'))
312 		goto fail;			/* no <instance> */
313 	len = p - ks_name;
314 	(void) strncpy(driver, ks_name, len);
315 	driver[len] = '\0';
316 	instance = atoi(p);
317 	if (part != NULL)
318 		part++;				/* skip ',' */
319 
320 	/* hash by instance and search for existing entry */
321 	dlhp = &disklist[instance & (DISKLIST_MOD - 1)];
322 	for (entry = *dlhp; entry; entry = entry->next) {
323 		if (strcmp(entry->ks_name, ks_name) == 0)
324 			return (entry);
325 	}
326 
327 	/* not found, translate kstat_name components and create new entry */
328 
329 	/* translate kstat_name dev information */
330 	if (drvinstpart2dev(driver, instance, part,
331 	    &devpath, &adevpath, want_devid ? &devid : NULL) == 0)
332 		goto fail;
333 
334 	/* parse and translate path information */
335 	if (pidp != NULL) {
336 		/* parse path information: ".t#.<phci-driver><instance>" */
337 		pidp++;				/* skip '.' */
338 		initiator++;			/* skip '.' */
339 		if (*pidp != 't' || !isdigit(pidp[1]))
340 			goto fail;		/* not ".t#" */
341 		pid = atoi(&pidp[1]);
342 
343 		/* translate <pid> to 'target-port' */
344 		if (drvpid2port(pid, &target_port) == 0)
345 			goto fail;
346 
347 		/* Establish 'target-port' form. */
348 		(void) snprintf(portform, sizeof (portform),
349 		    "%s.t%s.%s", adevpath, target_port, initiator);
350 		free(target_port);
351 		free(adevpath);
352 		adevpath = strdup(portform);
353 	}
354 
355 	/* make a new entry ... */
356 	entry = safe_alloc(sizeof (disk_list_t));
357 	entry->ks_name = safe_strdup(ks_name);
358 	entry->dname = devpath;
359 	entry->dsk = adevpath;
360 	entry->devidstr = devid;
361 
362 #ifdef	DEBUG
363 	(void) printf("lookup_ks_name:    new: %s	%s\n",
364 	    ks_name, entry->dsk ? entry->dsk : "NULL");
365 #endif	/* DEBUG */
366 
367 	/* add new entry to head of hashed list */
368 	entry->next = *dlhp;
369 	*dlhp = entry;
370 	return (entry);
371 
372 fail:
373 	free(devpath);
374 	free(adevpath);
375 	free(devid);
376 #ifdef	DEBUG
377 	(void) printf("lookup_ks_name: failed: %s\n", ks_name);
378 #endif	/* DEBUG */
379 	return (NULL);
380 }
381 
382 char *
383 lookup_nfs_name(char *ks, kstat_ctl_t *kc)
384 {
385 	uint_t minor;
386 	char *host, *path;
387 	char *cp;
388 	char *rstr = 0;
389 	size_t len;
390 
391 	if (sscanf(ks, "nfs%u", &minor) == 1) {
392 retry:
393 		cp = get_nfs_by_minor(minor);
394 		if (cp) {
395 			if (strchr(cp, ',') == NULL) {
396 				rstr = safe_strdup(cp);
397 				return (rstr);
398 			}
399 			host = cur_hostname(minor, kc);
400 			if (host) {
401 				if (*host) {
402 					path = cur_special(host, cp);
403 					if (path) {
404 						len = strlen(host);
405 						len += strlen(path);
406 						len += 2;
407 						rstr = safe_alloc(len);
408 						(void) snprintf(rstr, len,
409 						    "%s:%s", host, path);
410 					} else {
411 						rstr = safe_strdup(cp);
412 					}
413 				} else {
414 					rstr = safe_strdup(ks);
415 				}
416 				free(host);
417 			} else {
418 				rstr = safe_strdup(cp);
419 			}
420 		} else if (nfs_tried == 0) {
421 			nfs_tried = 1;
422 			do_mnttab();
423 			goto retry;
424 		}
425 	}
426 	return (rstr);
427 }
428 
429 static char *
430 get_nfs_by_minor(uint_t minor)
431 {
432 	mnt_t *localnfs;
433 
434 	localnfs = nfs;
435 	while (localnfs) {
436 		if (localnfs->minor == minor) {
437 			return (localnfs->device_name);
438 		}
439 		localnfs = localnfs->next;
440 	}
441 	return (0);
442 }
443 
444 /*
445  * Read the cur_hostname from the mntinfo kstat
446  */
447 static char *
448 cur_hostname(uint_t minor, kstat_ctl_t *kc)
449 {
450 	kstat_t *ksp;
451 	static struct mntinfo_kstat mik;
452 	char *rstr;
453 
454 	for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) {
455 		if (ksp->ks_type != KSTAT_TYPE_RAW)
456 			continue;
457 		if (ksp->ks_instance != minor)
458 			continue;
459 		if (strcmp(ksp->ks_module, "nfs"))
460 			continue;
461 		if (strcmp(ksp->ks_name, "mntinfo"))
462 			continue;
463 		if (ksp->ks_flags & KSTAT_FLAG_INVALID)
464 			return (NULL);
465 		if (kstat_read(kc, ksp, &mik) == -1)
466 			return (NULL);
467 		rstr = safe_strdup(mik.mik_curserver);
468 		return (rstr);
469 	}
470 	return (NULL);
471 }
472 
473 /*
474  * Given the hostname of the mounted server, extract the server
475  * mount point from the mnttab string.
476  *
477  * Common forms:
478  *	server1,server2,server3:/path
479  *	server1:/path,server2:/path
480  * or a hybrid of the two
481  */
482 static char *
483 cur_special(char *hostname, char *special)
484 {
485 	char *cp;
486 	char *path;
487 	size_t hlen = strlen(hostname);
488 
489 	/*
490 	 * find hostname in string
491 	 */
492 again:
493 	if ((cp = strstr(special, hostname)) == NULL)
494 		return (NULL);
495 
496 	/*
497 	 * hostname must be followed by ',' or ':'
498 	 */
499 	if (cp[hlen] != ',' && cp[hlen] != ':') {
500 		special = &cp[hlen];
501 		goto again;
502 	}
503 
504 	/*
505 	 * If hostname is followed by a ',' eat all characters until a ':'
506 	 */
507 	cp = &cp[hlen];
508 	if (*cp == ',') {
509 		cp++;
510 		while (*cp != ':') {
511 			if (*cp == '\0')
512 				return (NULL);
513 			cp++;
514 		}
515 	}
516 	path = ++cp;			/* skip ':' */
517 
518 	/*
519 	 * path is terminated by either 0, or space or ','
520 	 */
521 	while (*cp) {
522 		if (isspace(*cp) || *cp == ',') {
523 			*cp = '\0';
524 			return (path);
525 		}
526 		cp++;
527 	}
528 	return (path);
529 }
530