opts.c revision 131702
1/* 2 * Copyright (c) 1997-2004 Erez Zadok 3 * Copyright (c) 1989 Jan-Simon Pendry 4 * Copyright (c) 1989 Imperial College of Science, Technology & Medicine 5 * Copyright (c) 1989 The Regents of the University of California. 6 * All rights reserved. 7 * 8 * This code is derived from software contributed to Berkeley by 9 * Jan-Simon Pendry at Imperial College, London. 10 * 11 * Redistribution and use in source and binary forms, with or without 12 * modification, are permitted provided that the following conditions 13 * are met: 14 * 1. Redistributions of source code must retain the above copyright 15 * notice, this list of conditions and the following disclaimer. 16 * 2. Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in the 18 * documentation and/or other materials provided with the distribution. 19 * 3. All advertising materials mentioning features or use of this software 20 * must display the following acknowledgment: 21 * This product includes software developed by the University of 22 * California, Berkeley and its contributors. 23 * 4. Neither the name of the University nor the names of its contributors 24 * may be used to endorse or promote products derived from this software 25 * without specific prior written permission. 26 * 27 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 30 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 37 * SUCH DAMAGE. 38 * 39 * %W% (Berkeley) %G% 40 * 41 * $Id: opts.c,v 1.8.2.7 2004/01/06 03:15:16 ezk Exp $ 42 * 43 */ 44 45#ifdef HAVE_CONFIG_H 46# include <config.h> 47#endif /* HAVE_CONFIG_H */ 48#include <am_defs.h> 49#include <amd.h> 50 51/* 52 * MACROS: 53 */ 54#define NLEN 16 /* Length of longest option name (conservative) */ 55#define S(x) (x) , (sizeof(x)-1) 56/* 57 * The BUFSPACE macros checks that there is enough space 58 * left in the expansion buffer. If there isn't then we 59 * give up completely. This is done to avoid crashing the 60 * automounter itself (which would be a bad thing to do). 61 */ 62#define BUFSPACE(ep, len) (((ep) + (len)) < expbuf+MAXPATHLEN) 63 64/* 65 * TYPEDEFS: 66 */ 67typedef int (*IntFuncPtr) (char *); 68typedef struct opt_apply opt_apply; 69enum vs_opt { SelEQ, SelNE, VarAss }; 70 71/* 72 * STRUCTURES 73 */ 74struct opt { 75 char *name; /* Name of the option */ 76 int nlen; /* Length of option name */ 77 char **optp; /* Pointer to option value string */ 78 char **sel_p; /* Pointer to selector value string */ 79 int (*fxn_p)(char *); /* Pointer to boolean function */ 80 int case_insensitive; /* How to do selector comparisons */ 81}; 82 83struct opt_apply { 84 char **opt; 85 char *val; 86}; 87 88struct functable { 89 char *name; 90 IntFuncPtr func; 91}; 92 93/* 94 * FORWARD DEFINITION: 95 */ 96static int f_in_network(char *); 97static int f_netgrp(char *); 98static int f_netgrpd(char *); 99static int f_exists(char *); 100static int f_false(char *); 101static int f_true(char *); 102 103/* 104 * STATICS: 105 */ 106static char NullStr[] = "<NULL>"; 107static char nullstr[] = ""; 108static char *opt_dkey = NullStr; 109static char *opt_host = nullstr; /* XXX: was the global hostname */ 110static char *opt_hostd = hostd; 111static char *opt_key = nullstr; 112static char *opt_keyd = nullstr; 113static char *opt_map = nullstr; 114static char *opt_path = nullstr; 115static char uid_str[12], gid_str[12]; 116char *opt_uid = uid_str; 117char *opt_gid = gid_str; 118static char *vars[8]; 119static char *literal_dollar = "$"; /* ${dollar}: a literal '$' in maps */ 120 121/* 122 * GLOBALS 123 */ 124struct am_opts fs_static; /* copy of the options to play with */ 125 126 127/* 128 * Options in something corresponding to frequency of use so that 129 * first-match algorithm is sped up. 130 */ 131static struct opt opt_fields[] = { 132 /* Name and length. 133 Option str. Selector str. boolean fxn. case sensitive */ 134 { S("opts"), 135 &fs_static.opt_opts, 0, 0, FALSE }, 136 { S("host"), 137 0, &opt_host, 0, TRUE }, 138 { S("hostd"), 139 0, &opt_hostd, 0, TRUE }, 140 { S("type"), 141 &fs_static.opt_type, 0, 0, FALSE }, 142 { S("rhost"), 143 &fs_static.opt_rhost, 0, 0, TRUE }, 144 { S("rfs"), 145 &fs_static.opt_rfs, 0, 0, FALSE }, 146 { S("fs"), 147 &fs_static.opt_fs, 0, 0, FALSE }, 148 { S("key"), 149 0, &opt_key, 0, FALSE }, 150 { S("map"), 151 0, &opt_map, 0, FALSE }, 152 { S("sublink"), 153 &fs_static.opt_sublink, 0, 0, FALSE }, 154 { S("arch"), 155 0, &gopt.arch, 0, TRUE }, 156 { S("dev"), 157 &fs_static.opt_dev, 0, 0, FALSE }, 158 { S("pref"), 159 &fs_static.opt_pref, 0, 0, FALSE }, 160 { S("path"), 161 0, &opt_path, 0, FALSE }, 162 { S("autodir"), 163 0, &gopt.auto_dir, 0, FALSE }, 164 { S("delay"), 165 &fs_static.opt_delay, 0, 0, FALSE }, 166 { S("domain"), 167 0, &hostdomain, 0, TRUE }, 168 { S("karch"), 169 0, &gopt.karch, 0, TRUE }, 170 { S("cluster"), 171 0, &gopt.cluster, 0, TRUE }, 172 { S("wire"), 173 0, 0, f_in_network, TRUE }, 174 { S("network"), 175 0, 0, f_in_network, TRUE }, 176 { S("netnumber"), 177 0, 0, f_in_network, TRUE }, 178 { S("byte"), 179 0, &endian, 0, TRUE }, 180 { S("os"), 181 0, &gopt.op_sys, 0, TRUE }, 182 { S("osver"), 183 0, &gopt.op_sys_ver, 0, TRUE }, 184 { S("full_os"), 185 0, &gopt.op_sys_full, 0, TRUE }, 186 { S("vendor"), 187 0, &gopt.op_sys_vendor, 0, TRUE }, 188 { S("remopts"), 189 &fs_static.opt_remopts, 0, 0, FALSE }, 190 { S("mount"), 191 &fs_static.opt_mount, 0, 0, FALSE }, 192 { S("unmount"), 193 &fs_static.opt_unmount, 0, 0, FALSE }, 194 { S("cache"), 195 &fs_static.opt_cache, 0, 0, FALSE }, 196 { S("user"), 197 &fs_static.opt_user, 0, 0, FALSE }, 198 { S("group"), 199 &fs_static.opt_group, 0, 0, FALSE }, 200 { S(".key"), 201 0, &opt_dkey, 0, FALSE }, 202 { S("key."), 203 0, &opt_keyd, 0, FALSE }, 204 /* XXX: should maptype really be a variable? I think selector. -Erez */ 205 { S("maptype"), 206 &fs_static.opt_maptype, 0, 0, FALSE }, 207 { S("cachedir"), 208 &fs_static.opt_cachedir, 0, 0, FALSE }, 209 { S("addopts"), 210 &fs_static.opt_addopts, 0, 0, FALSE }, 211 { S("uid"), 212 0, &opt_uid, 0, FALSE }, 213 { S("gid"), 214 0, &opt_gid, 0, FALSE }, 215 { S("dollar"), 216 &literal_dollar, 0, 0, FALSE }, 217 { S("var0"), 218 &vars[0], 0, 0, FALSE }, 219 { S("var1"), 220 &vars[1], 0, 0, FALSE }, 221 { S("var2"), 222 &vars[2], 0, 0, FALSE }, 223 { S("var3"), 224 &vars[3], 0, 0, FALSE }, 225 { S("var4"), 226 &vars[4], 0, 0, FALSE }, 227 { S("var5"), 228 &vars[5], 0, 0, FALSE }, 229 { S("var6"), 230 &vars[6], 0, 0, FALSE }, 231 { S("var7"), 232 &vars[7], 0, 0, FALSE }, 233 { 0, 0, 0, 0, 0, FALSE }, 234}; 235 236static struct functable functable[] = { 237 { "in_network", f_in_network }, 238 { "netgrp", f_netgrp }, 239 { "netgrpd", f_netgrpd }, 240 { "exists", f_exists }, 241 { "false", f_false }, 242 { "true", f_true }, 243 { 0, 0 }, 244}; 245 246/* 247 * Specially expand the remote host name first 248 */ 249static opt_apply rhost_expansion[] = 250{ 251 {&fs_static.opt_rhost, "${host}"}, 252 {0, 0}, 253}; 254 255/* 256 * List of options which need to be expanded 257 * Note that the order here _may_ be important. 258 */ 259static opt_apply expansions[] = 260{ 261 {&fs_static.opt_sublink, 0}, 262 {&fs_static.opt_rfs, "${path}"}, 263 {&fs_static.opt_fs, "${autodir}/${rhost}${rfs}"}, 264 {&fs_static.opt_opts, "rw"}, 265 {&fs_static.opt_remopts, "${opts}"}, 266 {&fs_static.opt_mount, 0}, 267 {&fs_static.opt_unmount, 0}, 268 {&fs_static.opt_cachedir, 0}, 269 {&fs_static.opt_addopts, 0}, 270 {0, 0}, 271}; 272 273/* 274 * List of options which need to be free'ed before re-use 275 */ 276static opt_apply to_free[] = 277{ 278 {&fs_static.fs_glob, 0}, 279 {&fs_static.fs_local, 0}, 280 {&fs_static.fs_mtab, 0}, 281 {&fs_static.opt_sublink, 0}, 282 {&fs_static.opt_rfs, 0}, 283 {&fs_static.opt_fs, 0}, 284 {&fs_static.opt_rhost, 0}, 285 {&fs_static.opt_opts, 0}, 286 {&fs_static.opt_remopts, 0}, 287 {&fs_static.opt_mount, 0}, 288 {&fs_static.opt_unmount, 0}, 289 {&fs_static.opt_cachedir, 0}, 290 {&fs_static.opt_addopts, 0}, 291 {&vars[0], 0}, 292 {&vars[1], 0}, 293 {&vars[2], 0}, 294 {&vars[3], 0}, 295 {&vars[4], 0}, 296 {&vars[5], 0}, 297 {&vars[6], 0}, 298 {&vars[7], 0}, 299 {0, 0}, 300}; 301 302 303/* 304 * expand backslash escape sequences 305 */ 306static char 307backslash(char **p) 308{ 309 char c; 310 311 if ((*p)[1] == '\0') { 312 plog(XLOG_USER, "Empty backslash escape"); 313 return **p; 314 } 315 316 if (**p == '\\') { 317 (*p)++; 318 switch (**p) { 319 case 'g': 320 c = '\007'; /* Bell */ 321 break; 322 case 'b': 323 c = '\010'; /* Backspace */ 324 break; 325 case 't': 326 c = '\011'; /* Horizontal Tab */ 327 break; 328 case 'n': 329 c = '\012'; /* New Line */ 330 break; 331 case 'v': 332 c = '\013'; /* Vertical Tab */ 333 break; 334 case 'f': 335 c = '\014'; /* Form Feed */ 336 break; 337 case 'r': 338 c = '\015'; /* Carriage Return */ 339 break; 340 case 'e': 341 c = '\033'; /* Escape */ 342 break; 343 case '0': 344 case '1': 345 case '2': 346 case '3': 347 case '4': 348 case '5': 349 case '6': 350 case '7': 351 { 352 int cnt, val, ch; 353 354 for (cnt = 0, val = 0; cnt < 3; cnt++) { 355 ch = *(*p)++; 356 if (ch < '0' || ch > '7') { 357 (*p)--; 358 break; 359 } 360 val = (val << 3) | (ch - '0'); 361 } 362 363 if ((val & 0xffffff00) != 0) 364 plog(XLOG_USER, 365 "Too large character constant %u\n", 366 val); 367 c = (char) val; 368 --(*p); 369 } 370 break; 371 372 default: 373 c = **p; 374 break; 375 } 376 } else 377 c = **p; 378 379 return c; 380} 381 382 383/* 384 * Skip to next option in the string 385 */ 386static char * 387opt(char **p) 388{ 389 char *cp = *p; 390 char *dp = cp; 391 char *s = cp; 392 393top: 394 while (*cp && *cp != ';') { 395 if (*cp == '"') { 396 /* 397 * Skip past string 398 */ 399 for (cp++; *cp && *cp != '"'; cp++) 400 if (*cp == '\\') 401 *dp++ = backslash(&cp); 402 else 403 *dp++ = *cp; 404 if (*cp) 405 cp++; 406 } else { 407 *dp++ = *cp++; 408 } 409 } 410 411 /* 412 * Skip past any remaining ';'s 413 */ 414 while (*cp == ';') 415 cp++; 416 417 /* 418 * If we have a zero length string 419 * and there are more fields, then 420 * parse the next one. This allows 421 * sequences of empty fields. 422 */ 423 if (*cp && dp == s) 424 goto top; 425 426 *dp = '\0'; 427 428 *p = cp; 429 return s; 430} 431 432 433/* 434 * These routines add a new style of selector; function-style boolean 435 * operators. To add new ones, just define functions as in true, false, 436 * exists (below) and add them to the functable, above. 437 * 438 * Usage example: Some people have X11R5 local, some go to a server. I do 439 * this: 440 * 441 * * exists(/usr/pkg/${key});type:=link;fs:=/usr/pkg/${key} || \ 442 * -type:=nfs;rfs=/usr/pkg/${key} \ 443 * rhost:=server1 \ 444 * rhost:=server2 445 * 446 * -Rens Troost <rens@imsi.com> 447 */ 448static IntFuncPtr 449functable_lookup(char *key) 450{ 451 struct functable *fp; 452 453 for (fp = functable; fp->name; fp++) 454 if (FSTREQ(fp->name, key)) 455 return (fp->func); 456 return (IntFuncPtr) NULL; 457} 458 459 460static int 461eval_opts(char *opts, char *mapkey) 462{ 463 /* 464 * Fill in the global structure fs_static by 465 * cracking the string opts. opts may be 466 * scribbled on at will. 467 */ 468 char *o = opts; 469 char *f; 470 471 /* 472 * For each user-specified option 473 */ 474 while (*(f = opt(&o))) { 475 struct opt *op; 476 enum vs_opt vs_opt = VarAss; 477 char *eq = strchr(f, '='); 478 char *fx; 479 IntFuncPtr func; 480 char *opt = NULL; 481 482 if (!eq || eq[1] == '\0' || eq == f) { 483 /* 484 * No value, is it a function call? 485 */ 486 char *arg = strchr(f, '('); 487 488 if (!arg || arg[1] == '\0' || arg == f) { 489 /* 490 * No, just continue 491 */ 492 plog(XLOG_USER, "key %s: No value component in \"%s\"", mapkey, f); 493 continue; 494 } 495 496 /* null-terminate the argument */ 497 *arg++ = '\0'; 498 fx = strchr(arg, ')'); 499 if (!arg || fx == arg) { 500 plog(XLOG_USER, "key %s: Malformed function in \"%s\"", mapkey, f); 501 continue; 502 } 503 *fx = '\0'; 504 505 /* 506 * look up f in functable and pass it arg. 507 * func must return 0 on failure, and 1 on success. 508 */ 509 if ((func = functable_lookup(f))) { 510 if (!(*func) (arg)) { 511 return (0); 512 } 513 continue; 514 } else if (NSTREQ(f, "!", 1) && (func = functable_lookup(&f[1]))) { 515 /* then this is a negated prefixed function such as "!exists" */ 516 plog(XLOG_INFO, "executing negated function %s", &f[1]); 517 if ((*func) (arg)) { 518 return (0); 519 } 520 continue; 521 } else { 522 plog(XLOG_USER, "key %s: unknown function \"%s\"", mapkey, f); 523 return (0); 524 } 525 526 } 527 528 /* 529 * Check what type of operation is happening 530 * !=, =! is SelNE 531 * == is SelEQ 532 * := is VarAss 533 */ 534 if (eq[-1] == '!') { /* != */ 535 vs_opt = SelNE; 536 eq[-1] = '\0'; 537 opt = eq + 1; 538 } else if (eq[-1] == ':') { /* := */ 539 vs_opt = VarAss; 540 eq[-1] = '\0'; 541 opt = eq + 1; 542 } else if (eq[1] == '=') { /* == */ 543 vs_opt = SelEQ; 544 eq[0] = '\0'; 545 opt = eq + 2; 546 } else if (eq[1] == '!') { /* =! */ 547 vs_opt = SelNE; 548 eq[0] = '\0'; 549 opt = eq + 2; 550 } 551 552 /* 553 * For each recognized option 554 */ 555 for (op = opt_fields; op->name; op++) { 556 /* 557 * Check whether they match 558 */ 559 if (FSTREQ(op->name, f)) { 560 int selok; 561 switch (vs_opt) { 562 case SelEQ: 563 case SelNE: 564 if ((selok = (op->sel_p != NULL))) { 565 if (op->case_insensitive) { 566 selok = (STRCEQ(*op->sel_p, opt) == (vs_opt == SelNE)); 567 } else { 568 selok = (STREQ(*op->sel_p, opt) == (vs_opt == SelNE)); 569 } 570 } 571 if (selok) { 572 plog(XLOG_MAP, "key %s: map selector %s (=%s) did not %smatch %s", 573 mapkey, 574 op->name, 575 *op->sel_p, 576 vs_opt == SelNE ? "mis" : "", 577 opt); 578 return 0; 579 } 580 /* check if to apply a function */ 581 if (op->fxn_p && 582 ((*op->fxn_p)(opt) == (vs_opt == SelNE))) { 583 plog(XLOG_MAP, "key %s: map function %s did not %smatch %s", 584 mapkey, 585 op->name, 586 vs_opt == SelNE ? "mis" : "", 587 opt); 588 return 0; 589 } 590 break; 591 592 case VarAss: 593 if (op->sel_p) { 594 plog(XLOG_USER, "key %s: Can't assign to a selector (%s)", 595 mapkey, op->name); 596 return 0; 597 } 598 *op->optp = opt; 599 break; 600 601 } /* end of "switch (vs_opt)" statement */ 602 break; /* break out of for loop */ 603 } 604 } 605 606 if (!op->name) 607 plog(XLOG_USER, "key %s: Unrecognized key/option \"%s\"", mapkey, f); 608 } 609 610 return 1; 611} 612 613 614/* 615 * Skip to next option in the string, but don't scribble over the string. 616 * However, *p gets repointed to the start of the next string past ';'. 617 */ 618static char * 619opt_no_scribble(char **p) 620{ 621 char *cp = *p; 622 char *dp = cp; 623 char *s = cp; 624 625top: 626 while (*cp && *cp != ';') { 627 if (*cp == '\"') { 628 /* 629 * Skip past string 630 */ 631 cp++; 632 while (*cp && *cp != '\"') 633 *dp++ = *cp++; 634 if (*cp) 635 cp++; 636 } else { 637 *dp++ = *cp++; 638 } 639 } 640 641 /* 642 * Skip past any remaining ';'s 643 */ 644 while (*cp == ';') 645 cp++; 646 647 /* 648 * If we have a zero length string 649 * and there are more fields, then 650 * parse the next one. This allows 651 * sequences of empty fields. 652 */ 653 if (*cp && dp == s) 654 goto top; 655 656 *p = cp; 657 return s; 658} 659 660 661/* 662 * Strip any selectors from a string. Selectors are all assumed to be 663 * first in the string. This is used for the new /defaults method which will 664 * use selectors as well. 665 */ 666char * 667strip_selectors(char *opts, char *mapkey) 668{ 669 /* 670 * Fill in the global structure fs_static by 671 * cracking the string opts. opts may be 672 * scribbled on at will. 673 */ 674 char *o = opts; 675 char *oo = opts; 676 char *f; 677 678 /* 679 * Scan options. Note that the opt() function scribbles on the opt string. 680 */ 681 while (*(f = opt_no_scribble(&o))) { 682 enum vs_opt vs_opt = VarAss; 683 char *eq = strchr(f, '='); 684 685 if (!eq || eq[1] == '\0' || eq == f) { 686 /* 687 * No option or assignment? Return as is. 688 */ 689 plog(XLOG_USER, "key %s: No option or assignment in \"%s\"", mapkey, f); 690 return o; 691 } 692 /* 693 * Check what type of operation is happening 694 * !=, =! is SelNE 695 * == is SelEQ 696 * := is VarAss 697 */ 698 if (eq[-1] == '!') { /* != */ 699 vs_opt = SelNE; 700 } else if (eq[-1] == ':') { /* := */ 701 vs_opt = VarAss; 702 } else if (eq[1] == '=') { /* == */ 703 vs_opt = SelEQ; 704 } else if (eq[1] == '!') { /* =! */ 705 vs_opt = SelNE; 706 } 707 switch (vs_opt) { 708 case SelEQ: 709 case SelNE: 710 /* Skip this selector, maybe there's another one following it */ 711 plog(XLOG_USER, "skipping selector to \"%s\"", o); 712 /* store previous match. it may have been the first assignment */ 713 oo = o; 714 break; 715 716 case VarAss: 717 /* found the first assignment, return the string starting with it */ 718#ifdef DEBUG 719 dlog("found first assignment past selectors \"%s\"", o); 720#endif /* DEBUG */ 721 return oo; 722 } 723 } 724 725 /* return the same string by default. should not happen. */ 726 return oo; 727} 728 729 730/***************************************************************************** 731 *** BOOLEAN FUNCTIONS (return 0 if false, 1 if true): *** 732 *****************************************************************************/ 733 734/* test if arg is any of this host's network names or numbers */ 735static int 736f_in_network(char *arg) 737{ 738 int status; 739 740 if (!arg) 741 return FALSE; 742 743 status = is_network_member(arg); 744#ifdef DEBUG 745 plog(XLOG_USER, "%s is %son a local network", 746 arg, (status ? "" : "not ")); 747#endif /* DEBUG */ 748 return status; 749} 750 751 752/* test if this host (short hostname form) is in netgroup (arg) */ 753static int 754f_netgrp(char *arg) 755{ 756 int status; 757 758 status = innetgr(arg, opt_host, NULL, NULL); 759#ifdef DEBUG 760 plog(XLOG_USER, "netgrp = %s status = %d host = %s", arg, status, opt_host); 761#endif /* DEBUG */ 762 return status; 763} 764 765 766/* test if this host (fully-qualified name) is in netgroup (arg) */ 767static int 768f_netgrpd(char *arg) 769{ 770 int status; 771 772 status = innetgr(arg, opt_hostd, NULL, NULL); 773#ifdef DEBUG 774 plog(XLOG_USER, "netgrp = %s status = %d hostd = %s", arg, status, opt_hostd); 775#endif /* DEBUG */ 776 return status; 777} 778 779 780/* test if file (arg) exists via lstat */ 781static int 782f_exists(char *arg) 783{ 784 struct stat buf; 785 786 if (lstat(arg, &buf) < 0) 787 return (0); 788 else 789 return (1); 790} 791 792 793/* always false */ 794static int 795f_false(char *arg) 796{ 797 return (0); 798} 799 800 801/* always true */ 802static int 803f_true(char *arg) 804{ 805 return (1); 806} 807 808 809/* 810 * Free an option 811 */ 812static void 813free_op(opt_apply *p, int b) 814{ 815 if (*p->opt) { 816 XFREE(*p->opt); 817 *p->opt = 0; 818 } 819} 820 821 822/* 823 * Normalize slashes in the string. 824 */ 825void 826normalize_slash(char *p) 827{ 828 char *f = strchr(p, '/'); 829 char *f0 = f; 830 831 if (f) { 832 char *t = f; 833 do { 834 /* assert(*f == '/'); */ 835 if (f == f0 && f[0] == '/' && f[1] == '/') { 836 /* copy double slash iff first */ 837 *t++ = *f++; 838 *t++ = *f++; 839 } else { 840 /* copy a single / across */ 841 *t++ = *f++; 842 } 843 844 /* assert(f[-1] == '/'); */ 845 /* skip past more /'s */ 846 while (*f == '/') 847 f++; 848 849 /* assert(*f != '/'); */ 850 /* keep copying up to next / */ 851 while (*f && *f != '/') { 852 *t++ = *f++; 853 } 854 855 /* assert(*f == 0 || *f == '/'); */ 856 857 } while (*f); 858 *t = 0; /* derived from fix by Steven Glassman */ 859 } 860} 861 862 863/* 864 * Macro-expand an option. Note that this does not 865 * handle recursive expansions. They will go badly wrong. 866 * If sel is true then old expand selectors, otherwise 867 * don't expand selectors. 868 */ 869static void 870expand_op(opt_apply *p, int sel_p) 871{ 872 static const char expand_error[] = "No space to expand \"%s\""; 873 char expbuf[MAXPATHLEN + 1]; 874 char nbuf[NLEN + 1]; 875 char *ep = expbuf; 876 char *cp = *p->opt; 877 char *dp; 878 struct opt *op; 879#ifdef DEBUG 880 char *cp_orig = *p->opt; 881#endif /* DEBUG */ 882 883 while ((dp = strchr(cp, '$'))) { 884 char ch; 885 /* 886 * First copy up to the $ 887 */ 888 { 889 int len = dp - cp; 890 891 if (BUFSPACE(ep, len)) { 892 strncpy(ep, cp, len); 893 ep += len; 894 } else { 895 plog(XLOG_ERROR, expand_error, *p->opt); 896 goto out; 897 } 898 } 899 900 cp = dp + 1; 901 ch = *cp++; 902 if (ch == '$') { 903 if (BUFSPACE(ep, 1)) { 904 *ep++ = '$'; 905 } else { 906 plog(XLOG_ERROR, expand_error, *p->opt); 907 goto out; 908 } 909 } else if (ch == '{') { 910 /* Expansion... */ 911 enum { 912 E_All, E_Dir, E_File, E_Domain, E_Host 913 } todo; 914 /* 915 * Find closing brace 916 */ 917 char *br_p = strchr(cp, '}'); 918 int len; 919 920 /* 921 * Check we found it 922 */ 923 if (!br_p) { 924 /* 925 * Just give up 926 */ 927 plog(XLOG_USER, "No closing '}' in \"%s\"", *p->opt); 928 goto out; 929 } 930 len = br_p - cp; 931 932 /* 933 * Figure out which part of the variable to grab. 934 */ 935 if (*cp == '/') { 936 /* 937 * Just take the last component 938 */ 939 todo = E_File; 940 cp++; 941 --len; 942 } else if (br_p[-1] == '/') { 943 /* 944 * Take all but the last component 945 */ 946 todo = E_Dir; 947 --len; 948 } else if (*cp == '.') { 949 /* 950 * Take domain name 951 */ 952 todo = E_Domain; 953 cp++; 954 --len; 955 } else if (br_p[-1] == '.') { 956 /* 957 * Take host name 958 */ 959 todo = E_Host; 960 --len; 961 } else { 962 /* 963 * Take the whole lot 964 */ 965 todo = E_All; 966 } 967 968 /* 969 * Truncate if too long. Since it won't 970 * match anyway it doesn't matter that 971 * it has been cut short. 972 */ 973 if (len > NLEN) 974 len = NLEN; 975 976 /* 977 * Put the string into another buffer so 978 * we can do comparisons. 979 */ 980 strncpy(nbuf, cp, len); 981 nbuf[len] = '\0'; 982 983 /* 984 * Advance cp 985 */ 986 cp = br_p + 1; 987 988 /* 989 * Search the option array 990 */ 991 for (op = opt_fields; op->name; op++) { 992 /* 993 * Check for match 994 */ 995 if (len == op->nlen && STREQ(op->name, nbuf)) { 996 char xbuf[NLEN + 3]; 997 char *val; 998 /* 999 * Found expansion. Copy 1000 * the correct value field. 1001 */ 1002 if (!(!op->sel_p == !sel_p)) { 1003 /* 1004 * Copy the string across unexpanded 1005 */ 1006 sprintf(xbuf, "${%s%s%s}", 1007 todo == E_File ? "/" : 1008 todo == E_Domain ? "." : "", 1009 nbuf, 1010 todo == E_Dir ? "/" : 1011 todo == E_Host ? "." : ""); 1012 val = xbuf; 1013 /* 1014 * Make sure expansion doesn't 1015 * munge the value! 1016 */ 1017 todo = E_All; 1018 } else if (op->sel_p) { 1019 val = *op->sel_p; 1020 } else { 1021 val = *op->optp; 1022 } 1023 1024 if (val) { 1025 /* 1026 * Do expansion: 1027 * ${/var} means take just the last part 1028 * ${var/} means take all but the last part 1029 * ${.var} means take all but first part 1030 * ${var.} means take just the first part 1031 * ${var} means take the whole lot 1032 */ 1033 int vlen = strlen(val); 1034 char *vptr = val; 1035 switch (todo) { 1036 case E_Dir: 1037 vptr = strrchr(val, '/'); 1038 if (vptr) 1039 vlen = vptr - val; 1040 vptr = val; 1041 break; 1042 case E_File: 1043 vptr = strrchr(val, '/'); 1044 if (vptr) { 1045 vptr++; 1046 vlen = strlen(vptr); 1047 } else 1048 vptr = val; 1049 break; 1050 case E_Domain: 1051 vptr = strchr(val, '.'); 1052 if (vptr) { 1053 vptr++; 1054 vlen = strlen(vptr); 1055 } else { 1056 vptr = ""; 1057 vlen = 0; 1058 } 1059 break; 1060 case E_Host: 1061 vptr = strchr(val, '.'); 1062 if (vptr) 1063 vlen = vptr - val; 1064 vptr = val; 1065 break; 1066 case E_All: 1067 break; 1068 } 1069 1070 if (BUFSPACE(ep, vlen)) { 1071 strcpy(ep, vptr); 1072 ep += vlen; 1073 } else { 1074 plog(XLOG_ERROR, expand_error, *p->opt); 1075 goto out; 1076 } 1077 } 1078 /* 1079 * Done with this variable 1080 */ 1081 break; 1082 } 1083 } 1084 1085 /* 1086 * Check that the search was successful 1087 */ 1088 if (!op->name) { 1089 /* 1090 * If it wasn't then scan the 1091 * environment for that name 1092 * and use any value found 1093 */ 1094 char *env = getenv(nbuf); 1095 1096 if (env) { 1097 int vlen = strlen(env); 1098 1099 if (BUFSPACE(ep, vlen)) { 1100 strcpy(ep, env); 1101 ep += vlen; 1102 } else { 1103 plog(XLOG_ERROR, expand_error, *p->opt); 1104 goto out; 1105 } 1106#ifdef DEBUG 1107 amuDebug(D_STR) 1108 plog(XLOG_DEBUG, "Environment gave \"%s\" -> \"%s\"", nbuf, env); 1109#endif /* DEBUG */ 1110 } else { 1111 plog(XLOG_USER, "Unknown sequence \"${%s}\"", nbuf); 1112 } 1113 } 1114 } else { 1115 /* 1116 * Error, error 1117 */ 1118 plog(XLOG_USER, "Unknown $ sequence in \"%s\"", *p->opt); 1119 } 1120 } 1121 1122out: 1123 /* 1124 * Handle common case - no expansion 1125 */ 1126 if (cp == *p->opt) { 1127 *p->opt = strdup(cp); 1128 } else { 1129 /* 1130 * Finish off the expansion 1131 */ 1132 if (BUFSPACE(ep, strlen(cp))) { 1133 strcpy(ep, cp); 1134 /* ep += strlen(ep); */ 1135 } else { 1136 plog(XLOG_ERROR, expand_error, *p->opt); 1137 } 1138 1139 /* 1140 * Save the expansion 1141 */ 1142 *p->opt = strdup(expbuf); 1143 } 1144 1145 normalize_slash(*p->opt); 1146 1147#ifdef DEBUG 1148 amuDebug(D_STR) { 1149 plog(XLOG_DEBUG, "Expansion of \"%s\"...", cp_orig); 1150 plog(XLOG_DEBUG, "... is \"%s\"", *p->opt); 1151 } 1152#endif /* DEBUG */ 1153} 1154 1155 1156/* 1157 * Wrapper for expand_op 1158 */ 1159static void 1160expand_opts(opt_apply *p, int sel_p) 1161{ 1162 if (*p->opt) { 1163 expand_op(p, sel_p); 1164 } else if (p->val) { 1165 /* 1166 * Do double expansion, remembering 1167 * to free the string from the first 1168 * expansion... 1169 */ 1170 char *s = *p->opt = expand_key(p->val); 1171 expand_op(p, sel_p); 1172 XFREE(s); 1173 } 1174} 1175 1176 1177/* 1178 * Apply a function to a list of options 1179 */ 1180static void 1181apply_opts(void (*op) (opt_apply *, int), opt_apply ppp[], int b) 1182{ 1183 opt_apply *pp; 1184 1185 for (pp = ppp; pp->opt; pp++) 1186 (*op) (pp, b); 1187} 1188 1189 1190/* 1191 * Free the option table 1192 */ 1193void 1194free_opts(am_opts *fo) 1195{ 1196 /* 1197 * Copy in the structure we are playing with 1198 */ 1199 fs_static = *fo; 1200 1201 /* 1202 * Free previously allocated memory 1203 */ 1204 apply_opts(free_op, to_free, FALSE); 1205} 1206 1207 1208/* 1209 * Expand lookup key 1210 */ 1211char * 1212expand_key(char *key) 1213{ 1214 opt_apply oa; 1215 1216 oa.opt = &key; 1217 oa.val = 0; 1218 expand_opts(&oa, TRUE); 1219 1220 return key; 1221} 1222 1223 1224/* 1225 * Remove trailing /'s from a string 1226 * unless the string is a single / (Steven Glassman) 1227 * or unless it is two slashes // (Kevin D. Bond) 1228 */ 1229void 1230deslashify(char *s) 1231{ 1232 if (s && *s) { 1233 char *sl = s + strlen(s); 1234 1235 while (*--sl == '/' && sl > s) 1236 *sl = '\0'; 1237 } 1238} 1239 1240 1241int 1242eval_fs_opts(am_opts *fo, char *opts, char *g_opts, char *path, char *key, char *map) 1243{ 1244 int ok = TRUE; 1245 1246 free_opts(fo); 1247 1248 /* 1249 * Clear out the option table 1250 */ 1251 memset((voidp) &fs_static, 0, sizeof(fs_static)); 1252 memset((voidp) vars, 0, sizeof(vars)); 1253 memset((voidp) fo, 0, sizeof(*fo)); 1254 1255 /* set hostname */ 1256 opt_host = (char *) am_get_hostname(); 1257 1258 /* 1259 * Set key, map & path before expansion 1260 */ 1261 opt_key = key; 1262 opt_map = map; 1263 opt_path = path; 1264 1265 opt_dkey = strchr(key, '.'); 1266 if (!opt_dkey) { 1267 opt_dkey = NullStr; 1268 opt_keyd = key; 1269 } else { 1270 opt_keyd = strnsave(key, opt_dkey - key); 1271 opt_dkey++; 1272 if (*opt_dkey == '\0') /* check for 'host.' */ 1273 opt_dkey = NullStr; 1274 } 1275 1276 /* 1277 * Expand global options 1278 */ 1279 fs_static.fs_glob = expand_key(g_opts); 1280 1281 /* 1282 * Expand local options 1283 */ 1284 fs_static.fs_local = expand_key(opts); 1285 1286 /* 1287 * Expand default (global) options 1288 */ 1289 if (!eval_opts(fs_static.fs_glob, key)) 1290 ok = FALSE; 1291 1292 /* 1293 * Expand local options 1294 */ 1295 if (ok && !eval_opts(fs_static.fs_local, key)) 1296 ok = FALSE; 1297 1298 /* 1299 * Normalize remote host name. 1300 * 1. Expand variables 1301 * 2. Normalize relative to host tables 1302 * 3. Strip local domains from the remote host 1303 * name before using it in other expansions. 1304 * This makes mount point names and other things 1305 * much shorter, while allowing cross domain 1306 * sharing of mount maps. 1307 */ 1308 apply_opts(expand_opts, rhost_expansion, FALSE); 1309 if (ok && fs_static.opt_rhost && *fs_static.opt_rhost) 1310 host_normalize(&fs_static.opt_rhost); 1311 1312 /* 1313 * Macro expand the options. 1314 * Do this regardless of whether we are accepting 1315 * this mount - otherwise nasty things happen 1316 * with memory allocation. 1317 */ 1318 apply_opts(expand_opts, expansions, FALSE); 1319 1320 /* 1321 * Strip trailing slashes from local pathname... 1322 */ 1323 deslashify(fs_static.opt_fs); 1324 1325 /* 1326 * ok... copy the data back out. 1327 */ 1328 *fo = fs_static; 1329 1330 /* 1331 * Clear defined options 1332 */ 1333 if (opt_keyd != key && opt_keyd != nullstr) 1334 XFREE(opt_keyd); 1335 opt_keyd = nullstr; 1336 opt_dkey = NullStr; 1337 opt_key = opt_map = opt_path = nullstr; 1338 1339 return ok; 1340} 1341