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