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