]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/amd/fsinfo/fsi_analyze.c
MFC r308493, r308619: Update amd from am-utils 6.1.5 to 6.2.
[FreeBSD/stable/10.git] / contrib / amd / fsinfo / fsi_analyze.c
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
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 *
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 *
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
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
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
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
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
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
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
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
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
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
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
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
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 }