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