]> CyberLeo.Net >> Repos - FreeBSD/releng/9.2.git/blob - contrib/amd/fsinfo/fsi_analyze.c
- Copy stable/9 to releng/9.2 as part of the 9.2-RELEASE cycle.
[FreeBSD/releng/9.2.git] / contrib / amd / fsinfo / fsi_analyze.c
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
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 *
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 *
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
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
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
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
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
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
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
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
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
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
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
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
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 }