1 /*
2  * Copyright (c) 1989 Jan-Simon Pendry
3  * Copyright (c) 1989 Imperial College of Science, Technology & Medicine
4  * Copyright (c) 1989, 1993
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * This code is derived from software contributed to Berkeley by
8  * Jan-Simon Pendry at Imperial College, London.
9  *
10  * %sccs.include.redist.c%
11  *
12  *	@(#)fsi_analyze.c	8.1 (Berkeley) 06/06/93
13  *
14  * $Id: fsi_analyze.c,v 5.2.2.1 1992/02/09 15:09:41 jsp beta $
15  *
16  */
17 
18 /*
19  * Analyze filesystem declarations
20  *
21  * Note: most of this is magic!
22  */
23 
24 #include "../fsinfo/fsinfo.h"
25 
26 char *disk_fs_strings[] = {
27 	"fstype", "opts", "dumpset", "passno", "freq", "mount", "log", 0,
28 };
29 
30 char *mount_strings[] = {
31 	"volname", "exportfs", 0,
32 };
33 
34 char *fsmount_strings[] = {
35 	"as", "volname", "fstype", "opts", "from", 0,
36 };
37 
38 char *host_strings[] = {
39 	"host", "netif", "config", "arch", "cluster", "os", 0,
40 };
41 
42 char *ether_if_strings[] = {
43 	"inaddr", "netmask", "hwaddr", 0,
44 };
45 
46 /*
47  * Strip off the trailing part of a domain
48  * to produce a short-form domain relative
49  * to the local host domain.
50  * Note that this has no effect if the domain
51  * names do not have the same number of
52  * components.  If that restriction proves
53  * to be a problem then the loop needs recoding
54  * to skip from right to left and do partial
55  * matches along the way -- ie more expensive.
56  */
57 void domain_strip(otherdom, localdom)
58 char *otherdom, *localdom;
59 {
60 #ifdef PARTIAL_DOMAINS
61         char *p1 = otherdom-1;
62 	char *p2 = localdom-1;
63 
64         do {
65                 if (p1 = strchr(p1+1, '.'))
66                 if (p2 = strchr(p2+1, '.'))
67                 if (STREQ(p1+1, p2+1)) {
68                         *p1 = '\0';
69                         break;
70                 }
71         } while (p1 && p2);
72 #else
73 	char *p1, *p2;
74 
75 	if ((p1 = strchr(otherdom, '.')) &&
76 			(p2 = strchr(localdom, '.')) &&
77 			(strcmp(p1+1, p2+1) == 0))
78 		*p1 = '\0';
79 #endif /* PARTIAL_DOMAINS */
80 }
81 
82 /*
83  * Take a little-endian domain name and
84  * transform into a big-endian Un*x pathname.
85  * For example: kiska.doc.ic -> ic/doc/kiska
86  */
87 static char *compute_hostpath(hn)
88 char *hn;
89 {
90 	char *p = strdup(hn);
91 	char *d;
92 	char path[MAXPATHLEN];
93 
94 	domain_strip(p, hostname);
95 	path[0] = '\0';
96 
97 	do {
98 		d = strrchr(p, '.');
99 		if (d) {
100 			*d = 0;
101 			strcat(path, d+1);
102 			strcat(path, "/");
103 		} else {
104 			strcat(path, p);
105 		}
106 	} while (d);
107 
108 	log("hostpath of '%s' is '%s'", hn, path);
109 
110 	strcpy(p, path);
111 	return p;
112 }
113 
114 static dict_ent *find_volname(nn)
115 char *nn;
116 {
117 	dict_ent *de;
118 	char *p = strdup(nn);
119 	char *q;
120 
121 	do {
122 		log("Searching for volname %s", p);
123 		de = dict_locate(dict_of_volnames, p);
124 		q = strrchr(p, '/');
125 		if (q) *q = '\0';
126 	} while (!de && q);
127 
128 	free(p);
129 	return de;
130 }
131 
132 static show_required(l, mask, info, hostname, strings)
133 ioloc *l;
134 int mask;
135 char *info;
136 char *hostname;
137 char *strings[];
138 {
139 	int i;
140 	log("mask left for %s:%s is %#x", hostname, info, mask);
141 
142 	for (i = 0; strings[i]; i++)
143 		if (ISSET(mask, i))
144 			lerror(l, "%s:%s needs field \"%s\"", hostname, info, strings[i]);
145 }
146 
147 /*
148  * Check and fill in "exportfs" details.
149  * Make sure the m_exported field references
150  * the most local node with an "exportfs" entry.
151  */
152 static int check_exportfs(q, e)
153 qelem *q;
154 mount *e;
155 {
156 	mount *mp;
157 	int errors = 0;
158 
159 	ITER(mp, mount, q) {
160 		if (ISSET(mp->m_mask, DM_EXPORTFS)) {
161 			if (e)
162 				lwarning(mp->m_ioloc, "%s has duplicate exportfs data", mp->m_name);
163 			mp->m_exported = mp;
164 			if (!ISSET(mp->m_mask, DM_VOLNAME))
165 				set_mount(mp, DM_VOLNAME, strdup(mp->m_name));
166 		} else {
167 			mp->m_exported = e;
168 		}
169 
170 		/*
171 		 * Recursively descend the mount tree
172 		 */
173 		if (mp->m_mount)
174 			errors += check_exportfs(mp->m_mount, mp->m_exported);
175 
176 		/*
177 		 * If a volume name has been specified, but this node and none
178 		 * of its parents has been exported, report an error.
179 		 */
180 		if (ISSET(mp->m_mask, DM_VOLNAME) && !mp->m_exported) {
181 			lerror(mp->m_ioloc, "%s has a volname but no exportfs data", mp->m_name);
182 			errors++;
183 		}
184 	}
185 
186 	return errors;
187 }
188 
189 static int analyze_dkmount_tree(q, parent, dk)
190 qelem *q;
191 mount *parent;
192 disk_fs *dk;
193 {
194 	mount *mp;
195 	int errors = 0;
196 
197 	ITER(mp, mount, q) {
198 		log("Mount %s:", mp->m_name);
199 		if (parent) {
200 			char n[MAXPATHLEN];
201 			sprintf(n, "%s/%s", parent->m_name, mp->m_name);
202 			if (*mp->m_name == '/')
203 				lerror(mp->m_ioloc, "sub-directory %s of %s starts with '/'", mp->m_name, parent->m_name);
204 			else if (STREQ(mp->m_name, "default"))
205 				lwarning(mp->m_ioloc, "sub-directory of %s is named \"default\"", parent->m_name);
206 			log("Changing name %s to %s", mp->m_name, n);
207 			free(mp->m_name);
208 			mp->m_name = strdup(n);
209 		}
210 		mp->m_name_len = strlen(mp->m_name);
211 		mp->m_parent = parent;
212 		mp->m_dk = dk;
213 		if (mp->m_mount)
214 			analyze_dkmount_tree(mp->m_mount, mp, dk);
215 	}
216 
217 	return errors;
218 }
219 
220 /*
221  * The mount tree is a singleton list
222  * containing the top-level mount
223  * point for a disk.
224  */
225 static int analyze_dkmounts(dk, q)
226 disk_fs *dk;
227 qelem *q;
228 {
229 	int errors = 0;
230 	mount *mp, *mp2 = 0;
231 	int i = 0;
232 
233 	/*
234 	 * First scan the list of subdirs to make
235 	 * sure there is only one - and remember it
236 	 */
237 	if (q) {
238 		ITER(mp, mount, q) {
239 			mp2 = mp;
240 			i++;
241 		}
242 	}
243 
244 	/*
245 	 * Check...
246 	 */
247 	if (i < 1) {
248 		lerror(dk->d_ioloc, "%s:%s has no mount point", dk->d_host->h_hostname, dk->d_dev);
249 		return 1;
250 	}
251 	if (i > 1) {
252 		lerror(dk->d_ioloc, "%s:%s has more than one mount point", dk->d_host->h_hostname, dk->d_dev);
253 		errors++;
254 	}
255 	/*
256 	 * Now see if a default mount point is required
257 	 */
258 	if (STREQ(mp2->m_name, "default")) {
259 		if (ISSET(mp2->m_mask, DM_VOLNAME)) {
260 			char nbuf[1024];
261 			compute_automount_point(nbuf, dk->d_host, mp2->m_volname);
262 			free(mp2->m_name);
263 			mp2->m_name = strdup(nbuf);
264 			log("%s:%s has default mount on %s", dk->d_host->h_hostname, dk->d_dev, mp2->m_name);
265 		} else {
266 			lerror(dk->d_ioloc, "no volname given for %s:%s", dk->d_host->h_hostname, dk->d_dev);
267 			errors++;
268 		}
269 	}
270 	/*
271 	 * Fill in the disk mount point
272 	 */
273 	if (!errors && mp2 && mp2->m_name)
274 		dk->d_mountpt = strdup(mp2->m_name);
275 	else
276 		dk->d_mountpt = strdup("error");
277 
278 	/*
279 	 * Analyze the mount tree
280 	 */
281 	errors += analyze_dkmount_tree(q, 0, dk);
282 
283 	/*
284 	 * Analyze the export tree
285 	 */
286 	errors += check_exportfs(q, 0);
287 
288 	return errors;
289 }
290 
291 static void fixup_required_disk_info(dp)
292 disk_fs *dp;
293 {
294 	/*
295 	 * "fstype"
296 	 */
297 	if (ISSET(dp->d_mask, DF_FSTYPE)) {
298 		if (STREQ(dp->d_fstype, "swap")) {
299 			/*
300 			 * Fixup for a swap device
301 			 */
302 			if (!ISSET(dp->d_mask, DF_PASSNO)) {
303 				dp->d_passno = 0;
304 				BITSET(dp->d_mask, DF_PASSNO);
305 			} else if (dp->d_freq != 0) {
306 				lwarning(dp->d_ioloc,
307 					"Pass number for %s:%s is non-zero",
308 					dp->d_host->h_hostname, dp->d_dev);
309 			}
310 
311 			/*
312 			 * "freq"
313 			 */
314 			if (!ISSET(dp->d_mask, DF_FREQ)) {
315 				dp->d_freq = 0;
316 				BITSET(dp->d_mask, DF_FREQ);
317 			} else if (dp->d_freq != 0) {
318 				lwarning(dp->d_ioloc,
319 					"dump frequency for %s:%s is non-zero",
320 					dp->d_host->h_hostname, dp->d_dev);
321 			}
322 
323 			/*
324 			 * "opts"
325 			 */
326 			if (!ISSET(dp->d_mask, DF_OPTS))
327 				set_disk_fs(dp, DF_OPTS, strdup("swap"));
328 
329 			/*
330 			 * "mount"
331 			 */
332 			if (!ISSET(dp->d_mask, DF_MOUNT)) {
333 				qelem *q = new_que();
334 				mount *m = new_mount();
335 				m->m_name = strdup("swap");
336 				m->m_mount = new_que();
337 				ins_que(&m->m_q, q->q_back);
338 				dp->d_mount = q;
339 				BITSET(dp->d_mask, DF_MOUNT);
340 			} else {
341 				lerror(dp->d_ioloc, "%s: mount field specified for swap partition", dp->d_host->h_hostname);
342 			}
343 		} else if (STREQ(dp->d_fstype, "export")) {
344 			/*
345 			 * "passno"
346 			 */
347 			if (!ISSET(dp->d_mask, DF_PASSNO)) {
348 				dp->d_passno = 0;
349 				BITSET(dp->d_mask, DF_PASSNO);
350 			} else if (dp->d_passno != 0) {
351 				lwarning(dp->d_ioloc,
352 					"pass number for %s:%s is non-zero",
353 					dp->d_host->h_hostname, dp->d_dev);
354 			}
355 
356 			/*
357 			 * "freq"
358 			 */
359 			if (!ISSET(dp->d_mask, DF_FREQ)) {
360 				dp->d_freq = 0;
361 				BITSET(dp->d_mask, DF_FREQ);
362 			} else if (dp->d_freq != 0) {
363 				lwarning(dp->d_ioloc,
364 					"dump frequency for %s:%s is non-zero",
365 					dp->d_host->h_hostname, dp->d_dev);
366 			}
367 
368 			/*
369 			 * "opts"
370 			 */
371 			if (!ISSET(dp->d_mask, DF_OPTS))
372 				set_disk_fs(dp, DF_OPTS, strdup("rw,defaults"));
373 
374 		}
375 	}
376 }
377 
378 static void fixup_required_mount_info(fp, de)
379 fsmount *fp;
380 dict_ent *de;
381 {
382 	if (!ISSET(fp->f_mask, FM_FROM)) {
383 		if (de->de_count != 1) {
384 			lerror(fp->f_ioloc, "ambiguous mount: %s is a replicated filesystem", fp->f_volname);
385 		} else {
386 			dict_data *dd;
387 			mount *mp = 0;
388 			ITER(dd, dict_data, &de->de_q) {
389 				mp = (mount *) dd->dd_data;
390 				break;
391 			}
392 			if (!mp)
393 				abort();
394 			fp->f_ref = mp;
395 			set_fsmount(fp, FM_FROM, mp->m_dk->d_host->h_hostname);
396 			log("set: %s comes from %s", fp->f_volname, fp->f_from);
397 		}
398 	}
399 
400 	if (!ISSET(fp->f_mask, FM_FSTYPE)) {
401 		set_fsmount(fp, FM_FSTYPE, strdup("nfs"));
402 		log("set: fstype is %s", fp->f_fstype);
403 	}
404 
405 	if (!ISSET(fp->f_mask, FM_OPTS)) {
406 		set_fsmount(fp, FM_OPTS, strdup("rw,nosuid,grpid,defaults"));
407 		log("set: opts are %s", fp->f_opts);
408 	}
409 
410 	if (!ISSET(fp->f_mask, FM_LOCALNAME)) {
411 		if (fp->f_ref) {
412 			set_fsmount(fp, FM_LOCALNAME, strdup(fp->f_volname));
413 			log("set: localname is %s", fp->f_localname);
414 		} else {
415 			lerror(fp->f_ioloc, "cannot determine localname since volname %s is not uniquely defined", fp->f_volname);
416 		}
417 	}
418 }
419 
420 /*
421  * For each disk on a host
422  * analyze the mount information
423  * and fill in any derivable
424  * details.
425  */
426 static void analyze_drives(hp)
427 host *hp;
428 {
429 	qelem *q = hp->h_disk_fs;
430 	disk_fs *dp;
431 
432 	ITER(dp, disk_fs, q) {
433 		int req;
434 		log("Disk %s:", dp->d_dev);
435 		dp->d_host = hp;
436 		fixup_required_disk_info(dp);
437 		req = ~dp->d_mask & DF_REQUIRED;
438 		if (req)
439 			show_required(dp->d_ioloc, req, dp->d_dev, hp->h_hostname, disk_fs_strings);
440 		analyze_dkmounts(dp, dp->d_mount);
441 	}
442 }
443 
444 /*
445  * Check that all static mounts make sense and
446  * that the source volumes exist.
447  */
448 static void analyze_mounts(hp)
449 host *hp;
450 {
451 	qelem *q = hp->h_mount;
452 	fsmount *fp;
453 	int netbootp = 0;
454 
455 	ITER(fp, fsmount, q) {
456 		char *p;
457 		char *nn = strdup(fp->f_volname);
458 		int req;
459 		dict_ent *de;
460 		int found = 0;
461 		int matched = 0;
462 		do {
463 			p = 0;
464 			de = find_volname(nn);
465 			log("Mount: %s (trying %s)", fp->f_volname, nn);
466 
467 			if (de) {
468 				found = 1;
469 				/*
470 				 * Check that the from field is really exporting
471 				 * the filesystem requested.
472 				 */
473 				if (ISSET(fp->f_mask, FM_FROM)) {
474 					dict_data *dd;
475 					mount *mp2 = 0;
476 					ITER(dd, dict_data, &de->de_q) {
477 						mount *mp = (mount *) dd->dd_data;
478 						if (STREQ(mp->m_dk->d_host->h_hostname, fp->f_from)) {
479 							mp2 = mp;
480 							break;
481 						}
482 					}
483 
484 					if (mp2) {
485 						fp->f_ref = mp2;
486 						matched = 1;
487 						break;
488 					}
489 				} else {
490 					matched = 1;
491 					break;
492 				}
493 			}
494 			p = strrchr(nn, '/');
495 			if (p)
496 				*p = 0;
497 		} while (de && p);
498 		free(nn);
499 
500 		if (!found) {
501 			lerror(fp->f_ioloc, "volname %s unknown", fp->f_volname);
502 		} else if (matched) {
503 			fixup_required_mount_info(fp, de);
504 			req = ~fp->f_mask & FM_REQUIRED;
505 			if (req) {
506 				show_required(fp->f_ioloc, req, fp->f_volname, hp->h_hostname,
507 					fsmount_strings);
508 			} else if (strcmp(fp->f_localname, "/") == 0) {
509 				hp->h_netroot = fp;
510 				netbootp |= FM_NETROOT;
511 			} else if (strcmp(fp->f_localname, "swap") == 0) {
512 				hp->h_netswap = fp;
513 				netbootp |= FM_NETSWAP;
514 			}
515 		} else {
516 			lerror(fp->f_ioloc, "volname %s not exported from %s", fp->f_volname,
517 				fp->f_from ? fp->f_from : "anywhere");
518 		}
519 	}
520 
521 	if (netbootp && (netbootp != FM_NETBOOT))
522 		lerror(hp->h_ioloc, "network booting requires both root and swap areas");
523 }
524 
525 void analyze_hosts(q)
526 qelem *q;
527 {
528 	host *hp;
529 
530 	show_area_being_processed("analyze hosts", 5);
531 
532 	/*
533 	 * Check all drives
534 	 */
535 	ITER(hp, host, q) {
536 		log("disks on host %s", hp->h_hostname);
537 		show_new("ana-host");
538 		hp->h_hostpath = compute_hostpath(hp->h_hostname);
539 
540 		if (hp->h_disk_fs)
541 			analyze_drives(hp);
542 
543 	}
544 
545 	show_area_being_processed("analyze mounts", 5);
546 
547 	/*
548 	 * Check static mounts
549 	 */
550 	ITER(hp, host, q) {
551 		log("mounts on host %s", hp->h_hostname);
552 		show_new("ana-mount");
553 		if (hp->h_mount)
554 			analyze_mounts(hp);
555 
556 	}
557 }
558 
559 /*
560  * Check an automount request
561  */
562 static void analyze_automount(ap)
563 automount *ap;
564 {
565 	dict_ent *de = find_volname(ap->a_volname);
566 	if (de) {
567 		ap->a_mounted = de;
568 	} else {
569 		if (STREQ(ap->a_volname, ap->a_name))
570 			lerror(ap->a_ioloc, "unknown volname %s automounted", ap->a_volname);
571 		else
572 			lerror(ap->a_ioloc, "unknown volname %s automounted on %s", ap->a_volname, ap->a_name);
573 	}
574 }
575 
576 static void analyze_automount_tree(q, pref, lvl)
577 qelem *q;
578 char *pref;
579 int lvl;
580 {
581 	automount *ap;
582 
583 	ITER(ap, automount, q) {
584 		char nname[1024];
585 		if (lvl > 0 || ap->a_mount)
586 			if (ap->a_name[1] && strchr(ap->a_name+1, '/'))
587 				lerror(ap->a_ioloc, "not allowed '/' in a directory name");
588 		sprintf(nname, "%s/%s", pref, ap->a_name);
589 		free(ap->a_name);
590 		ap->a_name = strdup(nname[1] == '/' ? nname+1 : nname);
591 		log("automount point %s:", ap->a_name);
592 		show_new("ana-automount");
593 		if (ap->a_mount) {
594 			analyze_automount_tree(ap->a_mount, ap->a_name, lvl+1);
595 		} else if (ap->a_volname) {
596 			log("\tautomount from %s", ap->a_volname);
597 			analyze_automount(ap);
598 		} else if (ap->a_symlink) {
599 			log("\tsymlink to %s", ap->a_symlink);
600 		} else {
601 			ap->a_volname = strdup(ap->a_name);
602 			log("\timplicit automount from %s", ap->a_volname);
603 			analyze_automount(ap);
604 		}
605 	}
606 }
607 
608 void analyze_automounts(q)
609 qelem *q;
610 {
611 	auto_tree *tp;
612 
613 	show_area_being_processed("analyze automount", 5);
614 	/*
615 	 * q is a list of automounts
616 	 */
617 	ITER(tp, auto_tree, q)
618 		analyze_automount_tree(tp->t_mount, "", 0);
619 }
620