Home | History | Annotate | Line # | Download | only in fsinfo
fsi_analyze.c revision 1.1
      1 /*	$NetBSD: fsi_analyze.c,v 1.1 2008/09/19 20:07:21 christos Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 1997-2007 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