Home | History | Annotate | Line # | Download | only in fsinfo
fsi_analyze.c revision 1.1.1.3
      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 
     55 char *disk_fs_strings[] =
     56 {
     57   "fstype", "opts", "dumpset", "passno", "freq", "mount", "log", NULL,
     58 };
     59 
     60 char *mount_strings[] =
     61 {
     62   "volname", "exportfs", NULL,
     63 };
     64 
     65 char *fsmount_strings[] =
     66 {
     67   "as", "volname", "fstype", "opts", "from", NULL,
     68 };
     69 
     70 char *host_strings[] =
     71 {
     72   "host", "netif", "config", "arch", "cluster", "os", NULL,
     73 };
     74 
     75 char *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  */
     92 void
     93 domain_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  */
    109 static char *
    110 compute_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 
    138 static dict_ent *
    139 find_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 
    158 static void
    159 show_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  */
    175 static int
    176 check_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 
    212 static int
    213 analyze_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  */
    248 static int
    249 analyze_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 
    317 static void
    318 fixup_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 
    408 static void
    409 fixup_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  */
    454 static void
    455 analyze_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  */
    477 static void
    478 analyze_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 
    569 void
    570 analyze_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  */
    607 static void
    608 analyze_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 
    623 static void
    624 analyze_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 
    658 void
    659 analyze_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