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