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 
53 char *disk_fs_strings[] =
54 {
55   "fstype", "opts", "dumpset", "passno", "freq", "mount", "log", NULL,
56 };
57 
58 char *mount_strings[] =
59 {
60   "volname", "exportfs", NULL,
61 };
62 
63 char *fsmount_strings[] =
64 {
65   "as", "volname", "fstype", "opts", "from", NULL,
66 };
67 
68 char *host_strings[] =
69 {
70   "host", "netif", "config", "arch", "cluster", "os", NULL,
71 };
72 
73 char *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  */
90 void
domain_strip(char * otherdom,char * localdom)91 domain_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  */
107 static char *
compute_hostpath(char * hn)108 compute_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 
136 static dict_ent *
find_volname(char * nn)137 find_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 
156 static void
show_required(ioloc * l,int mask,char * info,char * hostname,char * strings[])157 show_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  */
173 static int
check_exportfs(qelem * q,fsi_mount * e)174 check_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 
210 static int
analyze_dkmount_tree(qelem * q,fsi_mount * parent,disk_fs * dk)211 analyze_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  */
246 static int
analyze_dkmounts(disk_fs * dk,qelem * q)247 analyze_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 
315 static void
fixup_required_disk_info(disk_fs * dp)316 fixup_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 
406 static void
fixup_required_mount_info(fsmount * fp,dict_ent * de)407 fixup_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  */
452 static void
analyze_drives(host * hp)453 analyze_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  */
475 static void
analyze_mounts(host * hp)476 analyze_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 
567 void
analyze_hosts(qelem * q)568 analyze_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  */
605 static void
analyze_automount(automount * ap)606 analyze_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 
621 static void
analyze_automount_tree(qelem * q,char * pref,int lvl)622 analyze_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 
656 void
analyze_automounts(qelem * q)657 analyze_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