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