xref: /openbsd-src/usr.sbin/amd/amd/host_ops.c (revision bf0193d8cd0ca4e683146c29a671bf62f193ec92)
1 /*	$OpenBSD: host_ops.c,v 1.20 2021/10/21 10:55:56 deraadt Exp $	*/
2 
3 /*
4  * Copyright (c) 1990 Jan-Simon Pendry
5  * Copyright (c) 1990 Imperial College of Science, Technology & Medicine
6  * Copyright (c) 1990, 1993
7  *	The Regents of the University of California.  All rights reserved.
8  *
9  * This code is derived from software contributed to Berkeley by
10  * Jan-Simon Pendry at Imperial College, London.
11  *
12  * Redistribution and use in source and binary forms, with or without
13  * modification, are permitted provided that the following conditions
14  * are met:
15  * 1. Redistributions of source code must retain the above copyright
16  *    notice, this list of conditions and the following disclaimer.
17  * 2. Redistributions in binary form must reproduce the above copyright
18  *    notice, this list of conditions and the following disclaimer in the
19  *    documentation and/or other materials provided with the distribution.
20  * 3. Neither the name of the University nor the names of its contributors
21  *    may be used to endorse or promote products derived from this software
22  *    without specific prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34  * SUCH DAMAGE.
35  *
36  *	from: @(#)host_ops.c	8.1 (Berkeley) 6/6/93
37  */
38 
39 #include "am.h"
40 
41 #ifdef HAS_HOST
42 
43 #include "mount.h"
44 #include <sys/stat.h>
45 
46 /*
47  * NFS host file system.
48  * Mounts all exported filesystems from a given host.
49  * This has now degenerated into a mess but will not
50  * be rewritten.  Amd 6 will support the abstractions
51  * needed to make this work correctly.
52  */
53 
54 /*
55  * Define HOST_RPC_UDP to use dgram instead of stream RPC.
56  * Datagrams are generally much faster.
57  */
58 /*#define	HOST_RPC_UDP*/
59 
60 /*
61  * Define HOST_MKDIRS to make Amd automatically try
62  * to create the mount points.
63  */
64 #define HOST_MKDIRS
65 
66 /*
67  * Determine the mount point
68  */
69 #define MAKE_MNTPT(mntpt, ex, mf) { \
70 			if (strcmp((ex)->ex_dir, "/") == 0) \
71 				strlcpy((mntpt), (mf)->mf_mount, sizeof((mntpt))); \
72 			else \
73 				snprintf((mntpt), sizeof(mntpt), "%s%s", (mf)->mf_mount, (ex)->ex_dir); \
74 }
75 
76 /*
77  * Execute needs the same as NFS plus a helper command
78  */
79 static char *
host_match(am_opts * fo)80 host_match(am_opts *fo)
81 {
82 #ifdef HOST_EXEC
83 	if (!host_helper) {
84 		plog(XLOG_USER, "No host helper command given");
85 		return FALSE;
86 	}
87 #endif /* HOST_EXEC */
88 
89 	/*
90 	 * Make sure rfs is specified to keep nfs_match happy...
91 	 */
92 	if (!fo->opt_rfs)
93 		fo->opt_rfs = "/";
94 
95 	return (*nfs_ops.fs_match)(fo);
96 }
97 
98 static int
host_init(mntfs * mf)99 host_init(mntfs *mf)
100 {
101 	if (strchr(mf->mf_info, ':') == 0)
102 		return ENOENT;
103 	return 0;
104 }
105 
106 /*
107  * Two implementations:
108  * HOST_EXEC gets you the external version.  The program specified with
109  * the -h option is called.  The external program is not published...
110  * roll your own.
111  *
112  * Otherwise you get the native version.  Faster but makes the program
113  * bigger.
114  */
115 
116 #ifndef HOST_EXEC
117 
118 static bool_t
xdr_pri_free(xdrproc_t xdr_args,void * args_ptr)119 xdr_pri_free(xdrproc_t xdr_args, void *args_ptr)
120 {
121 	XDR xdr;
122 
123 	xdr.x_op = XDR_FREE;
124 	return ((*xdr_args)(&xdr, args_ptr));
125 }
126 
127 static int
do_mount(fhstatus * fhp,char * dir,char * fs_name,char * opts,mntfs * mf)128 do_mount(fhstatus *fhp, char *dir, char *fs_name, char *opts, mntfs *mf)
129 {
130 	struct stat stb;
131 
132 #ifdef DEBUG
133 	dlog("host: mounting fs %s on %s", fs_name, dir);
134 #endif /* DEBUG */
135 #ifdef HOST_MKDIRS
136 	(void) mkdirs(dir, 0555);
137 #endif /* HOST_MKDIRS */
138 	if (stat(dir, &stb) < 0 || (stb.st_mode & S_IFMT) != S_IFDIR) {
139 		plog(XLOG_ERROR, "No mount point for %s - skipping", dir);
140 		return ENOENT;
141 	}
142 
143 	return mount_nfs_fh(fhp, dir, fs_name, opts, mf);
144 }
145 
146 static int
sortfun(const void * arg1,const void * arg2)147 sortfun(const void *arg1, const void *arg2)
148 {
149 	const exports *a = arg1, *b = arg2;
150 	return strcmp((*a)->ex_dir, (*b)->ex_dir);
151 }
152 
153 /*
154  * Get filehandle
155  */
156 static int
fetch_fhandle(CLIENT * client,char * dir,fhstatus * fhp)157 fetch_fhandle(CLIENT *client, char *dir, fhstatus *fhp)
158 {
159 	struct timeval tv;
160 	enum clnt_stat clnt_stat;
161 
162 	/*
163 	 * Pick a number, any number...
164 	 */
165 	tv.tv_sec = 20;
166 	tv.tv_usec = 0;
167 
168 #ifdef DEBUG
169 	dlog("Fetching fhandle for %s", dir);
170 #endif /* DEBUG */
171 	/*
172 	 * Call the mount daemon on the remote host to
173 	 * get the filehandle.
174 	 */
175 	fhp->fhs_vers = MOUNTVERS;
176 	clnt_stat = clnt_call(client, MOUNTPROC_MNT, xdr_dirpath, &dir, xdr_fhstatus, fhp, tv);
177 	if (clnt_stat != RPC_SUCCESS) {
178 		char *msg = clnt_sperrno(clnt_stat);
179 		plog(XLOG_ERROR, "mountd rpc failed: %s", msg);
180 		return EIO;
181 	}
182 	/*
183 	 * Check status of filehandle
184 	 */
185 	if (fhp->fhs_stat) {
186 #ifdef DEBUG
187 		errno = fhp->fhs_stat;
188 		dlog("fhandle fetch failed: %m");
189 #endif /* DEBUG */
190 		return fhp->fhs_stat;
191 	}
192 	return 0;
193 }
194 
195 /*
196  * Scan mount table to see if something already mounted
197  */
198 static int
already_mounted(mntlist * mlist,char * dir)199 already_mounted(mntlist *mlist, char *dir)
200 {
201 	mntlist *ml;
202 
203 	for (ml = mlist; ml; ml = ml->mnext)
204 		if (strcmp(ml->mnt->mnt_dir, dir) == 0)
205 			return 1;
206 	return 0;
207 }
208 
209 /*
210  * Mount the export tree from a host
211  */
212 static int
host_fmount(mntfs * mf)213 host_fmount(mntfs *mf)
214 {
215 	struct timeval tv2;
216 	CLIENT *client;
217 	enum clnt_stat clnt_stat;
218 	int n_export;
219 	int j, k;
220 	exports exlist = 0, ex;
221 	exports *ep = 0;
222 	fhstatus *fp = 0;
223 	char *host = mf->mf_server->fs_host;
224 	int error = 0;
225 	struct sockaddr_in sin;
226 	int sock = RPC_ANYSOCK;
227 	int ok = FALSE;
228 	mntlist *mlist;
229 	char fs_name[PATH_MAX], *rfs_dir;
230 	char mntpt[PATH_MAX];
231 	struct timeval tv;
232 	tv.tv_sec = 10; tv.tv_usec = 0;
233 
234 	/*
235 	 * Read the mount list
236 	 */
237 	mlist = read_mtab(mf->mf_mount);
238 
239 	/*
240 	 * Take a copy of the server address
241 	 */
242 	sin = *mf->mf_server->fs_ip;
243 
244 	/*
245 	 * Zero out the port - make sure we recompute
246 	 */
247 	sin.sin_port = 0;
248 	/*
249 	 * Make a client end-point.
250 	 * Try TCP first
251 	 */
252 	if ((client = clnttcp_create(&sin, MOUNTPROG, MOUNTVERS, &sock, 0, 0)) == NULL &&
253 		(client = clntudp_create(&sin, MOUNTPROG, MOUNTVERS, tv, &sock)) == NULL) {
254 		plog(XLOG_ERROR, "Failed to make rpc connection to mountd on %s", host);
255 		error = EIO;
256 		goto out;
257 	}
258 
259 	if (!nfs_auth) {
260 		error = make_nfs_auth();
261 		if (error)
262 			goto out;
263 	}
264 
265 	client->cl_auth = nfs_auth;
266 
267 #ifdef DEBUG
268 	dlog("Fetching export list from %s", host);
269 #endif /* DEBUG */
270 
271 	/*
272 	 * Fetch the export list
273 	 */
274 	tv2.tv_sec = 10; tv2.tv_usec = 0;
275 	clnt_stat = clnt_call(client, MOUNTPROC_EXPORT, xdr_void, 0, xdr_exports, &exlist, tv2);
276 	if (clnt_stat != RPC_SUCCESS) {
277 		/*clnt_perror(client, "rpc");*/
278 		error = EIO;
279 		goto out;
280 	}
281 
282 	/*
283 	 * Figure out how many exports were returned
284 	 */
285 	for (n_export = 0, ex = exlist; ex; ex = ex->ex_next) {
286 		/*printf("export %s\n", ex->ex_dir);*/
287 		n_export++;
288 	}
289 #ifdef DEBUG
290 	/*dlog("%d exports returned", n_export);*/
291 #endif /* DEBUG */
292 
293 	/*
294 	 * Allocate an array of pointers into the list
295 	 * so that they can be sorted.  If the filesystem
296 	 * is already mounted then ignore it.
297 	 */
298 	ep = xreallocarray(NULL, n_export, sizeof *ep);
299 	for (j = 0, ex = exlist; ex; ex = ex->ex_next) {
300 		MAKE_MNTPT(mntpt, ex, mf);
301 		if (!already_mounted(mlist, mntpt))
302 			ep[j++] = ex;
303 	}
304 	n_export = j;
305 
306 	/*
307 	 * Sort into order.
308 	 * This way the mounts are done in order down the tree,
309 	 * instead of any random order returned by the mount
310 	 * daemon (the protocol doesn't specify...).
311 	 */
312 	qsort(ep, n_export, sizeof(exports), sortfun);
313 
314 	/*
315 	 * Allocate an array of filehandles
316 	 */
317 	fp = xreallocarray(NULL, n_export, sizeof *fp);
318 
319 	/*
320 	 * Try to obtain filehandles for each directory.
321 	 * If a fetch fails then just zero out the array
322 	 * reference but discard the error.
323 	 */
324 	for (j = k = 0; j < n_export; j++) {
325 		/* Check and avoid a duplicated export entry */
326 		if (j > k && ep[k] && strcmp(ep[j]->ex_dir, ep[k]->ex_dir) == 0) {
327 #ifdef DEBUG
328 			dlog("avoiding dup fhandle requested for %s", ep[j]->ex_dir);
329 #endif
330 			ep[j] = 0;
331 		} else {
332 			k = j;
333 			if ((error = fetch_fhandle(client, ep[j]->ex_dir, &fp[j])))
334 				ep[j] = 0;
335 		}
336 	}
337 
338 	/*
339 	 * Mount each filesystem for which we have a filehandle.
340 	 * If any of the mounts succeed then mark "ok" and return
341 	 * error code 0 at the end.  If they all fail then return
342 	 * the last error code.
343 	 */
344 	strlcpy(fs_name, mf->mf_info, sizeof(fs_name));
345 	if ((rfs_dir = strchr(fs_name, ':')) == (char *) 0) {
346 		plog(XLOG_FATAL, "host_fmount: mf_info has no colon");
347 		error = EINVAL;
348 		goto out;
349 	}
350 	++rfs_dir;
351 	for (j = 0; j < n_export; j++) {
352 		ex = ep[j];
353 		if (ex) {
354 			strlcpy(rfs_dir, ex->ex_dir, fs_name + sizeof fs_name - rfs_dir);
355 			MAKE_MNTPT(mntpt, ex, mf);
356 			if (do_mount(&fp[j], mntpt, fs_name, mf->mf_mopts, mf) == 0)
357 				ok = TRUE;
358 		}
359 	}
360 
361 	/*
362 	 * Clean up and exit
363 	 */
364 out:
365 	discard_mntlist(mlist);
366 	free(ep);
367 	free(fp);
368 	if (client)
369 		clnt_destroy(client);
370 	if (exlist)
371 		xdr_pri_free(xdr_exports, &exlist);
372 	if (ok)
373 		return 0;
374 	return error;
375 }
376 
377 /*
378  * Return true if pref is a directory prefix of dir.
379  *
380  * TODO:
381  * Does not work if pref is "/".
382  */
383 static int
directory_prefix(char * pref,char * dir)384 directory_prefix(char *pref, char *dir)
385 {
386 	int len = strlen(pref);
387 	if (strncmp(pref, dir, len) != 0)
388 		return FALSE;
389 	if (dir[len] == '/' || dir[len] == '\0')
390 		return TRUE;
391 	return FALSE;
392 }
393 
394 /*
395  * Unmount a mount tree
396  */
397 static int
host_fumount(mntfs * mf)398 host_fumount(mntfs *mf)
399 {
400 	mntlist *ml, *mprev;
401 	int xerror = 0;
402 
403 	/*
404 	 * Read the mount list
405 	 */
406 	mntlist *mlist = read_mtab(mf->mf_mount);
407 
408 	/*
409 	 * Reverse list...
410 	 */
411 	ml = mlist;
412 	mprev = 0;
413 	while (ml) {
414 		mntlist *ml2 = ml->mnext;
415 		ml->mnext = mprev;
416 		mprev = ml;
417 		ml = ml2;
418 	}
419 	mlist = mprev;
420 
421 	/*
422 	 * Unmount all filesystems...
423 	 */
424 	for (ml = mlist; ml && !xerror; ml = ml->mnext) {
425 		char *dir = ml->mnt->mnt_dir;
426 		if (directory_prefix(mf->mf_mount, dir)) {
427 			int error;
428 #ifdef DEBUG
429 			dlog("host: unmounts %s", dir);
430 #endif /* DEBUG */
431 			/*
432 			 * Unmount "dir"
433 			 */
434 			error = umount_fs(dir);
435 			/*
436 			 * Keep track of errors
437 			 */
438 			if (error) {
439 				if (!xerror)
440 					xerror = error;
441 				if (error != EBUSY) {
442 					errno = error;
443 					plog(XLOG_ERROR, "Tree unmount of %s failed: %m", ml->mnt->mnt_dir);
444 				}
445 			} else {
446 #ifdef HOST_MKDIRS
447 				(void) rmdirs(dir);
448 #endif /* HOST_MKDIRS */
449 			}
450 		}
451 	}
452 
453 	/*
454 	 * Throw away mount list
455 	 */
456 	discard_mntlist(mlist);
457 
458 	/*
459 	 * Try to remount, except when we are shutting down.
460 	 */
461 	if (xerror && amd_state != Finishing) {
462 		xerror = host_fmount(mf);
463 		if (!xerror) {
464 			/*
465 			 * Don't log this - it's usually too verbose
466 			plog(XLOG_INFO, "Remounted host %s", mf->mf_info);
467 			 */
468 			xerror = EBUSY;
469 		}
470 	}
471 	return xerror;
472 }
473 
474 /*
475  * Tell mountd we're done.
476  * This is not quite right, because we may still
477  * have other filesystems mounted, but the existing
478  * mountd protocol is badly broken anyway.
479  */
host_umounted(am_node * mp)480 static void host_umounted(am_node *mp)
481 {
482 }
483 
484 
485 #else /* HOST_EXEC */
486 
487 static int
host_exec(char * op,char * host,char * fs,char * opts)488 host_exec(char *op, char *host, char *fs, char *opts)
489 {
490 	int error;
491 	char *argv[7];
492 
493 	/*
494 	 * Build arg vector
495 	 */
496 	argv[0] = host_helper;
497 	argv[1] = host_helper;
498 	argv[2] = op;
499 	argv[3] = host;
500 	argv[4] = fs;
501 	argv[5] = opts && *opts ? opts : "rw,default";
502 	argv[6] = 0;
503 
504 	/*
505 	 * Put stdout to stderr
506 	 */
507 	(void) fclose(stdout);
508 	(void) dup(fileno(logfp));
509 	if (fileno(logfp) != fileno(stderr)) {
510 		(void) fclose(stderr);
511 		(void) dup(fileno(logfp));
512 	}
513 	/*
514 	 * Try the exec
515 	 */
516 #ifdef DEBUG
517 	Debug(D_FULL) {
518 		char **cp = argv;
519 		plog(XLOG_DEBUG, "executing (un)mount command...");
520 		while (*cp) {
521 			plog(XLOG_DEBUG, "arg[%d] = '%s'", cp-argv, *cp);
522 			cp++;
523 		}
524 	}
525 #endif /* DEBUG */
526 	if (argv[0] == 0 || argv[1] == 0) {
527 		errno = EINVAL;
528 		plog(XLOG_USER, "1st/2nd args missing to (un)mount program");
529 	} else {
530 		(void) execv(argv[0], argv+1);
531 	}
532 	/*
533 	 * Save error number
534 	 */
535 	error = errno;
536 	plog(XLOG_ERROR, "exec %s failed: %m", argv[0]);
537 
538 	/*
539 	 * Return error
540 	 */
541 	return error;
542 }
543 
544 static int
host_mount(am_node * mp)545 host_mount(am_node *mp)
546 {
547 	mntfs *mf = mp->am_mnt;
548 
549 	return host_exec("mount", mf->mf_server->fs_host, mf->mf_mount, mf->mf_opts);
550 }
551 
552 static int
host_umount(am_node * mp)553 host_umount(am_node *mp)
554 {
555 	mntfs *mf = mp->am_mnt;
556 
557 	return host_exec("unmount", mf->mf_server->fs_host, mf->mf_mount, "xxx");
558 }
559 
560 #endif /* HOST_EXEC */
561 
562 /*
563  * Ops structure
564  */
565 am_ops host_ops = {
566 	"host",
567 	host_match,
568 	host_init,
569 	auto_fmount,
570 	host_fmount,
571 	auto_fumount,
572 	host_fumount,
573 	efs_lookuppn,
574 	efs_readdir,
575 	0, /* host_readlink */
576 	0, /* host_mounted */
577 #ifdef HOST_EXEC
578 	0, /* host_umounted */
579 #else
580 	host_umounted,
581 #endif
582 	find_nfs_srvr,
583 	FS_MKMNT|FS_BACKGROUND|FS_AMQINFO
584 };
585 
586 #endif /* HAS_HOST */
587