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
domain_strip(char * otherdom,char * localdom)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 *
compute_hostpath(char * hn)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 *
find_volname(char * nn)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
show_required(ioloc * l,int mask,char * info,char * hostname,char * strings[])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
check_exportfs(qelem * q,fsi_mount * e)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
analyze_dkmount_tree(qelem * q,fsi_mount * parent,disk_fs * dk)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
analyze_dkmounts(disk_fs * dk,qelem * q)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
fixup_required_disk_info(disk_fs * dp)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
fixup_required_mount_info(fsmount * fp,dict_ent * de)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
analyze_drives(host * hp)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
analyze_mounts(host * hp)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
analyze_hosts(qelem * q)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
analyze_automount(automount * ap)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
analyze_automount_tree(qelem * q,char * pref,int lvl)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
analyze_automounts(qelem * q)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 }
669