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