xref: /illumos-gate/usr/src/cmd/fs.d/autofs/auto_subr.c (revision 734b6a94890be549309b21156f8ed6d4561cac51)
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 <ctype.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <unistd.h>
32 #include <locale.h>
33 #include <syslog.h>
34 #include <errno.h>
35 #include <string.h>
36 #include <stdarg.h>
37 #include <dirent.h>
38 #include <thread.h>
39 #include <sys/param.h>
40 #include <sys/time.h>
41 #include <sys/vfs.h>
42 #include <sys/types.h>
43 #include <sys/stat.h>
44 #include <sys/mnttab.h>
45 #include <sys/mntent.h>
46 #include <sys/mount.h>
47 #include <sys/signal.h>
48 #include <sys/utsname.h>
49 #include <sys/systeminfo.h>
50 #include <sys/tiuser.h>
51 #include <sys/utsname.h>
52 #include <rpc/rpc.h>
53 #include <rpcsvc/nfs_prot.h>
54 #include <assert.h>
55 #include "automount.h"
56 #include <zone.h>
57 #include <priv.h>
58 #include <fcntl.h>
59 
60 static char *check_hier(char *);
61 static int natisa(char *, size_t);
62 
63 struct mntlist *current_mounts;
64 
65 static bool_t nodirect_map = FALSE;
66 
67 /*
68  * If the system is labeled then we need to
69  * have a uniquely-named auto_home map for each zone.
70  * The maps are made unique by appending the zonename.
71  * The home directory is made unique by prepending /zone/<zonename>
72  * for each zone that is dominated by the current zone.
73  * The current zone's home directory mount point is not changed.
74  *
75  * For each auto_home_<zonename> a default template map is created
76  * only if it doesn't exist yet. The default entry is used to declare
77  * local home directories created within each zone. For example:
78  *
79  *	+auto_home_public
80  *      * -fstype=lofs :/zone/public/export/home/&
81  */
82 static void
83 loadzone_maps(char *mntpnt, char *map, char *opts, char **stack, char ***stkptr)
84 {
85 	zoneid_t *zids = NULL;
86 	zoneid_t my_zoneid;
87 	uint_t nzents_saved;
88 	uint_t nzents;
89 	int i;
90 
91 	if (!priv_ineffect(PRIV_SYS_MOUNT))
92 		return;
93 
94 	if (zone_list(NULL, &nzents) != 0) {
95 		return;
96 	}
97 	my_zoneid = getzoneid();
98 again:
99 	if (nzents == 0)
100 		return;
101 
102 	zids = malloc(nzents * sizeof (zoneid_t));
103 	nzents_saved = nzents;
104 
105 	if (zone_list(zids, &nzents) != 0) {
106 		free(zids);
107 		return;
108 	}
109 	if (nzents != nzents_saved) {
110 		/* list changed, try again */
111 		free(zids);
112 		goto again;
113 	}
114 
115 	for (i = 0; i < nzents; i++) {
116 		char zonename[ZONENAME_MAX];
117 		char zoneroot[MAXPATHLEN];
118 
119 		if (getzonenamebyid(zids[i], zonename, ZONENAME_MAX) != -1) {
120 			char appended_map[MAXPATHLEN];
121 			char prepended_mntpnt[MAXPATHLEN];
122 			char map_path[MAXPATHLEN];
123 			int fd;
124 
125 			(void) snprintf(appended_map, sizeof (appended_map),
126 			    "%s_%s", map, zonename);
127 
128 			/* for current zone, leave mntpnt alone */
129 			if (zids[i] != my_zoneid) {
130 				(void) snprintf(prepended_mntpnt,
131 				    sizeof (prepended_mntpnt),
132 				    "/zone/%s%s", zonename, mntpnt);
133 				if (zone_getattr(zids[i], ZONE_ATTR_ROOT,
134 				    zoneroot, sizeof (zoneroot)) == -1)
135 					continue;
136 			} else {
137 				(void) strcpy(prepended_mntpnt, mntpnt);
138 				zoneroot[0] = '\0';
139 			}
140 
141 			dirinit(prepended_mntpnt, appended_map, opts, 0, stack,
142 			    stkptr);
143 			/*
144 			 * Next create auto_home_<zone> maps for each zone
145 			 */
146 
147 			(void) snprintf(map_path, sizeof (map_path),
148 			    "/etc/%s", appended_map);
149 			/*
150 			 * If the map file doesn't exist create a template
151 			 */
152 			if ((fd = open(map_path, O_RDWR | O_CREAT | O_EXCL,
153 			    S_IRUSR | S_IWUSR | S_IRGRP| S_IROTH)) != -1) {
154 				int len;
155 				char map_rec[MAXPATHLEN];
156 
157 				len = snprintf(map_rec, sizeof (map_rec),
158 				    "+%s\n*\t-fstype=lofs\t:%s/export/home/&\n",
159 				    appended_map, zoneroot);
160 				if (len <= sizeof (map_rec))
161 					(void) write(fd, map_rec, len);
162 				(void) close(fd);
163 			}
164 		}
165 	}
166 	free(zids);
167 }
168 
169 void
170 dirinit(char *mntpnt, char *map, char *opts, int direct, char **stack,
171     char ***stkptr)
172 {
173 	struct autodir *dir;
174 	char *p;
175 
176 	if (strcmp(map, "-null") == 0) {
177 		if (strcmp(mntpnt, "/-") == 0)
178 			nodirect_map = TRUE;
179 		goto enter;
180 	}
181 
182 	p = mntpnt + (strlen(mntpnt) - 1);
183 	if (*p == '/')
184 		*p = '\0';	/* trim trailing / */
185 	if (*mntpnt != '/') {
186 		pr_msg("dir %s must start with '/'", mntpnt);
187 		return;
188 	}
189 	if (p = check_hier(mntpnt)) {
190 		pr_msg("hierarchical mountpoint: %s and %s",
191 			p, mntpnt);
192 		return;
193 	}
194 
195 	/*
196 	 * If it's a direct map then call dirinit
197 	 * for every map entry.
198 	 */
199 	if ((strcmp(mntpnt, "/-") == 0) && !(nodirect_map)) {
200 		(void) loaddirect_map(map, map, opts, stack, stkptr);
201 		return;
202 	}
203 
204 	/*
205 	 * Home directories are polyinstantiated on
206 	 * labeled systems.
207 	 */
208 	if (is_system_labeled() &&
209 	    (strcmp(mntpnt, "/home") == 0) &&
210 	    (strcmp(map, "auto_home") == 0)) {
211 		(void) loadzone_maps(mntpnt, map, opts, stack, stkptr);
212 		return;
213 	}
214 enter:
215 	dir = (struct autodir *)malloc(sizeof (*dir));
216 	if (dir == NULL)
217 		goto alloc_failed;
218 	dir->dir_name = strdup(mntpnt);
219 	if (dir->dir_name == NULL)
220 		goto alloc_failed;
221 	dir->dir_map = strdup(map);
222 	if (dir->dir_map == NULL)
223 		goto alloc_failed;
224 	dir->dir_opts = strdup(opts);
225 	if (dir->dir_opts == NULL)
226 		goto alloc_failed;
227 	dir->dir_direct = direct;
228 	dir->dir_remount = 0;
229 	dir->dir_next = NULL;
230 
231 	/*
232 	 * Append to dir chain
233 	 */
234 	if (dir_head == NULL)
235 		dir_head = dir;
236 	else
237 		dir_tail->dir_next = dir;
238 
239 	dir->dir_prev = dir_tail;
240 	dir_tail = dir;
241 
242 	return;
243 
244 alloc_failed:
245 	if (dir != NULL) {
246 		if (dir->dir_opts)
247 			free(dir->dir_opts);
248 		if (dir->dir_map)
249 			free(dir->dir_map);
250 		if (dir->dir_name)
251 			free(dir->dir_name);
252 		free(dir);
253 	}
254 	pr_msg("dirinit: memory allocation failed");
255 }
256 
257 /*
258  *  Check whether the mount point is a
259  *  subdirectory or a parent directory
260  *  of any previously mounted automount
261  *  mount point.
262  */
263 static char *
264 check_hier(mntpnt)
265 	char *mntpnt;
266 {
267 	register struct autodir *dir;
268 	register char *p, *q;
269 
270 	for (dir = dir_head; dir; dir = dir->dir_next) {
271 		p = dir->dir_name;
272 		q = mntpnt;
273 		for (; *p == *q; p++, q++)
274 			if (*p == '\0')
275 				break;
276 		if (*p == '/' && *q == '\0')
277 			return (dir->dir_name);
278 		if (*p == '\0' && *q == '/')
279 			return (dir->dir_name);
280 		if (*p == '\0' && *q == '\0')
281 			return (NULL);
282 	}
283 	return (NULL);	/* it's not a subdir or parent */
284 }
285 
286 /*
287  * Gets the next token from the string "p" and copies
288  * it into "w".  Both "wq" and "w" are quote vectors
289  * for "w" and "p".  Delim is the character to be used
290  * as a delimiter for the scan.  A space means "whitespace".
291  * The call to getword must provide buffers w and wq of size at
292  * least wordsz. getword() will pass strings of maximum length
293  * (wordsz-1), since it needs to null terminate the string.
294  * Returns 0 on ok and -1 on error.
295  */
296 int
297 getword(char *w, char *wq, char **p, char **pq, char delim, int wordsz)
298 {
299 	char *tmp = w;
300 	char *tmpq = wq;
301 	int count = wordsz;
302 
303 	if (wordsz <= 0) {
304 		if (verbose)
305 			syslog(LOG_ERR,
306 			"getword: input word size %d must be > 0", wordsz);
307 		return (-1);
308 	}
309 
310 	while ((delim == ' ' ? isspace(**p) : **p == delim) && **pq == ' ')
311 		(*p)++, (*pq)++;
312 
313 	while (**p &&
314 		!((delim == ' ' ? isspace(**p) : **p == delim) &&
315 			**pq == ' ')) {
316 		if (--count <= 0) {
317 			*tmp = '\0';
318 			*tmpq = '\0';
319 			syslog(LOG_ERR,
320 			"maximum word length (%d) exceeded", wordsz);
321 			return (-1);
322 		}
323 		*w++  = *(*p)++;
324 		*wq++ = *(*pq)++;
325 	}
326 	*w  = '\0';
327 	*wq = '\0';
328 
329 	return (0);
330 }
331 
332 /*
333  * get_line attempts to get a line from the map, upto LINESZ. A line in
334  * the map is a concatenation of lines if the continuation symbol '\'
335  * is used at the end of the line. Returns line on success, a NULL on
336  * EOF, and an empty string on lines > linesz.
337  */
338 char *
339 get_line(FILE *fp, char *map, char *line, int linesz)
340 {
341 	register char *p = line;
342 	register int len;
343 	int excess = 0;
344 
345 	*p = '\0';
346 
347 	for (;;) {
348 		if (fgets(p, linesz - (p-line), fp) == NULL) {
349 			return (*line ? line : NULL);	/* EOF */
350 		}
351 
352 		len = strlen(line);
353 		if (len <= 0) {
354 			p = line;
355 			continue;
356 		}
357 		p = &line[len - 1];
358 
359 		/*
360 		 * Is input line too long?
361 		 */
362 		if (*p != '\n') {
363 			excess = 1;
364 			/*
365 			 * Perhaps last char read was '\'. Reinsert it
366 			 * into the stream to ease the parsing when we
367 			 * read the rest of the line to discard.
368 			 */
369 			(void) ungetc(*p, fp);
370 			break;
371 		}
372 trim:
373 		/* trim trailing white space */
374 		while (p >= line && isspace(*(uchar_t *)p))
375 			*p-- = '\0';
376 		if (p < line) {			/* empty line */
377 			p = line;
378 			continue;
379 		}
380 
381 		if (*p == '\\') {		/* continuation */
382 			*p = '\0';
383 			continue;
384 		}
385 
386 		/*
387 		 * Ignore comments. Comments start with '#'
388 		 * which must be preceded by a whitespace, unless
389 		 * if '#' is the first character in the line.
390 		 */
391 		p = line;
392 		while (p = strchr(p, '#')) {
393 			if (p == line || isspace(*(p-1))) {
394 				*p-- = '\0';
395 				goto trim;
396 			}
397 			p++;
398 		}
399 		break;
400 	}
401 	if (excess) {
402 		int c;
403 
404 		/*
405 		 * discard rest of line and return an empty string.
406 		 * done to set the stream to the correct place when
407 		 * we are done with this line.
408 		 */
409 		while ((c = getc(fp)) != EOF) {
410 			*p = c;
411 			if (*p == '\n')		/* end of the long line */
412 				break;
413 			else if (*p == '\\') {		/* continuation */
414 				if (getc(fp) == EOF)	/* ignore next char */
415 					break;
416 			}
417 		}
418 		syslog(LOG_ERR,
419 			"map %s: line too long (max %d chars)",
420 			map, linesz-1);
421 		*line = '\0';
422 	}
423 
424 	return (line);
425 }
426 
427 /*
428  * Gets the retry=n entry from opts.
429  * Returns 0 if retry=n is not present in option string,
430  * retry=n is invalid, or when option string is NULL.
431  */
432 int
433 get_retry(char *opts)
434 {
435 	int retry = 0;
436 	char buf[MAXOPTSLEN];
437 	char *p, *pb, *lasts;
438 
439 	if (opts == NULL)
440 		return (retry);
441 
442 	(void) strcpy(buf, opts);
443 	pb = buf;
444 	while (p = (char *)strtok_r(pb, ",", &lasts)) {
445 		pb = NULL;
446 		if (strncmp(p, "retry=", 6) == 0)
447 			retry = atoi(p+6);
448 	}
449 	return (retry > 0 ? retry : 0);
450 }
451 
452 /*
453  * Returns zero if "opt" is found in mnt->mnt_opts, setting
454  * *sval to whatever follows the equal sign after "opt".
455  * str_opt allocates a string long enough to store the value of
456  * "opt" plus a terminating null character and returns it as *sval.
457  * It is the responsability of the caller to deallocate *sval.
458  * *sval will be equal to NULL upon return if either "opt=" is not found,
459  * or "opt=" has no value associated with it.
460  *
461  * stropt will return -1 on error.
462  */
463 int
464 str_opt(struct mnttab *mnt, char *opt, char **sval)
465 {
466 	char *str, *comma;
467 
468 	/*
469 	 * is "opt" in the options field?
470 	 */
471 	if (str = hasmntopt(mnt, opt)) {
472 		str += strlen(opt);
473 		if (*str++ != '=' ||
474 		    (*str == ',' || *str == '\0')) {
475 			syslog(LOG_ERR, "Bad option field");
476 			return (-1);
477 		}
478 		comma = strchr(str, ',');
479 		if (comma != NULL)
480 			*comma = '\0';
481 		*sval = strdup(str);
482 		if (comma != NULL)
483 			*comma = ',';
484 		if (*sval == NULL)
485 			return (-1);
486 	} else
487 		*sval = NULL;
488 
489 	return (0);
490 }
491 
492 /*
493  * Performs text expansions in the string "pline".
494  * "plineq" is the quote vector for "pline".
495  * An identifier prefixed by "$" is replaced by the
496  * corresponding environment variable string.  A "&"
497  * is replaced by the key string for the map entry.
498  *
499  * This routine will return an error (non-zero) if *size* would be
500  * exceeded after expansion, indicating that the macro_expand failed.
501  * This is to prevent writing past the end of pline and plineq.
502  * Both pline and plineq are left untouched in such error case.
503  */
504 int
505 macro_expand(key, pline, plineq, size)
506 	char *key, *pline, *plineq;
507 	int size;
508 {
509 	register char *p,  *q;
510 	register char *bp, *bq;
511 	register char *s;
512 	char buffp[LINESZ], buffq[LINESZ];
513 	char namebuf[64], *pn;
514 	int expand = 0;
515 	struct utsname name;
516 	char isaname[64];
517 
518 	p = pline;  q = plineq;
519 	bp = buffp; bq = buffq;
520 
521 	while (*p) {
522 		if (*p == '&' && *q == ' ') {	/* insert key */
523 			/*
524 			 * make sure we don't overflow buffer
525 			 */
526 			if ((int)((bp - buffp) + strlen(key)) < size) {
527 				for (s = key; *s; s++) {
528 					*bp++ = *s;
529 					*bq++ = ' ';
530 				}
531 				expand++;
532 				p++; q++;
533 				continue;
534 			} else {
535 				/*
536 				 * line too long...
537 				 */
538 				return (1);
539 			}
540 		}
541 
542 		if (*p == '$' && *q == ' ') {	/* insert env var */
543 			p++; q++;
544 			pn = namebuf;
545 			if (*p == '{') {
546 				p++; q++;
547 				while (*p && *p != '}') {
548 					*pn++ = *p++;
549 					q++;
550 				}
551 				if (*p) {
552 					p++; q++;
553 				}
554 			} else {
555 				while (*p && (*p == '_' || isalnum(*p))) {
556 					*pn++ = *p++;
557 					q++;
558 				}
559 			}
560 			*pn = '\0';
561 
562 			s = getenv(namebuf);
563 			if (!s) {
564 				/* not found in env */
565 				if (strcmp(namebuf, "HOST") == 0) {
566 					(void) uname(&name);
567 					s = name.nodename;
568 				} else if (strcmp(namebuf, "OSREL") == 0) {
569 					(void) uname(&name);
570 					s = name.release;
571 				} else if (strcmp(namebuf, "OSNAME") == 0) {
572 					(void) uname(&name);
573 					s = name.sysname;
574 				} else if (strcmp(namebuf, "OSVERS") == 0) {
575 					(void) uname(&name);
576 					s = name.version;
577 				} else if (strcmp(namebuf, "NATISA") == 0) {
578 					if (natisa(isaname, sizeof (isaname)))
579 						s = isaname;
580 				}
581 			}
582 
583 			if (s) {
584 				if ((int)((bp - buffp) + strlen(s)) < size) {
585 					while (*s) {
586 						*bp++ = *s++;
587 						*bq++ = ' ';
588 					}
589 				} else {
590 					/*
591 					 * line too long...
592 					 */
593 					return (1);
594 				}
595 			}
596 			expand++;
597 			continue;
598 		}
599 		/*
600 		 * Since buffp needs to be null terminated, we need to
601 		 * check that there's still room in the buffer to
602 		 * place at least two more characters, *p and the
603 		 * terminating null.
604 		 */
605 		if (bp - buffp == size - 1) {
606 			/*
607 			 * There was not enough room for at least two more
608 			 * characters, return with an error.
609 			 */
610 			return (1);
611 		}
612 		/*
613 		 * The total number of characters so far better be less
614 		 * than the size of buffer passed in.
615 		 */
616 		*bp++ = *p++;
617 		*bq++ = *q++;
618 
619 	}
620 	if (!expand)
621 		return (0);
622 	*bp = '\0';
623 	*bq = '\0';
624 	/*
625 	 * We know buffp/buffq will fit in pline/plineq since we
626 	 * processed at most size characters.
627 	 */
628 	(void) strcpy(pline, buffp);
629 	(void) strcpy(plineq, buffq);
630 
631 	return (0);
632 }
633 
634 /*
635  * Removes quotes from the string "str" and returns
636  * the quoting information in "qbuf". e.g.
637  * original str: 'the "quick brown" f\ox'
638  * unquoted str: 'the quick brown fox'
639  * and the qbuf: '    ^^^^^^^^^^^  ^ '
640  */
641 void
642 unquote(str, qbuf)
643 	char *str, *qbuf;
644 {
645 	register int escaped, inquote, quoted;
646 	register char *ip, *bp, *qp;
647 	char buf[LINESZ];
648 
649 	escaped = inquote = quoted = 0;
650 
651 	for (ip = str, bp = buf, qp = qbuf; *ip; ip++) {
652 		if (!escaped) {
653 			if (*ip == '\\') {
654 				escaped = 1;
655 				quoted++;
656 				continue;
657 			} else
658 			if (*ip == '"') {
659 				inquote = !inquote;
660 				quoted++;
661 				continue;
662 			}
663 		}
664 
665 		*bp++ = *ip;
666 		*qp++ = (inquote || escaped) ? '^' : ' ';
667 		escaped = 0;
668 	}
669 	*bp = '\0';
670 	*qp = '\0';
671 	if (quoted)
672 		(void) strcpy(str, buf);
673 }
674 
675 /*
676  * Removes trailing spaces from string "s".
677  */
678 void
679 trim(s)
680 	char *s;
681 {
682 	char *p = &s[strlen(s) - 1];
683 
684 	while (p >= s && isspace(*(uchar_t *)p))
685 		*p-- = '\0';
686 }
687 
688 /*
689  * try to allocate memory using malloc, if malloc fails, then flush the
690  * rddir caches, and retry. If the second allocation after the readdir
691  * caches have been flushed fails too, then return NULL to indicate
692  * memory could not be allocated.
693  */
694 char *
695 auto_rddir_malloc(unsigned nbytes)
696 {
697 	char *p;
698 	int again = 0;
699 
700 	if ((p = malloc(nbytes)) == NULL) {
701 		/*
702 		 * No memory, free rddir caches and try again
703 		 */
704 		mutex_lock(&cleanup_lock);
705 		cond_signal(&cleanup_start_cv);
706 		if (cond_wait(&cleanup_done_cv, &cleanup_lock)) {
707 			mutex_unlock(&cleanup_lock);
708 			syslog(LOG_ERR, "auto_rddir_malloc interrupted\n");
709 		} else {
710 			mutex_unlock(&cleanup_lock);
711 			again = 1;
712 		}
713 	}
714 
715 	if (again)
716 		p = malloc(nbytes);
717 
718 	return (p);
719 }
720 
721 /*
722  * try to strdup a string, if it fails, then flush the rddir caches,
723  * and retry. If the second strdup fails, return NULL to indicate failure.
724  */
725 char *
726 auto_rddir_strdup(const char *s1)
727 {
728 	char *s2;
729 	int again = 0;
730 
731 	if ((s2 = strdup(s1)) == NULL) {
732 		/*
733 		 * No memory, free rddir caches and try again
734 		 */
735 		mutex_lock(&cleanup_lock);
736 		cond_signal(&cleanup_start_cv);
737 		if (cond_wait(&cleanup_done_cv, &cleanup_lock)) {
738 			mutex_unlock(&cleanup_lock);
739 			syslog(LOG_ERR, "auto_rddir_strdup interrupted\n");
740 		} else {
741 			mutex_unlock(&cleanup_lock);
742 			again = 1;
743 		}
744 	}
745 
746 	if (again)
747 		s2 = strdup(s1);
748 
749 	return (s2);
750 }
751 
752 /*
753  * Returns a pointer to the entry corresponding to 'name' if found,
754  * otherwise it returns NULL.
755  */
756 struct dir_entry *
757 btree_lookup(struct dir_entry *head, char *name)
758 {
759 	register struct dir_entry *p;
760 	register int direction;
761 
762 	for (p = head; p != NULL; ) {
763 		direction = strcmp(name, p->name);
764 		if (direction == 0)
765 			return (p);
766 		if (direction > 0)
767 			p = p->right;
768 		else p = p->left;
769 	}
770 	return (NULL);
771 }
772 
773 /*
774  * Add entry to binary tree
775  * Duplicate entries are not added
776  */
777 void
778 btree_enter(struct dir_entry **head, struct dir_entry *ent)
779 {
780 	register struct dir_entry *p, *prev = NULL;
781 	register int direction;
782 
783 	ent->right = ent->left = NULL;
784 	if (*head == NULL) {
785 		*head = ent;
786 		return;
787 	}
788 
789 	for (p = *head; p != NULL; ) {
790 		prev = p;
791 		direction = strcmp(ent->name, p->name);
792 		if (direction == 0) {
793 			/*
794 			 * entry already in btree
795 			 */
796 			return;
797 		}
798 		if (direction > 0)
799 			p = p->right;
800 		else p = p->left;
801 	}
802 	assert(prev != NULL);
803 	if (direction > 0)
804 		prev->right = ent;
805 	else prev->left = ent;
806 }
807 
808 /*
809  * If entry doesn't exist already, add it to the linear list
810  * after '*last' and to the binary tree list.
811  * If '*last == NULL' then the list is walked till the end.
812  * *last is always set to the new element after successful completion.
813  * if entry already exists '*last' is only updated if not previously
814  * provided.
815  */
816 int
817 add_dir_entry(char *name, struct dir_entry **list, struct dir_entry **last)
818 {
819 	struct dir_entry *e, *l;
820 
821 	if ((*list != NULL) && (*last == NULL)) {
822 		/*
823 		 * walk the list to find last element
824 		 */
825 		for (l = *list; l != NULL; l = l->next)
826 			*last = l;
827 	}
828 
829 	if (btree_lookup(*list, name) == NULL) {
830 		/*
831 		 * not a duplicate, add it to list
832 		 */
833 		/* LINTED pointer alignment */
834 		e = (struct dir_entry *)
835 			auto_rddir_malloc(sizeof (struct dir_entry));
836 		if (e == NULL)
837 			return (ENOMEM);
838 		(void) memset((char *)e, 0, sizeof (*e));
839 		e->name = auto_rddir_strdup(name);
840 		if (e->name == NULL) {
841 			free(e);
842 			return (ENOMEM);
843 		}
844 		e->next = NULL;
845 		if (*list == NULL) {
846 			/*
847 			 * list is empty
848 			 */
849 			*list = *last = e;
850 		} else {
851 			/*
852 			 * append to end of list
853 			 */
854 			assert(*last != NULL);
855 			(*last)->next = e;
856 			*last = e;
857 		}
858 		/*
859 		 * add to binary tree
860 		 */
861 		btree_enter(list, e);
862 	}
863 	return (0);
864 }
865 
866 /*
867  * Print trace output.
868  * Like fprintf(stderr, fmt, ...) except that if "id" is nonzero, the output
869  * is preceeded by the ID of the calling thread.
870  */
871 #define	FMT_BUFSIZ 1024
872 
873 void
874 trace_prt(int id, char *fmt, ...)
875 {
876 	va_list args;
877 
878 	char buf[FMT_BUFSIZ];
879 
880 	if (id) {
881 		(void) sprintf(buf, "t%u\t%s", thr_self(), fmt);
882 		fmt = buf;
883 	}
884 	va_start(args, fmt);
885 	(void) vfprintf(stderr, fmt, args);
886 	va_end(args);
887 }
888 
889 /*
890  * Extract the isalist(5) for userland from the kernel.
891  */
892 static char *
893 isalist(void)
894 {
895 	char *buf;
896 	size_t bufsize = BUFSIZ;	/* wild guess */
897 	long ret;
898 
899 	buf = malloc(bufsize);
900 	do {
901 		ret = sysinfo(SI_ISALIST, buf, bufsize);
902 		if (ret == -1l)
903 			return (NULL);
904 		if (ret > bufsize) {
905 			bufsize = ret;
906 			buf = realloc(buf, bufsize);
907 		} else
908 			break;
909 	} while (buf != NULL);
910 
911 	return (buf);
912 }
913 
914 /*
915  * Classify isa's as to bitness of the corresponding ABIs.
916  * isa's which have no "official" system ABI are returned
917  * unrecognised i.e. zero bits.
918  */
919 static int
920 bitness(char *isaname)
921 {
922 	if (strcmp(isaname, "sparc") == 0 ||
923 	    strcmp(isaname, "i386") == 0)
924 		return (32);
925 
926 	if (strcmp(isaname, "sparcv9") == 0)
927 		return (64);
928 
929 	return (0);
930 }
931 
932 /*
933  * Find the left-most element in the isalist that matches our idea of a
934  * system ABI.
935  *
936  * On machines with only one ABI, this is usually the same as uname -p.
937  */
938 static int
939 natisa(char *buf, size_t bufsize)
940 {
941 	int bits;
942 	char *isa, *list;
943 	char *lasts;
944 
945 	if ((list = isalist()) == NULL)
946 		return (0);
947 
948 	for (isa = strtok_r(list, " ", &lasts);
949 	    isa; isa = strtok_r(0, " ", &lasts))
950 		if ((bits = bitness(isa)) != 0)
951 			break;	/* ignore "extension" architectures */
952 
953 	if (isa == 0 || bits == 0) {
954 		free(list);
955 		return (0);	/* can't figure it out :( */
956 	}
957 
958 	(void) strncpy(buf, isa, bufsize);
959 	free(list);
960 
961 	return (1);
962 }
963