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