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