1 /* 2 * Copyright (c) 1989 Jan-Simon Pendry 3 * Copyright (c) 1989 Imperial College of Science, Technology & Medicine 4 * Copyright (c) 1989 The Regents of the University of California. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to Berkeley by 8 * Jan-Simon Pendry at Imperial College, London. 9 * 10 * %sccs.include.redist.c% 11 * 12 * @(#)opts.c 1.3 (Berkeley) 6/26/91 13 * 14 * $Id: opts.c,v 5.2.2.3 1992/05/31 16:34:13 jsp Exp $ 15 * 16 */ 17 18 #include "am.h" 19 20 extern char *getenv P((const char *)); 21 22 /* 23 * static copy of the options with 24 * which to play 25 */ 26 static struct am_opts fs_static; 27 28 static char *opt_host = hostname; 29 static char *opt_hostd = hostd; 30 static char nullstr[] = ""; 31 static char *opt_key = nullstr; 32 static char *opt_map = nullstr; 33 static char *opt_path = nullstr; 34 35 static char *vars[8]; 36 37 /* 38 * Length of longest option name 39 */ 40 #define NLEN 16 /* conservative */ 41 #define S(x) (x) , (sizeof(x)-1) 42 static struct opt { 43 char *name; /* Name of the option */ 44 int nlen; /* Length of option name */ 45 char **optp; /* Pointer to option value string */ 46 char **sel_p; /* Pointer to selector value string */ 47 } opt_fields[] = { 48 /* Options in something corresponding to frequency of use */ 49 { S("opts"), &fs_static.opt_opts, 0 }, 50 { S("host"), 0, &opt_host }, 51 { S("hostd"), 0, &opt_hostd }, 52 { S("type"), &fs_static.opt_type, 0 }, 53 { S("rhost"), &fs_static.opt_rhost, 0 }, 54 { S("rfs"), &fs_static.opt_rfs, 0 }, 55 { S("fs"), &fs_static.opt_fs, 0 }, 56 { S("key"), 0, &opt_key }, 57 { S("map"), 0, &opt_map }, 58 { S("sublink"), &fs_static.opt_sublink, 0 }, 59 { S("arch"), 0, &arch }, 60 { S("dev"), &fs_static.opt_dev, 0 }, 61 { S("pref"), &fs_static.opt_pref, 0 }, 62 { S("path"), 0, &opt_path }, 63 { S("autodir"), 0, &auto_dir }, 64 { S("delay"), &fs_static.opt_delay, 0 }, 65 { S("domain"), 0, &hostdomain }, 66 { S("karch"), 0, &karch }, 67 { S("cluster"), 0, &cluster }, 68 { S("wire"), 0, &wire }, 69 { S("byte"), 0, &endian }, 70 { S("os"), 0, &op_sys }, 71 { S("remopts"), &fs_static.opt_remopts, 0 }, 72 { S("mount"), &fs_static.opt_mount, 0 }, 73 { S("unmount"), &fs_static.opt_unmount, 0 }, 74 { S("cache"), &fs_static.opt_cache, 0 }, 75 { S("user"), &fs_static.opt_user, 0 }, 76 { S("group"), &fs_static.opt_group, 0 }, 77 { S("var0"), &vars[0], 0 }, 78 { S("var1"), &vars[1], 0 }, 79 { S("var2"), &vars[2], 0 }, 80 { S("var3"), &vars[3], 0 }, 81 { S("var4"), &vars[4], 0 }, 82 { S("var5"), &vars[5], 0 }, 83 { S("var6"), &vars[6], 0 }, 84 { S("var7"), &vars[7], 0 }, 85 { 0, 0, 0, 0 }, 86 }; 87 88 typedef struct opt_apply opt_apply; 89 struct opt_apply { 90 char **opt; 91 char *val; 92 }; 93 94 /* 95 * Specially expand the remote host name first 96 */ 97 static opt_apply rhost_expansion[] = { 98 { &fs_static.opt_rhost, "${host}" }, 99 { 0, 0 }, 100 }; 101 /* 102 * List of options which need to be expanded 103 * Note that this the order here _may_ be important. 104 */ 105 static opt_apply expansions[] = { 106 /* { &fs_static.opt_dir, 0 }, */ 107 { &fs_static.opt_sublink, 0 }, 108 { &fs_static.opt_rfs, "${path}" }, 109 { &fs_static.opt_fs, "${autodir}/${rhost}${rfs}" }, 110 { &fs_static.opt_opts, "rw" }, 111 { &fs_static.opt_remopts, "${opts}" }, 112 { &fs_static.opt_mount, 0 }, 113 { &fs_static.opt_unmount, 0 }, 114 { 0, 0 }, 115 }; 116 117 /* 118 * List of options which need to be free'ed before re-use 119 */ 120 static opt_apply to_free[] = { 121 { &fs_static.fs_glob, 0 }, 122 { &fs_static.fs_local, 0 }, 123 { &fs_static.fs_mtab, 0 }, 124 /* { &fs_static.opt_dir, 0 }, */ 125 { &fs_static.opt_sublink, 0 }, 126 { &fs_static.opt_rfs, 0 }, 127 { &fs_static.opt_fs, 0 }, 128 { &fs_static.opt_rhost, 0 }, 129 { &fs_static.opt_opts, 0 }, 130 { &fs_static.opt_remopts, 0 }, 131 { &fs_static.opt_mount, 0 }, 132 { &fs_static.opt_unmount, 0 }, 133 { &vars[0], 0 }, 134 { &vars[1], 0 }, 135 { &vars[2], 0 }, 136 { &vars[3], 0 }, 137 { &vars[4], 0 }, 138 { &vars[5], 0 }, 139 { &vars[6], 0 }, 140 { &vars[7], 0 }, 141 { 0, 0 }, 142 }; 143 144 /* 145 * Skip to next option in the string 146 */ 147 static char *opt P((char**)); 148 static char *opt(p) 149 char **p; 150 { 151 char *cp = *p; 152 char *dp = cp; 153 char *s = cp; 154 155 top: 156 while (*cp && *cp != ';') { 157 if (*cp == '\"') { 158 /* 159 * Skip past string 160 */ 161 cp++; 162 while (*cp && *cp != '\"') 163 *dp++ = *cp++; 164 if (*cp) 165 cp++; 166 } else { 167 *dp++ = *cp++; 168 } 169 } 170 171 /* 172 * Skip past any remaining ';'s 173 */ 174 while (*cp == ';') 175 cp++; 176 177 /* 178 * If we have a zero length string 179 * and there are more fields, then 180 * parse the next one. This allows 181 * sequences of empty fields. 182 */ 183 if (*cp && dp == s) 184 goto top; 185 186 *dp = '\0'; 187 188 *p = cp; 189 return s; 190 } 191 192 static int eval_opts P((char*, char*)); 193 static int eval_opts(opts, mapkey) 194 char *opts; 195 char *mapkey; 196 { 197 /* 198 * Fill in the global structure fs_static by 199 * cracking the string opts. opts may be 200 * scribbled on at will. 201 */ 202 char *o = opts; 203 char *f; 204 205 /* 206 * For each user-specified option 207 */ 208 while (*(f = opt(&o))) { 209 struct opt *op; 210 enum vs_opt { OldSyn, SelEQ, SelNE, VarAss } vs_opt; 211 char *eq = strchr(f, '='); 212 char *opt; 213 if (!eq || eq[1] == '\0' || eq == f) { 214 /* 215 * No value, just continue 216 */ 217 plog(XLOG_USER, "key %s: No value component in \"%s\"", mapkey, f); 218 continue; 219 } 220 221 /* 222 * Check what type of operation is happening 223 * !=, =! is SelNE 224 * == is SelEQ 225 * := is VarAss 226 * = is OldSyn (either SelEQ or VarAss) 227 */ 228 if (eq[-1] == '!') { /* != */ 229 vs_opt = SelNE; 230 eq[-1] = '\0'; 231 opt = eq + 1; 232 } else if (eq[-1] == ':') { /* := */ 233 vs_opt = VarAss; 234 eq[-1] = '\0'; 235 opt = eq + 1; 236 } else if (eq[1] == '=') { /* == */ 237 vs_opt = SelEQ; 238 eq[0] = '\0'; 239 opt = eq + 2; 240 } else if (eq[1] == '!') { /* =! */ 241 vs_opt = SelNE; 242 eq[0] = '\0'; 243 opt = eq + 2; 244 } else { /* = */ 245 vs_opt = OldSyn; 246 eq[0] = '\0'; 247 opt = eq + 1; 248 } 249 250 /* 251 * For each recognised option 252 */ 253 for (op = opt_fields; op->name; op++) { 254 /* 255 * Check whether they match 256 */ 257 if (FSTREQ(op->name, f)) { 258 switch (vs_opt) { 259 #if AMD_COMPAT <= 5000108 260 case OldSyn: 261 plog(XLOG_WARNING, "key %s: Old syntax selector found: %s=%s", mapkey, f, opt); 262 if (!op->sel_p) { 263 *op->optp = opt; 264 break; 265 } 266 /* fall through ... */ 267 #endif /* 5000108 */ 268 case SelEQ: 269 case SelNE: 270 if (op->sel_p && (STREQ(*op->sel_p, opt) == (vs_opt == SelNE))) { 271 plog(XLOG_MAP, "key %s: map selector %s (=%s) did not %smatch %s", 272 mapkey, 273 op->name, 274 *op->sel_p, 275 vs_opt == SelNE ? "not " : "", 276 opt); 277 return 0; 278 } 279 break; 280 281 case VarAss: 282 if (op->sel_p) { 283 plog(XLOG_USER, "key %s: Can't assign to a selector (%s)", mapkey, op->name); 284 return 0; 285 } 286 *op->optp = opt; 287 break; 288 } 289 break; 290 } 291 } 292 293 if (!op->name) 294 plog(XLOG_USER, "key %s: Unrecognised key/option \"%s\"", mapkey, f); 295 } 296 297 return 1; 298 } 299 300 /* 301 * Free an option 302 */ 303 static void free_op P((opt_apply*, int)); 304 /*ARGSUSED*/ 305 static void free_op(p, b) 306 opt_apply *p; 307 int b; 308 { 309 if (*p->opt) { 310 free(*p->opt); 311 *p->opt = 0; 312 } 313 } 314 315 /* 316 * Normalize slashes in the string. 317 */ 318 void normalize_slash P((char *p)); 319 void normalize_slash(p) 320 char *p; 321 { 322 char *f = strchr(p, '/'); 323 char *f0 = f; 324 if (f) { 325 char *t = f; 326 do { 327 /* assert(*f == '/'); */ 328 if (f == f0 && f[0] == '/' && f[1] == '/') { 329 /* copy double slash iff first */ 330 *t++ = *f++; 331 *t++ = *f++; 332 } else { 333 /* copy a single / across */ 334 *t++ = *f++; 335 } 336 337 /* assert(f[-1] == '/'); */ 338 /* skip past more /'s */ 339 while (*f == '/') 340 f++; 341 342 /* assert(*f != '/'); */ 343 /* keep copying up to next / */ 344 while (*f && *f != '/') { 345 *t++ = *f++; 346 } 347 348 /* assert(*f == 0 || *f == '/'); */ 349 350 } while (*f); 351 *t = 0; /* derived from fix by Steven Glassman */ 352 } 353 } 354 355 /* 356 * Macro-expand an option. Note that this does not 357 * handle recursive expansions. They will go badly wrong. 358 * If sel is true then old expand selectors, otherwise 359 * don't expand selectors. 360 */ 361 static void expand_op P((opt_apply*, int)); 362 static void expand_op(p, sel_p) 363 opt_apply *p; 364 int sel_p; 365 { 366 /* 367 * The BUFSPACE macros checks that there is enough space 368 * left in the expansion buffer. If there isn't then we 369 * give up completely. This is done to avoid crashing the 370 * automounter itself (which would be a bad thing to do). 371 */ 372 #define BUFSPACE(ep, len) (((ep) + (len)) < expbuf+MAXPATHLEN) 373 static char expand_error[] = "No space to expand \"%s\""; 374 375 char expbuf[MAXPATHLEN+1]; 376 char nbuf[NLEN+1]; 377 char *ep = expbuf; 378 char *cp = *p->opt; 379 char *dp; 380 #ifdef DEBUG 381 char *cp_orig = *p->opt; 382 #endif /* DEBUG */ 383 struct opt *op; 384 385 while (dp = strchr(cp, '$')) { 386 char ch; 387 /* 388 * First copy up to the $ 389 */ 390 { int len = dp - cp; 391 if (BUFSPACE(ep, len)) { 392 strncpy(ep, cp, len); 393 ep += len; 394 } else { 395 plog(XLOG_ERROR, expand_error, *p->opt); 396 goto out; 397 } 398 } 399 cp = dp + 1; 400 ch = *cp++; 401 if (ch == '$') { 402 if (BUFSPACE(ep, 1)) { 403 *ep++ = '$'; 404 } else { 405 plog(XLOG_ERROR, expand_error, *p->opt); 406 goto out; 407 } 408 } else if (ch == '{') { 409 /* Expansion... */ 410 enum { E_All, E_Dir, E_File, E_Domain, E_Host } todo; 411 /* 412 * Find closing brace 413 */ 414 char *br_p = strchr(cp, '}'); 415 int len; 416 /* 417 * Check we found it 418 */ 419 if (!br_p) { 420 /* 421 * Just give up 422 */ 423 plog(XLOG_USER, "No closing '}' in \"%s\"", *p->opt); 424 goto out; 425 } 426 len = br_p - cp; 427 /* 428 * Figure out which part of the variable to grab. 429 */ 430 if (*cp == '/') { 431 /* 432 * Just take the last component 433 */ 434 todo = E_File; 435 cp++; 436 --len; 437 } else if (br_p[-1] == '/') { 438 /* 439 * Take all but the last component 440 */ 441 todo = E_Dir; 442 --len; 443 } else if (*cp == '.') { 444 /* 445 * Take domain name 446 */ 447 todo = E_Domain; 448 cp++; 449 --len; 450 } else if (br_p[-1] == '.') { 451 /* 452 * Take host name 453 */ 454 todo = E_Host; 455 --len; 456 } else { 457 /* 458 * Take the whole lot 459 */ 460 todo = E_All; 461 } 462 /* 463 * Truncate if too long. Since it won't 464 * match anyway it doesn't matter that 465 * it has been cut short. 466 */ 467 if (len > NLEN) 468 len = NLEN; 469 /* 470 * Put the string into another buffer so 471 * we can do comparisons. 472 */ 473 strncpy(nbuf, cp, len); 474 nbuf[len] = '\0'; 475 /* 476 * Advance cp 477 */ 478 cp = br_p + 1; 479 /* 480 * Search the option array 481 */ 482 for (op = opt_fields; op->name; op++) { 483 /* 484 * Check for match 485 */ 486 if (len == op->nlen && STREQ(op->name, nbuf)) { 487 char xbuf[NLEN+3]; 488 char *val; 489 /* 490 * Found expansion. Copy 491 * the correct value field. 492 */ 493 if (!(!op->sel_p == !sel_p)) { 494 /* 495 * Copy the string across unexpanded 496 */ 497 sprintf(xbuf, "${%s%s%s}", 498 todo == E_File ? "/" : 499 todo == E_Domain ? "." : "", 500 nbuf, 501 todo == E_Dir ? "/" : 502 todo == E_Host ? "." : ""); 503 val = xbuf; 504 /* 505 * Make sure expansion doesn't 506 * munge the value! 507 */ 508 todo = E_All; 509 } else if (op->sel_p) { 510 val = *op->sel_p; 511 } else { 512 val = *op->optp; 513 } 514 if (val) { 515 /* 516 * Do expansion: 517 * ${/var} means take just the last part 518 * ${var/} means take all but the last part 519 * ${.var} means take all but first part 520 * ${var.} means take just the first part 521 * ${var} means take the whole lot 522 */ 523 int vlen = strlen(val); 524 char *vptr = val; 525 switch (todo) { 526 case E_Dir: 527 vptr = strrchr(val, '/'); 528 if (vptr) 529 vlen = vptr - val; 530 vptr = val; 531 break; 532 case E_File: 533 vptr = strrchr(val, '/'); 534 if (vptr) { 535 vptr++; 536 vlen = strlen(vptr); 537 } else 538 vptr = val; 539 break; 540 case E_Domain: 541 vptr = strchr(val, '.'); 542 if (vptr) { 543 vptr++; 544 vlen = strlen(vptr); 545 } else { 546 vptr = ""; 547 vlen = 0; 548 } 549 break; 550 case E_Host: 551 vptr = strchr(val, '.'); 552 if (vptr) 553 vlen = vptr - val; 554 vptr = val; 555 break; 556 case E_All: 557 break; 558 } 559 #ifdef DEBUG 560 /*dlog("Expanding \"%s\" to \"%s\"", nbuf, val);*/ 561 #endif /* DEBUG */ 562 if (BUFSPACE(ep, vlen)) { 563 strcpy(ep, vptr); 564 ep += vlen; 565 } else { 566 plog(XLOG_ERROR, expand_error, *p->opt); 567 goto out; 568 } 569 } 570 /* 571 * Done with this variable 572 */ 573 break; 574 } 575 } 576 /* 577 * Check that the search was succesful 578 */ 579 if (!op->name) { 580 /* 581 * If it wasn't then scan the 582 * environment for that name 583 * and use any value found 584 */ 585 char *env = getenv(nbuf); 586 if (env) { 587 int vlen = strlen(env); 588 589 if (BUFSPACE(ep, vlen)) { 590 strcpy(ep, env); 591 ep += vlen; 592 } else { 593 plog(XLOG_ERROR, expand_error, *p->opt); 594 goto out; 595 } 596 #ifdef DEBUG 597 Debug(D_STR) 598 plog(XLOG_DEBUG, "Environment gave \"%s\" -> \"%s\"", nbuf, env); 599 #endif /* DEBUG */ 600 } else { 601 plog(XLOG_USER, "Unknown sequence \"${%s}\"", nbuf); 602 } 603 } 604 } else { 605 /* 606 * Error, error 607 */ 608 plog(XLOG_USER, "Unknown $ sequence in \"%s\"", *p->opt); 609 } 610 } 611 612 out: 613 /* 614 * Handle common case - no expansion 615 */ 616 if (cp == *p->opt) { 617 *p->opt = strdup(cp); 618 } else { 619 /* 620 * Finish off the expansion 621 */ 622 if (BUFSPACE(ep, strlen(cp))) { 623 strcpy(ep, cp); 624 /*ep += strlen(ep);*/ 625 } else { 626 plog(XLOG_ERROR, expand_error, *p->opt); 627 } 628 629 /* 630 * Save the exansion 631 */ 632 *p->opt = strdup(expbuf); 633 } 634 635 normalize_slash(*p->opt); 636 637 #ifdef DEBUG 638 Debug(D_STR) { 639 plog(XLOG_DEBUG, "Expansion of \"%s\"...", cp_orig); 640 plog(XLOG_DEBUG, "... is \"%s\"", *p->opt); 641 } 642 #endif /* DEBUG */ 643 } 644 645 /* 646 * Wrapper for expand_op 647 */ 648 static void expand_opts P((opt_apply*, int)); 649 static void expand_opts(p, sel_p) 650 opt_apply *p; 651 int sel_p; 652 { 653 if (*p->opt) { 654 expand_op(p, sel_p); 655 } else if (p->val) { 656 /* 657 * Do double expansion, remembering 658 * to free the string from the first 659 * expansion... 660 */ 661 char *s = *p->opt = expand_key(p->val); 662 expand_op(p, sel_p); 663 free(s); 664 } 665 } 666 667 /* 668 * Apply a function to a list of options 669 */ 670 static void apply_opts(op, ppp, b) 671 void (*op)(); 672 opt_apply ppp[]; 673 int b; 674 { 675 opt_apply *pp; 676 for (pp = ppp; pp->opt; pp++) 677 (*op)(pp, b); 678 } 679 680 /* 681 * Free the option table 682 */ 683 void free_opts(fo) 684 am_opts *fo; 685 { 686 /* 687 * Copy in the structure we are playing with 688 */ 689 fs_static = *fo; 690 691 /* 692 * Free previously allocated memory 693 */ 694 apply_opts(free_op, to_free, FALSE); 695 } 696 697 /* 698 * Expand lookup key 699 */ 700 char *expand_key(key) 701 char *key; 702 { 703 opt_apply oa; 704 705 oa.opt = &key; oa.val = 0; 706 expand_opts(&oa, TRUE); 707 708 return key; 709 } 710 711 /* 712 * Remove trailing /'s from a string 713 * unless the string is a single / (Steven Glassman) 714 */ 715 void deslashify P((char *s)); 716 void deslashify(s) 717 char *s; 718 { 719 if (s && *s) { 720 char *sl = s + strlen(s); 721 while (*--sl == '/' && sl > s) 722 *sl = '\0'; 723 } 724 } 725 726 int eval_fs_opts(fo, opts, g_opts, path, key, map) 727 am_opts *fo; 728 char *opts, *g_opts, *path, *key, *map; 729 { 730 int ok = TRUE; 731 732 free_opts(fo); 733 734 /* 735 * Clear out the option table 736 */ 737 bzero((voidp) &fs_static, sizeof(fs_static)); 738 bzero((voidp) vars, sizeof(vars)); 739 bzero((voidp) fo, sizeof(*fo)); 740 741 /* 742 * Set key, map & path before expansion 743 */ 744 opt_key = key; 745 opt_map = map; 746 opt_path = path; 747 748 /* 749 * Expand global options 750 */ 751 fs_static.fs_glob = expand_key(g_opts); 752 753 /* 754 * Expand local options 755 */ 756 fs_static.fs_local = expand_key(opts); 757 758 /* 759 * Expand default (global) options 760 */ 761 if (!eval_opts(fs_static.fs_glob, key)) 762 ok = FALSE; 763 764 /* 765 * Expand local options 766 */ 767 if (ok && !eval_opts(fs_static.fs_local, key)) 768 ok = FALSE; 769 770 /* 771 * Normalise remote host name. 772 * 1. Expand variables 773 * 2. Normalize relative to host tables 774 * 3. Strip local domains from the remote host 775 * name before using it in other expansions. 776 * This makes mount point names and other things 777 * much shorter, while allowing cross domain 778 * sharing of mount maps. 779 */ 780 apply_opts(expand_opts, rhost_expansion, FALSE); 781 if (ok && fs_static.opt_rhost && *fs_static.opt_rhost) 782 host_normalize(&fs_static.opt_rhost); 783 784 /* 785 * Macro expand the options. 786 * Do this regardless of whether we are accepting 787 * this mount - otherwise nasty things happen 788 * with memory allocation. 789 */ 790 apply_opts(expand_opts, expansions, FALSE); 791 792 /* 793 * Strip trailing slashes from local pathname... 794 */ 795 deslashify(fs_static.opt_fs); 796 797 /* 798 * ok... copy the data back out. 799 */ 800 *fo = fs_static; 801 802 /* 803 * Clear defined options 804 */ 805 opt_key = opt_map = opt_path = nullstr; 806 807 return ok; 808 } 809