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