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	if (CHECK_STRCPY(buf, opts, sizeof (buf))) {
335                return (retry);
336        }
337	pb = buf;
338	while ((p = (char *)strtok_r(pb, ",", &lasts)) != NULL) {
339		pb = NULL;
340		if (strncmp(p, "retry=", 6) == 0)
341			retry = atoi(p+6);
342	}
343	return (retry > 0 ? retry : 0);
344}
345
346#if 0
347/*
348 * Returns zero if "opt" is found in mnt->mnt_opts, setting
349 * *sval to whatever follows the equal sign after "opt".
350 * str_opt allocates a string long enough to store the value of
351 * "opt" plus a terminating null character and returns it as *sval.
352 * It is the responsability of the caller to deallocate *sval.
353 * *sval will be equal to NULL upon return if either "opt=" is not found,
354 * or "opt=" has no value associated with it.
355 *
356 * stropt will return -1 on error.
357 */
358int
359str_opt(struct mnttab *mnt, char *opt, char **sval)
360{
361	char *str, *comma;
362
363	/*
364	 * is "opt" in the options field?
365	 */
366	if (str = hasmntopt(mnt, opt)) {
367		str += strlen(opt);
368		if (*str++ != '=' ||
369		    (*str == ',' || *str == '\0')) {
370			syslog(LOG_ERR, "Bad option field");
371			return (-1);
372		}
373		comma = strchr(str, ',');
374		if (comma != NULL)
375			*comma = '\0';
376		*sval = strdup(str);
377		if (comma != NULL)
378			*comma = ',';
379		if (*sval == NULL)
380			return (-1);
381	} else
382		*sval = NULL;
383
384	return (0);
385}
386#endif
387
388/*
389 * Performs text expansions in the string "pline".
390 * "plineq" is the quote vector for "pline".
391 * An identifier prefixed by "$" is replaced by the
392 * corresponding environment variable string.  A "&"
393 * is replaced by the key string for the map entry.
394 *
395 * This routine will return an error status, indicating that the
396 * macro_expand failed, if *size* would be exceeded after expansion
397 * or if a variable name is bigger than MAXVARNAMELEN.
398 * This is to prevent writing past the end of pline and plineq or
399 * the end of the variable name buffer.
400 * Both pline and plineq are left untouched in such error case.
401 */
402#define MAXVARNAMELEN	64		/* maximum variable name length */
403macro_expand_status
404macro_expand(key, pline, plineq, size)
405	const char *key;
406	char *pline, *plineq;
407	int size;
408{
409	register char *p,  *q;
410	register char *bp, *bq;
411	register const char *s;
412	char buffp[LINESZ], buffq[LINESZ];
413	char namebuf[MAXVARNAMELEN+1], *pn;
414	int expand = 0;
415	struct utsname name;
416	char isaname[64];
417
418	p = pline;  q = plineq;
419	bp = buffp; bq = buffq;
420
421	while (*p) {
422		if (*p == '&' && *q == ' ') {	/* insert key */
423			/*
424			 * make sure we don't overflow buffer
425			 */
426			if ((int)((bp - buffp) + strlen(key)) < size) {
427				for (s = key; *s; s++) {
428					*bp++ = *s;
429					*bq++ = ' ';
430				}
431				expand++;
432				p++; q++;
433				continue;
434			} else {
435				/*
436				 * line too long...
437				 */
438				return (MEXPAND_LINE_TOO_LONG);
439			}
440		}
441
442		if (*p == '$' && *q == ' ') {	/* insert env var */
443			p++; q++;
444			pn = namebuf;
445			if (*p == '{') {
446				p++; q++;
447				while (*p && *p != '}') {
448					if (pn >= &namebuf[MAXVARNAMELEN])
449						return (MEXPAND_VARNAME_TOO_LONG);
450					*pn++ = *p++;
451					q++;
452				}
453				if (*p) {
454					p++; q++;
455				}
456			} else {
457				while (*p && (*p == '_' || isalnum(*p))) {
458					if (pn >= &namebuf[MAXVARNAMELEN])
459						return (MEXPAND_VARNAME_TOO_LONG);
460					*pn++ = *p++;
461					q++;
462				}
463			}
464			*pn = '\0';
465
466			s = getenv(namebuf);
467			if (!s) {
468				/* not found in env */
469				if (strcmp(namebuf, "HOST") == 0) {
470					(void) uname(&name);
471					s = name.nodename;
472				} else if (strcmp(namebuf, "OSREL") == 0) {
473					(void) uname(&name);
474					s = name.release;
475				} else if (strcmp(namebuf, "OSNAME") == 0) {
476					(void) uname(&name);
477					s = name.sysname;
478				} else if (strcmp(namebuf, "OSVERS") == 0) {
479					/*
480					 * OS X is BSD-flavored, so the OS
481					 * "version" from uname is a string
482					 * with all sorts of crud in it.
483					 *
484					 * In Solaris, this seems to be
485					 * something that indicates the
486					 * patch level of the OS.  Nothing
487					 * like that exists in OS X, so
488					 * just say "unknown".
489					 */
490					s = "unknown";
491				} else if (strcmp(namebuf, "NATISA") == 0) {
492					if (natisa(isaname, sizeof (isaname)))
493						s = isaname;
494				}
495			}
496
497			if (s) {
498				if ((int)((bp - buffp) + strlen(s)) < size) {
499					while (*s) {
500						*bp++ = *s++;
501						*bq++ = ' ';
502					}
503				} else {
504					/*
505					 * line too long...
506					 */
507					return (MEXPAND_LINE_TOO_LONG);
508				}
509			}
510			expand++;
511			continue;
512		}
513		/*
514		 * Since buffp needs to be null terminated, we need to
515		 * check that there's still room in the buffer to
516		 * place at least two more characters, *p and the
517		 * terminating null.
518		 */
519		if (bp - buffp == size - 1) {
520			/*
521			 * There was not enough room for at least two more
522			 * characters, return with an error.
523			 */
524			return (MEXPAND_LINE_TOO_LONG);
525		}
526		/*
527		 * The total number of characters so far better be less
528		 * than the size of buffer passed in.
529		 */
530		*bp++ = *p++;
531		*bq++ = *q++;
532
533	}
534	if (!expand)
535		return (MEXPAND_OK);
536	*bp = '\0';
537	*bq = '\0';
538	/*
539	 * We know buffp/buffq will fit in pline/plineq since we
540	 * processed at most size characters.
541	 */
542	(void) strcpy(pline, buffp);
543	(void) strcpy(plineq, buffq);
544
545	return (MEXPAND_OK);
546}
547
548/*
549 * Removes quotes from the string "str" and returns
550 * the quoting information in "qbuf". e.g.
551 * original str: 'the "quick brown" f\ox'
552 * unquoted str: 'the quick brown fox'
553 * and the qbuf: '    ^^^^^^^^^^^  ^ '
554 */
555void
556unquote(str, qbuf)
557	char *str, *qbuf;
558{
559	register int escaped, inquote, quoted;
560	register char *ip, *bp, *qp;
561	char buf[LINESZ];
562
563	escaped = inquote = quoted = 0;
564
565	for (ip = str, bp = buf, qp = qbuf; *ip; ip++) {
566		if (!escaped) {
567			if (*ip == '\\') {
568				escaped = 1;
569				quoted++;
570				continue;
571			} else
572			if (*ip == '"') {
573				inquote = !inquote;
574				quoted++;
575				continue;
576			}
577		}
578
579		*bp++ = *ip;
580		*qp++ = (inquote || escaped) ? '^' : ' ';
581		escaped = 0;
582	}
583	*bp = '\0';
584	*qp = '\0';
585	if (quoted)
586		(void) strcpy(str, buf);
587}
588
589/*
590 * Removes trailing spaces from string "s".
591 */
592void
593trim(s)
594	char *s;
595{
596	size_t slen;
597	char *p;
598
599	slen = strlen(s);
600	if (slen == 0)
601		return;	/* nothing to trim */
602	p = &s[slen - 1];
603
604	while (p >= s && isspace(*(uchar_t *)p))
605		*p-- = '\0';
606}
607
608/*
609 * try to allocate memory using malloc, if malloc fails, then flush the
610 * rddir caches, and retry. If the second allocation after the readdir
611 * caches have been flushed fails too, then return NULL to indicate
612 * memory could not be allocated.
613 */
614char *
615auto_rddir_malloc(unsigned nbytes)
616{
617	char *p;
618	int again = 0;
619
620	if ((p = malloc(nbytes)) == NULL) {
621		/*
622		 * No memory, free rddir caches and try again
623		 */
624		pthread_mutex_lock(&cleanup_lock);
625		pthread_cond_signal(&cleanup_start_cv);
626		if (pthread_cond_wait(&cleanup_done_cv, &cleanup_lock)) {
627			pthread_mutex_unlock(&cleanup_lock);
628			syslog(LOG_ERR, "auto_rddir_malloc interrupted\n");
629		} else {
630			pthread_mutex_unlock(&cleanup_lock);
631			again = 1;
632		}
633	}
634
635	if (again)
636		p = malloc(nbytes);
637
638	return (p);
639}
640
641/*
642 * try to strdup a string, if it fails, then flush the rddir caches,
643 * and retry. If the second strdup fails, return NULL to indicate failure.
644 */
645char *
646auto_rddir_strdup(const char *s1)
647{
648	char *s2;
649	int again = 0;
650
651	if ((s2 = strdup(s1)) == NULL) {
652		/*
653		 * No memory, free rddir caches and try again
654		 */
655		pthread_mutex_lock(&cleanup_lock);
656		pthread_cond_signal(&cleanup_start_cv);
657		if (pthread_cond_wait(&cleanup_done_cv, &cleanup_lock)) {
658			pthread_mutex_unlock(&cleanup_lock);
659			syslog(LOG_ERR, "auto_rddir_strdup interrupted\n");
660		} else {
661			pthread_mutex_unlock(&cleanup_lock);
662			again = 1;
663		}
664	}
665
666	if (again)
667		s2 = strdup(s1);
668
669	return (s2);
670}
671
672/*
673 * Returns a pointer to the entry corresponding to 'name' if found,
674 * otherwise it returns NULL.
675 */
676struct dir_entry *
677btree_lookup(struct dir_entry *head, const char *name)
678{
679	register struct dir_entry *p;
680	register int direction;
681
682	for (p = head; p != NULL; ) {
683		direction = strcmp(name, p->name);
684		if (direction == 0)
685			return (p);
686		if (direction > 0)
687			p = p->right;
688		else p = p->left;
689	}
690	return (NULL);
691}
692
693/*
694 * Add entry to binary tree
695 * Duplicate entries are not added
696 */
697void
698btree_enter(struct dir_entry **head, struct dir_entry *ent)
699{
700	register struct dir_entry *p, *prev = NULL;
701	register int direction;
702
703	ent->right = ent->left = NULL;
704	if (*head == NULL) {
705		*head = ent;
706		return;
707	}
708
709	for (p = *head; p != NULL; ) {
710		prev = p;
711		direction = strcmp(ent->name, p->name);
712		if (direction == 0) {
713			/*
714			 * entry already in btree
715			 */
716			return;
717		}
718		if (direction > 0)
719			p = p->right;
720		else p = p->left;
721	}
722	assert(prev != NULL);
723	if (direction > 0)
724		prev->right = ent;
725	else prev->left = ent;
726}
727
728/*
729 * If entry doesn't exist already, add it to the linear list
730 * after '*last' and to the binary tree list.
731 * If '*last == NULL' then the list is walked till the end.
732 * *last is always set to the new element after successful completion.
733 * if entry already exists '*last' is only updated if not previously
734 * provided.
735 *
736 * Returns 0 on success, -1 if the name isn't valid (".", "..", or
737 * contains "/"), an errno value on error.
738 */
739int
740add_dir_entry(const char *name, const char *linebuf, const char *lineqbuf,
741    struct dir_entry **list, struct dir_entry **last)
742{
743	struct dir_entry *e, *l;
744	const char *p;
745
746	if (name[0] == '.') {
747		if (name[1] == '\0')
748			return (-1);	/* "." */
749		if (name[1] == '.' && name[2] == '\0')
750			return (-1);	/* ".." */
751	}
752	for (p = name; *p != '\0'; p++) {
753		if (*p == '/')
754			return (-1);
755	}
756
757	if ((*list != NULL) && (*last == NULL)) {
758		/*
759		 * walk the list to find last element
760		 */
761		for (l = *list; l != NULL; l = l->next)
762			*last = l;
763	}
764
765	if (btree_lookup(*list, name) == NULL) {
766		/*
767		 * not a duplicate, add it to list
768		 */
769		/* LINTED pointer alignment */
770		e = (struct dir_entry *)
771			auto_rddir_malloc(sizeof (struct dir_entry));
772		if (e == NULL)
773			return (ENOMEM);
774		(void) memset((char *)e, 0, sizeof (*e));
775		e->name = auto_rddir_strdup(name);
776		if (e->name == NULL) {
777			free(e);
778			return (ENOMEM);
779		}
780		if (linebuf != NULL) {
781			/*
782			 * If linebuf != NULL, lineqbuf must != NULL
783			 * as well.
784			 */
785			e->line = auto_rddir_strdup(linebuf);
786			if (e->line == NULL) {
787				free(e->name);
788				free(e);
789				return (ENOMEM);
790			}
791			e->lineq = auto_rddir_strdup(lineqbuf);
792			if (e->lineq == NULL) {
793				free(e->line);
794				free(e->name);
795				free(e);
796				return (ENOMEM);
797			}
798		} else {
799			e->line = NULL;
800			e->lineq = NULL;
801		}
802		e->next = NULL;
803		if (*list == NULL) {
804			/*
805			 * list is empty
806			 */
807			*list = *last = e;
808		} else {
809			/*
810			 * append to end of list
811			 */
812			assert(*last != NULL);
813			(*last)->next = e;
814			*last = e;
815		}
816		/*
817		 * add to binary tree
818		 */
819		btree_enter(list, e);
820	}
821	return (0);
822}
823
824/*
825 * Log trace output.
826 */
827void
828trace_prt(__unused int newmsg, char *fmt, ...)
829{
830	va_list args;
831        static dispatch_once_t pred;
832
833        dispatch_once(&pred, ^{
834		/*
835		 * Send a message to the syslog that turns
836		 * off the messages-per-second limit.
837		 */
838		aslmsg m = asl_new(ASL_TYPE_MSG);
839
840		asl_set(m, "ASLOption", "control");
841		asl_set(m, ASL_KEY_LEVEL, ASL_STRING_NOTICE);
842		asl_set(m, ASL_KEY_MSG, "= mps_limit 0");
843	 	asl_send(NULL, m);
844		asl_free(m);
845	});
846
847	va_start(args, fmt);
848	(void) vsyslog(LOG_ERR, fmt, args);
849	va_end(args);
850}
851
852/*
853 * Return the name of the highest-bitness ISA for this machine.
854 * We assume here that, as this is part of the OS, it'll be built
855 * fat enough that the ISA for which we're compiled is the ISA in
856 * question.  We also assume (correctly, as of the current version
857 * of our compiler) that, when building for x86-64, __x86_64__ is
858 * defined and __i386__ isn't, and we assume (correctly) that we
859 * aren't supporting 64-bit PowerPC any more.
860 */
861static int
862natisa(char *buf, size_t bufsize)
863{
864#if defined(__ppc__)
865	(void) strlcpy(buf, "powerpc", bufsize);
866#elif defined(__i386__)
867	(void) strlcpy(buf, "i386", bufsize);
868#elif defined(__x86_64__)
869	(void) strlcpy(buf, "x86_64", bufsize);
870#else
871#error "can't determine native ISA"
872#endif
873	return (1);
874}
875