1 /* $NetBSD: fsi_analyze.c,v 1.1.1.2 2009/03/20 20:26:55 christos Exp $ */ 2 3 /* 4 * Copyright (c) 1997-2009 Erez Zadok 5 * Copyright (c) 1989 Jan-Simon Pendry 6 * Copyright (c) 1989 Imperial College of Science, Technology & Medicine 7 * Copyright (c) 1989 The Regents of the University of California. 8 * All rights reserved. 9 * 10 * This code is derived from software contributed to Berkeley by 11 * Jan-Simon Pendry at Imperial College, London. 12 * 13 * Redistribution and use in source and binary forms, with or without 14 * modification, are permitted provided that the following conditions 15 * are met: 16 * 1. Redistributions of source code must retain the above copyright 17 * notice, this list of conditions and the following disclaimer. 18 * 2. Redistributions in binary form must reproduce the above copyright 19 * notice, this list of conditions and the following disclaimer in the 20 * documentation and/or other materials provided with the distribution. 21 * 3. All advertising materials mentioning features or use of this software 22 * must display the following acknowledgment: 23 * This product includes software developed by the University of 24 * California, Berkeley and its contributors. 25 * 4. Neither the name of the University nor the names of its contributors 26 * may be used to endorse or promote products derived from this software 27 * without specific prior written permission. 28 * 29 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 30 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 31 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 32 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 33 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 34 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 35 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 36 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 37 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 38 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 39 * SUCH DAMAGE. 40 * 41 * 42 * File: am-utils/fsinfo/fsi_analyze.c 43 * 44 */ 45 46 /* 47 * Analyze filesystem declarations 48 * 49 * Note: most of this is magic! 50 */ 51 52 #ifdef HAVE_CONFIG_H 53 # include <config.h> 54 #endif /* HAVE_CONFIG_H */ 55 #include <am_defs.h> 56 #include <fsi_data.h> 57 #include <fsinfo.h> 58 59 char *disk_fs_strings[] = 60 { 61 "fstype", "opts", "dumpset", "passno", "freq", "mount", "log", NULL, 62 }; 63 64 char *mount_strings[] = 65 { 66 "volname", "exportfs", NULL, 67 }; 68 69 char *fsmount_strings[] = 70 { 71 "as", "volname", "fstype", "opts", "from", NULL, 72 }; 73 74 char *host_strings[] = 75 { 76 "host", "netif", "config", "arch", "cluster", "os", NULL, 77 }; 78 79 char *ether_if_strings[] = 80 { 81 "inaddr", "netmask", "hwaddr", NULL, 82 }; 83 84 85 /* 86 * Strip off the trailing part of a domain 87 * to produce a short-form domain relative 88 * to the local host domain. 89 * Note that this has no effect if the domain 90 * names do not have the same number of 91 * components. If that restriction proves 92 * to be a problem then the loop needs recoding 93 * to skip from right to left and do partial 94 * matches along the way -- ie more expensive. 95 */ 96 void 97 domain_strip(char *otherdom, char *localdom) 98 { 99 char *p1, *p2; 100 101 if ((p1 = strchr(otherdom, '.')) && 102 (p2 = strchr(localdom, '.')) && 103 STREQ(p1 + 1, p2 + 1)) 104 *p1 = '\0'; 105 } 106 107 108 /* 109 * Take a little-endian domain name and 110 * transform into a big-endian Un*x pathname. 111 * For example: kiska.doc.ic -> ic/doc/kiska 112 */ 113 static char * 114 compute_hostpath(char *hn) 115 { 116 char *p = xmalloc(MAXPATHLEN); 117 char *d; 118 char path[MAXPATHLEN]; 119 120 xstrlcpy(p, hn, MAXPATHLEN); 121 domain_strip(p, hostname); 122 path[0] = '\0'; 123 124 do { 125 d = strrchr(p, '.'); 126 if (d) { 127 *d = '\0'; 128 xstrlcat(path, d + 1, sizeof(path)); 129 xstrlcat(path, "/", sizeof(path)); 130 } else { 131 xstrlcat(path, p, sizeof(path)); 132 } 133 } while (d); 134 135 fsi_log("hostpath of '%s' is '%s'", hn, path); 136 137 xstrlcpy(p, path, MAXPATHLEN); 138 return p; 139 } 140 141 142 static dict_ent * 143 find_volname(char *nn) 144 { 145 dict_ent *de; 146 char *p = strdup(nn); 147 char *q; 148 149 do { 150 fsi_log("Searching for volname %s", p); 151 de = dict_locate(dict_of_volnames, p); 152 q = strrchr(p, '/'); 153 if (q) 154 *q = '\0'; 155 } while (!de && q); 156 157 XFREE(p); 158 return de; 159 } 160 161 162 static void 163 show_required(ioloc *l, int mask, char *info, char *hostname, char *strings[]) 164 { 165 int i; 166 fsi_log("mask left for %s:%s is %#x", hostname, info, mask); 167 168 for (i = 0; strings[i]; i++) 169 if (ISSET(mask, i)) 170 lerror(l, "%s:%s needs field \"%s\"", hostname, info, strings[i]); 171 } 172 173 174 /* 175 * Check and fill in "exportfs" details. 176 * Make sure the m_exported field references 177 * the most local node with an "exportfs" entry. 178 */ 179 static int 180 check_exportfs(qelem *q, fsi_mount *e) 181 { 182 fsi_mount *mp; 183 int errors = 0; 184 185 ITER(mp, fsi_mount, q) { 186 if (ISSET(mp->m_mask, DM_EXPORTFS)) { 187 if (e) 188 lwarning(mp->m_ioloc, "%s has duplicate exportfs data", mp->m_name); 189 mp->m_exported = mp; 190 if (!ISSET(mp->m_mask, DM_VOLNAME)) 191 set_mount(mp, DM_VOLNAME, strdup(mp->m_name)); 192 } else { 193 mp->m_exported = e; 194 } 195 196 /* 197 * Recursively descend the mount tree 198 */ 199 if (mp->m_mount) 200 errors += check_exportfs(mp->m_mount, mp->m_exported); 201 202 /* 203 * If a volume name has been specified, but this node and none 204 * of its parents has been exported, report an error. 205 */ 206 if (ISSET(mp->m_mask, DM_VOLNAME) && !mp->m_exported) { 207 lerror(mp->m_ioloc, "%s has a volname but no exportfs data", mp->m_name); 208 errors++; 209 } 210 } 211 212 return errors; 213 } 214 215 216 static int 217 analyze_dkmount_tree(qelem *q, fsi_mount *parent, disk_fs *dk) 218 { 219 fsi_mount *mp; 220 int errors = 0; 221 222 ITER(mp, fsi_mount, q) { 223 fsi_log("Mount %s:", mp->m_name); 224 if (parent) { 225 char n[MAXPATHLEN]; 226 xsnprintf(n, sizeof(n), "%s/%s", parent->m_name, mp->m_name); 227 if (*mp->m_name == '/') 228 lerror(mp->m_ioloc, "sub-directory %s of %s starts with '/'", mp->m_name, parent->m_name); 229 else if (STREQ(mp->m_name, "default")) 230 lwarning(mp->m_ioloc, "sub-directory of %s is named \"default\"", parent->m_name); 231 fsi_log("Changing name %s to %s", mp->m_name, n); 232 XFREE(mp->m_name); 233 mp->m_name = strdup(n); 234 } 235 236 mp->m_name_len = strlen(mp->m_name); 237 mp->m_parent = parent; 238 mp->m_dk = dk; 239 if (mp->m_mount) 240 analyze_dkmount_tree(mp->m_mount, mp, dk); 241 } 242 243 return errors; 244 } 245 246 247 /* 248 * The mount tree is a singleton list 249 * containing the top-level mount 250 * point for a disk. 251 */ 252 static int 253 analyze_dkmounts(disk_fs *dk, qelem *q) 254 { 255 int errors = 0; 256 fsi_mount *mp, *mp2 = NULL; 257 int i = 0; 258 259 /* 260 * First scan the list of subdirs to make 261 * sure there is only one - and remember it 262 */ 263 if (q) { 264 ITER(mp, fsi_mount, q) { 265 mp2 = mp; 266 i++; 267 } 268 } 269 270 /* 271 * Check... 272 */ 273 if (i < 1) { 274 lerror(dk->d_ioloc, "%s:%s has no mount point", dk->d_host->h_hostname, dk->d_dev); 275 return 1; 276 } 277 278 if (i > 1) { 279 lerror(dk->d_ioloc, "%s:%s has more than one mount point", dk->d_host->h_hostname, dk->d_dev); 280 errors++; 281 } 282 283 /* 284 * Now see if a default mount point is required 285 */ 286 if (mp2 && STREQ(mp2->m_name, "default")) { 287 if (ISSET(mp2->m_mask, DM_VOLNAME)) { 288 char nbuf[1024]; 289 compute_automount_point(nbuf, sizeof(nbuf), dk->d_host, mp2->m_volname); 290 XFREE(mp2->m_name); 291 mp2->m_name = strdup(nbuf); 292 fsi_log("%s:%s has default mount on %s", dk->d_host->h_hostname, dk->d_dev, mp2->m_name); 293 } else { 294 lerror(dk->d_ioloc, "no volname given for %s:%s", dk->d_host->h_hostname, dk->d_dev); 295 errors++; 296 } 297 } 298 299 /* 300 * Fill in the disk mount point 301 */ 302 if (!errors && mp2 && mp2->m_name) 303 dk->d_mountpt = strdup(mp2->m_name); 304 else 305 dk->d_mountpt = strdup("error"); 306 307 /* 308 * Analyze the mount tree 309 */ 310 errors += analyze_dkmount_tree(q, NULL, dk); 311 312 /* 313 * Analyze the export tree 314 */ 315 errors += check_exportfs(q, NULL); 316 317 return errors; 318 } 319 320 321 static void 322 fixup_required_disk_info(disk_fs *dp) 323 { 324 /* 325 * "fstype" 326 */ 327 if (ISSET(dp->d_mask, DF_FSTYPE)) { 328 if (STREQ(dp->d_fstype, "swap")) { 329 330 /* 331 * Fixup for a swap device 332 */ 333 if (!ISSET(dp->d_mask, DF_PASSNO)) { 334 dp->d_passno = 0; 335 BITSET(dp->d_mask, DF_PASSNO); 336 } else if (dp->d_freq != 0) { 337 lwarning(dp->d_ioloc, 338 "Pass number for %s:%s is non-zero", 339 dp->d_host->h_hostname, dp->d_dev); 340 } 341 342 /* 343 * "freq" 344 */ 345 if (!ISSET(dp->d_mask, DF_FREQ)) { 346 dp->d_freq = 0; 347 BITSET(dp->d_mask, DF_FREQ); 348 } else if (dp->d_freq != 0) { 349 lwarning(dp->d_ioloc, 350 "dump frequency for %s:%s is non-zero", 351 dp->d_host->h_hostname, dp->d_dev); 352 } 353 354 /* 355 * "opts" 356 */ 357 if (!ISSET(dp->d_mask, DF_OPTS)) 358 set_disk_fs(dp, DF_OPTS, strdup("swap")); 359 360 /* 361 * "mount" 362 */ 363 if (!ISSET(dp->d_mask, DF_MOUNT)) { 364 qelem *q = new_que(); 365 fsi_mount *m = new_mount(); 366 367 m->m_name = strdup("swap"); 368 m->m_mount = new_que(); 369 ins_que(&m->m_q, q->q_back); 370 dp->d_mount = q; 371 BITSET(dp->d_mask, DF_MOUNT); 372 } else { 373 lerror(dp->d_ioloc, "%s: mount field specified for swap partition", dp->d_host->h_hostname); 374 } 375 } else if (STREQ(dp->d_fstype, "export")) { 376 377 /* 378 * "passno" 379 */ 380 if (!ISSET(dp->d_mask, DF_PASSNO)) { 381 dp->d_passno = 0; 382 BITSET(dp->d_mask, DF_PASSNO); 383 } else if (dp->d_passno != 0) { 384 lwarning(dp->d_ioloc, 385 "pass number for %s:%s is non-zero", 386 dp->d_host->h_hostname, dp->d_dev); 387 } 388 389 /* 390 * "freq" 391 */ 392 if (!ISSET(dp->d_mask, DF_FREQ)) { 393 dp->d_freq = 0; 394 BITSET(dp->d_mask, DF_FREQ); 395 } else if (dp->d_freq != 0) { 396 lwarning(dp->d_ioloc, 397 "dump frequency for %s:%s is non-zero", 398 dp->d_host->h_hostname, dp->d_dev); 399 } 400 401 /* 402 * "opts" 403 */ 404 if (!ISSET(dp->d_mask, DF_OPTS)) 405 set_disk_fs(dp, DF_OPTS, strdup("rw,defaults")); 406 407 } 408 } 409 } 410 411 412 static void 413 fixup_required_mount_info(fsmount *fp, dict_ent *de) 414 { 415 if (!ISSET(fp->f_mask, FM_FROM)) { 416 if (de->de_count != 1) { 417 lerror(fp->f_ioloc, "ambiguous mount: %s is a replicated filesystem", fp->f_volname); 418 } else { 419 dict_data *dd; 420 fsi_mount *mp = NULL; 421 dd = AM_FIRST(dict_data, &de->de_q); 422 mp = (fsi_mount *) dd->dd_data; 423 if (!mp) 424 abort(); 425 fp->f_ref = mp; 426 set_fsmount(fp, FM_FROM, mp->m_dk->d_host->h_hostname); 427 fsi_log("set: %s comes from %s", fp->f_volname, fp->f_from); 428 } 429 } 430 431 if (!ISSET(fp->f_mask, FM_FSTYPE)) { 432 set_fsmount(fp, FM_FSTYPE, strdup("nfs")); 433 fsi_log("set: fstype is %s", fp->f_fstype); 434 } 435 436 if (!ISSET(fp->f_mask, FM_OPTS)) { 437 set_fsmount(fp, FM_OPTS, strdup("rw,nosuid,grpid,defaults")); 438 fsi_log("set: opts are %s", fp->f_opts); 439 } 440 441 if (!ISSET(fp->f_mask, FM_LOCALNAME)) { 442 if (fp->f_ref) { 443 set_fsmount(fp, FM_LOCALNAME, strdup(fp->f_volname)); 444 fsi_log("set: localname is %s", fp->f_localname); 445 } else { 446 lerror(fp->f_ioloc, "cannot determine localname since volname %s is not uniquely defined", fp->f_volname); 447 } 448 } 449 } 450 451 452 /* 453 * For each disk on a host 454 * analyze the mount information 455 * and fill in any derivable 456 * details. 457 */ 458 static void 459 analyze_drives(host *hp) 460 { 461 qelem *q = hp->h_disk_fs; 462 disk_fs *dp; 463 464 ITER(dp, disk_fs, q) { 465 int req; 466 fsi_log("Disk %s:", dp->d_dev); 467 dp->d_host = hp; 468 fixup_required_disk_info(dp); 469 req = ~dp->d_mask & DF_REQUIRED; 470 if (req) 471 show_required(dp->d_ioloc, req, dp->d_dev, hp->h_hostname, disk_fs_strings); 472 analyze_dkmounts(dp, dp->d_mount); 473 } 474 } 475 476 477 /* 478 * Check that all static mounts make sense and 479 * that the source volumes exist. 480 */ 481 static void 482 analyze_mounts(host *hp) 483 { 484 qelem *q = hp->h_mount; 485 fsmount *fp; 486 int netbootp = 0; 487 488 ITER(fp, fsmount, q) { 489 char *p; 490 char *nn = strdup(fp->f_volname); 491 int req; 492 dict_ent *de = (dict_ent *) NULL; 493 int found = 0; 494 int matched = 0; 495 496 if (ISSET(fp->f_mask, FM_DIRECT)) { 497 found = 1; 498 matched = 1; 499 } else 500 do { 501 p = NULL; 502 de = find_volname(nn); 503 fsi_log("Mount: %s (trying %s)", fp->f_volname, nn); 504 505 if (de) { 506 found = 1; 507 508 /* 509 * Check that the from field is really exporting 510 * the filesystem requested. 511 * LBL: If fake mount, then don't care about 512 * consistency check. 513 */ 514 if (ISSET(fp->f_mask, FM_FROM) && !ISSET(fp->f_mask, FM_DIRECT)) { 515 dict_data *dd; 516 fsi_mount *mp2 = NULL; 517 518 ITER(dd, dict_data, &de->de_q) { 519 fsi_mount *mp = (fsi_mount *) dd->dd_data; 520 521 if (fp->f_from && 522 STREQ(mp->m_dk->d_host->h_hostname, fp->f_from)) { 523 mp2 = mp; 524 break; 525 } 526 } 527 528 if (mp2) { 529 fp->f_ref = mp2; 530 matched = 1; 531 break; 532 } 533 } else { 534 matched = 1; 535 break; 536 } 537 } 538 p = strrchr(nn, '/'); 539 if (p) 540 *p = '\0'; 541 } while (de && p); 542 XFREE(nn); 543 544 if (!found) { 545 lerror(fp->f_ioloc, "volname %s unknown", fp->f_volname); 546 } else if (matched) { 547 548 if (de) 549 fixup_required_mount_info(fp, de); 550 req = ~fp->f_mask & FM_REQUIRED; 551 if (req) { 552 show_required(fp->f_ioloc, req, fp->f_volname, hp->h_hostname, 553 fsmount_strings); 554 } else if (STREQ(fp->f_localname, "/")) { 555 hp->h_netroot = fp; 556 netbootp |= FM_NETROOT; 557 } else if (STREQ(fp->f_localname, "swap")) { 558 hp->h_netswap = fp; 559 netbootp |= FM_NETSWAP; 560 } 561 562 } else { 563 lerror(fp->f_ioloc, "volname %s not exported from %s", fp->f_volname, 564 fp->f_from ? fp->f_from : "anywhere"); 565 } 566 } 567 568 if (netbootp && (netbootp != FM_NETBOOT)) 569 lerror(hp->h_ioloc, "network booting requires both root and swap areas"); 570 } 571 572 573 void 574 analyze_hosts(qelem *q) 575 { 576 host *hp; 577 578 show_area_being_processed("analyze hosts", 5); 579 580 /* 581 * Check all drives 582 */ 583 ITER(hp, host, q) { 584 fsi_log("disks on host %s", hp->h_hostname); 585 show_new("ana-host"); 586 hp->h_hostpath = compute_hostpath(hp->h_hostname); 587 588 if (hp->h_disk_fs) 589 analyze_drives(hp); 590 591 } 592 593 show_area_being_processed("analyze mounts", 5); 594 595 /* 596 * Check static mounts 597 */ 598 ITER(hp, host, q) { 599 fsi_log("mounts on host %s", hp->h_hostname); 600 show_new("ana-mount"); 601 if (hp->h_mount) 602 analyze_mounts(hp); 603 604 } 605 } 606 607 608 /* 609 * Check an automount request 610 */ 611 static void 612 analyze_automount(automount *ap) 613 { 614 dict_ent *de = find_volname(ap->a_volname); 615 616 if (de) { 617 ap->a_mounted = de; 618 } else { 619 if (STREQ(ap->a_volname, ap->a_name)) 620 lerror(ap->a_ioloc, "unknown volname %s automounted", ap->a_volname); 621 else 622 lerror(ap->a_ioloc, "unknown volname %s automounted on %s", ap->a_volname, ap->a_name); 623 } 624 } 625 626 627 static void 628 analyze_automount_tree(qelem *q, char *pref, int lvl) 629 { 630 automount *ap; 631 632 ITER(ap, automount, q) { 633 char nname[1024]; 634 635 if (lvl > 0 || ap->a_mount) 636 if (ap->a_name[1] && strchr(ap->a_name + 1, '/')) 637 lerror(ap->a_ioloc, "not allowed '/' in a directory name"); 638 xsnprintf(nname, sizeof(nname), "%s/%s", pref, ap->a_name); 639 XFREE(ap->a_name); 640 ap->a_name = strdup(nname[1] == '/' ? nname + 1 : nname); 641 fsi_log("automount point %s:", ap->a_name); 642 show_new("ana-automount"); 643 644 if (ap->a_mount) { 645 analyze_automount_tree(ap->a_mount, ap->a_name, lvl + 1); 646 } else if (ap->a_hardwiredfs) { 647 fsi_log("\thardwired from %s to %s", ap->a_volname, ap->a_hardwiredfs); 648 } else if (ap->a_volname) { 649 fsi_log("\tautomount from %s", ap->a_volname); 650 analyze_automount(ap); 651 } else if (ap->a_symlink) { 652 fsi_log("\tsymlink to %s", ap->a_symlink); 653 } else { 654 ap->a_volname = strdup(ap->a_name); 655 fsi_log("\timplicit automount from %s", ap->a_volname); 656 analyze_automount(ap); 657 } 658 } 659 } 660 661 662 void 663 analyze_automounts(qelem *q) 664 { 665 auto_tree *tp; 666 667 show_area_being_processed("analyze automount", 5); 668 669 /* 670 * q is a list of automounts 671 */ 672 ITER(tp, auto_tree, q) 673 analyze_automount_tree(tp->t_mount, "", 0); 674 } 675