Merge from vendor branch LESS:
[dragonfly.git] / contrib / amd / fsinfo / fsi_analyze.c
1 /*
2  * Copyright (c) 1997-1999 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  *      %W% (Berkeley) %G%
40  *
41  * $Id: fsi_analyze.c,v 1.2 1999/01/10 21:54:27 ezk Exp $
42  *
43  */
44
45 /*
46  * Analyze filesystem declarations
47  *
48  * Note: most of this is magic!
49  */
50
51 #ifdef HAVE_CONFIG_H
52 # include <config.h>
53 #endif /* HAVE_CONFIG_H */
54 #include <am_defs.h>
55 #include <fsi_data.h>
56 #include <fsinfo.h>
57
58 char *disk_fs_strings[] =
59 {
60   "fstype", "opts", "dumpset", "passno", "freq", "mount", "log", 0,
61 };
62
63 char *mount_strings[] =
64 {
65   "volname", "exportfs", 0,
66 };
67
68 char *fsmount_strings[] =
69 {
70   "as", "volname", "fstype", "opts", "from", 0,
71 };
72
73 char *host_strings[] =
74 {
75   "host", "netif", "config", "arch", "cluster", "os", 0,
76 };
77
78 char *ether_if_strings[] =
79 {
80   "inaddr", "netmask", "hwaddr", 0,
81 };
82
83
84 /*
85  * Strip off the trailing part of a domain
86  * to produce a short-form domain relative
87  * to the local host domain.
88  * Note that this has no effect if the domain
89  * names do not have the same number of
90  * components.  If that restriction proves
91  * to be a problem then the loop needs recoding
92  * to skip from right to left and do partial
93  * matches along the way -- ie more expensive.
94  */
95 void
96 domain_strip(char *otherdom, char *localdom)
97 {
98   char *p1, *p2;
99
100   if ((p1 = strchr(otherdom, '.')) &&
101       (p2 = strchr(localdom, '.')) &&
102       STREQ(p1 + 1, p2 + 1))
103     *p1 = '\0';
104 }
105
106
107 /*
108  * Take a little-endian domain name and
109  * transform into a big-endian Un*x pathname.
110  * For example: kiska.doc.ic -> ic/doc/kiska
111  */
112 static char *
113 compute_hostpath(char *hn)
114 {
115   char *p = strdup(hn);
116   char *d;
117   char path[MAXPATHLEN];
118
119   domain_strip(p, hostname);
120   path[0] = '\0';
121
122   do {
123     d = strrchr(p, '.');
124     if (d) {
125       *d = 0;
126       strcat(path, d + 1);
127       strcat(path, "/");
128     } else {
129       strcat(path, p);
130     }
131   } while (d);
132
133   log("hostpath of '%s' is '%s'", hn, path);
134
135   strcpy(p, path);
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     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   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     log("Mount %s:", mp->m_name);
222     if (parent) {
223       char n[MAXPATHLEN];
224       sprintf(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       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 (STREQ(mp2->m_name, "default")) {
285     if (ISSET(mp2->m_mask, DM_VOLNAME)) {
286       char nbuf[1024];
287       compute_automount_point(nbuf, dk->d_host, mp2->m_volname);
288       XFREE(mp2->m_name);
289       mp2->m_name = strdup(nbuf);
290       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       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     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     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       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     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         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 (STREQ(mp->m_dk->d_host->h_hostname, fp->f_from)) {
520                 mp2 = mp;
521                 break;
522               }
523             }
524
525             if (mp2) {
526               fp->f_ref = mp2;
527               matched = 1;
528               break;
529             }
530           } else {
531             matched = 1;
532             break;
533           }
534         }
535         p = strrchr(nn, '/');
536         if (p)
537           *p = 0;
538       } while (de && p);
539     XFREE(nn);
540
541     if (!found) {
542       lerror(fp->f_ioloc, "volname %s unknown", fp->f_volname);
543     } else if (matched) {
544
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     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     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     sprintf(nname, "%s/%s", pref, ap->a_name);
635     XFREE(ap->a_name);
636     ap->a_name = strdup(nname[1] == '/' ? nname + 1 : nname);
637     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       log("\thardwired from %s to %s", ap->a_volname, ap->a_hardwiredfs);
644     } else if (ap->a_volname) {
645       log("\tautomount from %s", ap->a_volname);
646       analyze_automount(ap);
647     } else if (ap->a_symlink) {
648       log("\tsymlink to %s", ap->a_symlink);
649     } else {
650       ap->a_volname = strdup(ap->a_name);
651       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 }