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