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 * @(#)fsi_analyze.c 5.3 (Berkeley) 05/12/91 13 * 14 * $Id: fsi_analyze.c,v 5.2.1.3 91/05/07 22:19:06 jsp Alpha $ 15 * 16 */ 17 18 /* 19 * Analyze filesystem declarations 20 * 21 * Note: most of this is magic! 22 */ 23 24 #include "../fsinfo/fsinfo.h" 25 26 char *disk_fs_strings[] = { 27 "fstype", "opts", "dumpset", "passno", "freq", "mount", "log", 0, 28 }; 29 30 char *mount_strings[] = { 31 "volname", "exportfs", 0, 32 }; 33 34 char *fsmount_strings[] = { 35 "as", "volname", "fstype", "opts", "from", 0, 36 }; 37 38 char *host_strings[] = { 39 "host", "netif", "config", "arch", "cluster", "os", 0, 40 }; 41 42 char *ether_if_strings[] = { 43 "inaddr", "netmask", "hwaddr", 0, 44 }; 45 46 /* 47 * Strip off the trailing part of a domain 48 * to produce a short-form domain relative 49 * to the local host domain. 50 * Note that this has no effect if the domain 51 * names do not have the same number of 52 * components. If that restriction proves 53 * to be a problem then the loop needs recoding 54 * to skip from right to left and do partial 55 * matches along the way -- ie more expensive. 56 */ 57 void domain_strip(otherdom, localdom) 58 char *otherdom, *localdom; 59 { 60 #ifdef PARTIAL_DOMAINS 61 char *p1 = otherdom-1; 62 char *p2 = localdom-1; 63 64 do { 65 if (p1 = strchr(p1+1, '.')) 66 if (p2 = strchr(p2+1, '.')) 67 if (STREQ(p1+1, p2+1)) { 68 *p1 = '\0'; 69 break; 70 } 71 } while (p1 && p2); 72 #else 73 char *p1, *p2; 74 75 if ((p1 = strchr(otherdom, '.')) && 76 (p2 = strchr(localdom, '.')) && 77 (strcmp(p1+1, p2+1) == 0)) 78 *p1 = '\0'; 79 #endif /* PARTIAL_DOMAINS */ 80 } 81 82 /* 83 * Take a little-endian domain name and 84 * transform into a big-endian Un*x pathname. 85 * For example: kiska.doc.ic -> ic/doc/kiska 86 */ 87 static char *compute_hostpath(hn) 88 char *hn; 89 { 90 char *p = strdup(hn); 91 char *d; 92 char path[MAXPATHLEN]; 93 94 domain_strip(p, hostname); 95 path[0] = '\0'; 96 97 do { 98 d = strrchr(p, '.'); 99 if (d) { 100 *d = 0; 101 strcat(path, d+1); 102 strcat(path, "/"); 103 } else { 104 strcat(path, p); 105 } 106 } while (d); 107 108 log("hostpath of '%s' is '%s'", hn, path); 109 110 strcpy(p, path); 111 return p; 112 } 113 114 static dict_ent *find_volname(nn) 115 char *nn; 116 { 117 dict_ent *de; 118 char *p = strdup(nn); 119 char *q; 120 121 do { 122 log("Searching for volname %s", p); 123 de = dict_locate(dict_of_volnames, p); 124 q = strrchr(p, '/'); 125 if (q) *q = '\0'; 126 } while (!de && q); 127 128 free(p); 129 return de; 130 } 131 132 static show_required(l, mask, info, hostname, strings) 133 ioloc *l; 134 int mask; 135 char *info; 136 char *hostname; 137 char *strings[]; 138 { 139 int i; 140 log("mask left for %s:%s is %#x", hostname, info, mask); 141 142 for (i = 0; strings[i]; i++) 143 if (ISSET(mask, i)) 144 lerror(l, "%s:%s needs field \"%s\"", hostname, info, strings[i]); 145 } 146 147 /* 148 * Check and fill in "exportfs" details. 149 * Make sure the m_exported field references 150 * the most local node with an "exportfs" entry. 151 */ 152 static int check_exportfs(q, e) 153 qelem *q; 154 mount *e; 155 { 156 mount *mp; 157 int errors = 0; 158 159 ITER(mp, mount, q) { 160 if (ISSET(mp->m_mask, DM_EXPORTFS)) { 161 if (e) 162 lwarning(mp->m_ioloc, "%s has duplicate exportfs data", mp->m_name); 163 mp->m_exported = mp; 164 if (!ISSET(mp->m_mask, DM_VOLNAME)) 165 set_mount(mp, DM_VOLNAME, strdup(mp->m_name)); 166 } else { 167 mp->m_exported = e; 168 } 169 170 /* 171 * Recursively descend the mount tree 172 */ 173 if (mp->m_mount) 174 errors += check_exportfs(mp->m_mount, mp->m_exported); 175 176 /* 177 * If a volume name has been specified, but this node and none 178 * of its parents has been exported, report an error. 179 */ 180 if (ISSET(mp->m_mask, DM_VOLNAME) && !mp->m_exported) { 181 lerror(mp->m_ioloc, "%s has a volname but no exportfs data", mp->m_name); 182 errors++; 183 } 184 } 185 186 return errors; 187 } 188 189 static int analyze_dkmount_tree(q, parent, dk) 190 qelem *q; 191 mount *parent; 192 disk_fs *dk; 193 { 194 mount *mp; 195 int errors = 0; 196 197 ITER(mp, mount, q) { 198 log("Mount %s:", mp->m_name); 199 if (parent) { 200 char n[MAXPATHLEN]; 201 sprintf(n, "%s/%s", parent->m_name, mp->m_name); 202 if (*mp->m_name == '/') 203 lerror(mp->m_ioloc, "sub-directory %s of %s starts with '/'", mp->m_name, parent->m_name); 204 else if (STREQ(mp->m_name, "default")) 205 lwarning(mp->m_ioloc, "sub-directory of %s is named \"default\"", parent->m_name); 206 log("Changing name %s to %s", mp->m_name, n); 207 free(mp->m_name); 208 mp->m_name = strdup(n); 209 } 210 mp->m_name_len = strlen(mp->m_name); 211 mp->m_parent = parent; 212 mp->m_dk = dk; 213 if (mp->m_mount) 214 analyze_dkmount_tree(mp->m_mount, mp, dk); 215 } 216 217 return errors; 218 } 219 220 /* 221 * The mount tree is a singleton list 222 * containing the top-level mount 223 * point for a disk. 224 */ 225 static int analyze_dkmounts(dk, q) 226 disk_fs *dk; 227 qelem *q; 228 { 229 int errors = 0; 230 mount *mp, *mp2 = 0; 231 int i = 0; 232 233 /* 234 * First scan the list of subdirs to make 235 * sure there is only one - and remember it 236 */ 237 if (q) { 238 ITER(mp, mount, q) { 239 mp2 = mp; 240 i++; 241 } 242 } 243 244 /* 245 * Check... 246 */ 247 if (i < 1) { 248 lerror(dk->d_ioloc, "%s:%s has no mount point", dk->d_host->h_hostname, dk->d_dev); 249 return 1; 250 } 251 if (i > 1) { 252 lerror(dk->d_ioloc, "%s:%s has more than one mount point", dk->d_host->h_hostname, dk->d_dev); 253 errors++; 254 } 255 /* 256 * Now see if a default mount point is required 257 */ 258 if (STREQ(mp2->m_name, "default")) { 259 if (ISSET(mp2->m_mask, DM_VOLNAME)) { 260 char nbuf[1024]; 261 compute_automount_point(nbuf, dk->d_host, mp2->m_volname); 262 free(mp2->m_name); 263 mp2->m_name = strdup(nbuf); 264 log("%s:%s has default mount on %s", dk->d_host->h_hostname, dk->d_dev, mp2->m_name); 265 } else { 266 lerror(dk->d_ioloc, "no volname given for %s:%s", dk->d_host->h_hostname, dk->d_dev); 267 errors++; 268 } 269 } 270 /* 271 * Fill in the disk mount point 272 */ 273 if (!errors && mp2 && mp2->m_name) 274 dk->d_mountpt = strdup(mp2->m_name); 275 else 276 dk->d_mountpt = strdup("error"); 277 278 /* 279 * Analyze the mount tree 280 */ 281 errors += analyze_dkmount_tree(q, 0, dk); 282 283 /* 284 * Analyze the export tree 285 */ 286 errors += check_exportfs(q, 0); 287 288 return errors; 289 } 290 291 static void fixup_required_disk_info(dp) 292 disk_fs *dp; 293 { 294 /* 295 * "fstype" 296 */ 297 if (ISSET(dp->d_mask, DF_FSTYPE)) { 298 if (STREQ(dp->d_fstype, "swap")) { 299 /* 300 * Fixup for a swap device 301 */ 302 if (!ISSET(dp->d_mask, DF_PASSNO)) { 303 dp->d_passno = 0; 304 BITSET(dp->d_mask, DF_PASSNO); 305 } else if (dp->d_freq != 0) { 306 lwarning(dp->d_ioloc, 307 "Pass number for %s:%s is non-zero", 308 dp->d_host->h_hostname, dp->d_dev); 309 } 310 311 /* 312 * "freq" 313 */ 314 if (!ISSET(dp->d_mask, DF_FREQ)) { 315 dp->d_freq = 0; 316 BITSET(dp->d_mask, DF_FREQ); 317 } else if (dp->d_freq != 0) { 318 lwarning(dp->d_ioloc, 319 "dump frequency for %s:%s is non-zero", 320 dp->d_host->h_hostname, dp->d_dev); 321 } 322 323 /* 324 * "opts" 325 */ 326 if (!ISSET(dp->d_mask, DF_OPTS)) 327 set_disk_fs(dp, DF_OPTS, strdup("swap")); 328 329 /* 330 * "mount" 331 */ 332 if (!ISSET(dp->d_mask, DF_MOUNT)) { 333 qelem *q = new_que(); 334 mount *m = new_mount(); 335 m->m_name = strdup("swap"); 336 m->m_mount = new_que(); 337 ins_que(&m->m_q, q->q_back); 338 dp->d_mount = q; 339 BITSET(dp->d_mask, DF_MOUNT); 340 } else { 341 lerror(dp->d_ioloc, "%s: mount field specified for swap partition", dp->d_host->h_hostname); 342 } 343 } else if (STREQ(dp->d_fstype, "export")) { 344 /* 345 * "passno" 346 */ 347 if (!ISSET(dp->d_mask, DF_PASSNO)) { 348 dp->d_passno = 0; 349 BITSET(dp->d_mask, DF_PASSNO); 350 } else if (dp->d_passno != 0) { 351 lwarning(dp->d_ioloc, 352 "pass number for %s:%s is non-zero", 353 dp->d_host->h_hostname, dp->d_dev); 354 } 355 356 /* 357 * "freq" 358 */ 359 if (!ISSET(dp->d_mask, DF_FREQ)) { 360 dp->d_freq = 0; 361 BITSET(dp->d_mask, DF_FREQ); 362 } else if (dp->d_freq != 0) { 363 lwarning(dp->d_ioloc, 364 "dump frequency for %s:%s is non-zero", 365 dp->d_host->h_hostname, dp->d_dev); 366 } 367 368 /* 369 * "opts" 370 */ 371 if (!ISSET(dp->d_mask, DF_OPTS)) 372 set_disk_fs(dp, DF_OPTS, strdup("rw,defaults")); 373 374 } 375 } 376 } 377 378 static void fixup_required_mount_info(fp, de) 379 fsmount *fp; 380 dict_ent *de; 381 { 382 if (!ISSET(fp->f_mask, FM_FROM)) { 383 if (de->de_count != 1) { 384 lerror(fp->f_ioloc, "ambiguous mount: %s is a replicated filesystem", fp->f_volname); 385 } else { 386 dict_data *dd; 387 mount *mp = 0; 388 ITER(dd, dict_data, &de->de_q) { 389 mp = (mount *) dd->dd_data; 390 break; 391 } 392 if (!mp) 393 abort(); 394 fp->f_ref = mp; 395 set_fsmount(fp, FM_FROM, mp->m_dk->d_host->h_hostname); 396 log("set: %s comes from %s", fp->f_volname, fp->f_from); 397 } 398 } 399 400 if (!ISSET(fp->f_mask, FM_FSTYPE)) { 401 set_fsmount(fp, FM_FSTYPE, strdup("nfs")); 402 log("set: fstype is %s", fp->f_fstype); 403 } 404 405 if (!ISSET(fp->f_mask, FM_OPTS)) { 406 set_fsmount(fp, FM_OPTS, strdup("rw,nosuid,grpid,defaults")); 407 log("set: opts are %s", fp->f_opts); 408 } 409 410 if (!ISSET(fp->f_mask, FM_LOCALNAME)) { 411 if (fp->f_ref) { 412 set_fsmount(fp, FM_LOCALNAME, strdup(fp->f_volname)); 413 log("set: localname is %s", fp->f_localname); 414 } else { 415 lerror(fp->f_ioloc, "cannot determine localname since volname %s is not uniquely defined", fp->f_volname); 416 } 417 } 418 } 419 420 /* 421 * For each disk on a host 422 * analyze the mount information 423 * and fill in any derivable 424 * details. 425 */ 426 static void analyze_drives(hp) 427 host *hp; 428 { 429 qelem *q = hp->h_disk_fs; 430 disk_fs *dp; 431 432 ITER(dp, disk_fs, q) { 433 int req; 434 log("Disk %s:", dp->d_dev); 435 dp->d_host = hp; 436 fixup_required_disk_info(dp); 437 req = ~dp->d_mask & DF_REQUIRED; 438 if (req) 439 show_required(dp->d_ioloc, req, dp->d_dev, hp->h_hostname, disk_fs_strings); 440 analyze_dkmounts(dp, dp->d_mount); 441 } 442 } 443 444 /* 445 * Check that all static mounts make sense and 446 * that the source volumes exist. 447 */ 448 static void analyze_mounts(hp) 449 host *hp; 450 { 451 qelem *q = hp->h_mount; 452 fsmount *fp; 453 int netbootp = 0; 454 455 ITER(fp, fsmount, q) { 456 char *p; 457 char *nn = strdup(fp->f_volname); 458 int req; 459 dict_ent *de; 460 int found = 0; 461 int matched = 0; 462 do { 463 p = 0; 464 de = find_volname(nn); 465 log("Mount: %s (trying %s)", fp->f_volname, nn); 466 467 if (de) { 468 found = 1; 469 /* 470 * Check that the from field is really exporting 471 * the filesystem requested. 472 */ 473 if (ISSET(fp->f_mask, FM_FROM)) { 474 dict_data *dd; 475 mount *mp2 = 0; 476 ITER(dd, dict_data, &de->de_q) { 477 mount *mp = (mount *) dd->dd_data; 478 if (STREQ(mp->m_dk->d_host->h_hostname, fp->f_from)) { 479 mp2 = mp; 480 break; 481 } 482 } 483 484 if (mp2) { 485 fp->f_ref = mp2; 486 matched = 1; 487 break; 488 } 489 } else { 490 matched = 1; 491 break; 492 } 493 } 494 p = strrchr(nn, '/'); 495 if (p) 496 *p = 0; 497 } while (de && p); 498 free(nn); 499 500 if (!found) { 501 lerror(fp->f_ioloc, "volname %s unknown", fp->f_volname); 502 } else if (matched) { 503 fixup_required_mount_info(fp, de); 504 req = ~fp->f_mask & FM_REQUIRED; 505 if (req) { 506 show_required(fp->f_ioloc, req, fp->f_volname, hp->h_hostname, 507 fsmount_strings); 508 } else if (strcmp(fp->f_localname, "/") == 0) { 509 hp->h_netroot = fp; 510 netbootp |= FM_NETROOT; 511 } else if (strcmp(fp->f_localname, "swap") == 0) { 512 hp->h_netswap = fp; 513 netbootp |= FM_NETSWAP; 514 } 515 } else { 516 lerror(fp->f_ioloc, "volname %s not exported from %s", fp->f_volname, 517 fp->f_from ? fp->f_from : "anywhere"); 518 } 519 } 520 521 if (netbootp && (netbootp != FM_NETBOOT)) 522 lerror(hp->h_ioloc, "network booting requires both root and swap areas"); 523 } 524 525 void analyze_hosts(q) 526 qelem *q; 527 { 528 host *hp; 529 530 show_area_being_processed("analyze hosts", 5); 531 532 /* 533 * Check all drives 534 */ 535 ITER(hp, host, q) { 536 log("disks on host %s", hp->h_hostname); 537 show_new("ana-host"); 538 hp->h_hostpath = compute_hostpath(hp->h_hostname); 539 540 if (hp->h_disk_fs) 541 analyze_drives(hp); 542 543 } 544 545 show_area_being_processed("analyze mounts", 5); 546 547 /* 548 * Check static mounts 549 */ 550 ITER(hp, host, q) { 551 log("mounts on host %s", hp->h_hostname); 552 show_new("ana-mount"); 553 if (hp->h_mount) 554 analyze_mounts(hp); 555 556 } 557 } 558 559 /* 560 * Check an automount request 561 */ 562 static void analyze_automount(ap) 563 automount *ap; 564 { 565 dict_ent *de = find_volname(ap->a_volname); 566 if (de) { 567 ap->a_mounted = de; 568 } else { 569 if (STREQ(ap->a_volname, ap->a_name)) 570 lerror(ap->a_ioloc, "unknown volname %s automounted", ap->a_volname); 571 else 572 lerror(ap->a_ioloc, "unknown volname %s automounted on %s", ap->a_volname, ap->a_name); 573 } 574 } 575 576 static void analyze_automount_tree(q, pref, lvl) 577 qelem *q; 578 char *pref; 579 int lvl; 580 { 581 automount *ap; 582 583 ITER(ap, automount, q) { 584 char nname[1024]; 585 if (lvl > 0 || ap->a_mount) 586 if (ap->a_name[1] && strchr(ap->a_name+1, '/')) 587 lerror(ap->a_ioloc, "not allowed '/' in a directory name"); 588 sprintf(nname, "%s/%s", pref, ap->a_name); 589 free(ap->a_name); 590 ap->a_name = strdup(nname[1] == '/' ? nname+1 : nname); 591 log("automount point %s:", ap->a_name); 592 show_new("ana-automount"); 593 if (ap->a_mount) { 594 analyze_automount_tree(ap->a_mount, ap->a_name, lvl+1); 595 } else if (ap->a_volname) { 596 log("\tautomount from %s", ap->a_volname); 597 analyze_automount(ap); 598 } else if (ap->a_symlink) { 599 log("\tsymlink to %s", ap->a_symlink); 600 } else { 601 ap->a_volname = strdup(ap->a_name); 602 log("\timplicit automount from %s", ap->a_volname); 603 analyze_automount(ap); 604 } 605 } 606 } 607 608 void analyze_automounts(q) 609 qelem *q; 610 { 611 auto_tree *tp; 612 613 show_area_being_processed("analyze automount", 5); 614 /* 615 * q is a list of automounts 616 */ 617 ITER(tp, auto_tree, q) 618 analyze_automount_tree(tp->t_mount, "", 0); 619 } 620