1270096Strasz/*-
2270096Strasz * Copyright (c) 2014 The FreeBSD Foundation
3270096Strasz * All rights reserved.
4270096Strasz *
5270096Strasz * This software was developed by Edward Tomasz Napierala under sponsorship
6270096Strasz * from the FreeBSD Foundation.
7270096Strasz *
8270096Strasz * Redistribution and use in source and binary forms, with or without
9270096Strasz * modification, are permitted provided that the following conditions
10270096Strasz * are met:
11270096Strasz * 1. Redistributions of source code must retain the above copyright
12270096Strasz *    notice, this list of conditions and the following disclaimer.
13270096Strasz * 2. Redistributions in binary form must reproduce the above copyright
14270096Strasz *    notice, this list of conditions and the following disclaimer in the
15270096Strasz *    documentation and/or other materials provided with the distribution.
16270096Strasz *
17270096Strasz * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18270096Strasz * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19270096Strasz * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20270096Strasz * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21270096Strasz * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22270096Strasz * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23270096Strasz * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24270096Strasz * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25270096Strasz * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26270096Strasz * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27270096Strasz * SUCH DAMAGE.
28270096Strasz *
29270096Strasz */
30270096Strasz
31270897Strasz#include <sys/cdefs.h>
32270897Strasz__FBSDID("$FreeBSD$");
33270897Strasz
34270096Strasz#include <sys/types.h>
35270096Strasz#include <sys/time.h>
36270096Strasz#include <sys/ioctl.h>
37270096Strasz#include <sys/param.h>
38270096Strasz#include <sys/linker.h>
39270096Strasz#include <sys/mount.h>
40270096Strasz#include <sys/socket.h>
41270096Strasz#include <sys/stat.h>
42270096Strasz#include <sys/wait.h>
43270096Strasz#include <sys/utsname.h>
44270096Strasz#include <assert.h>
45270096Strasz#include <ctype.h>
46270096Strasz#include <errno.h>
47270096Strasz#include <fcntl.h>
48270096Strasz#include <libgen.h>
49270096Strasz#include <netdb.h>
50270096Strasz#include <signal.h>
51270096Strasz#include <stdbool.h>
52270096Strasz#include <stdint.h>
53270096Strasz#include <stdio.h>
54270096Strasz#include <stdlib.h>
55270096Strasz#include <string.h>
56270096Strasz#include <unistd.h>
57270096Strasz
58270096Strasz#include <libutil.h>
59270096Strasz
60270096Strasz#include "common.h"
61270096Strasz#include "mntopts.h"
62270096Strasz
63270096Straszstatic int
64270096Straszunmount_by_statfs(const struct statfs *sb, bool force)
65270096Strasz{
66270096Strasz	char *fsid_str;
67270096Strasz	int error, ret, flags;
68270096Strasz
69270096Strasz	ret = asprintf(&fsid_str, "FSID:%d:%d",
70270096Strasz	    sb->f_fsid.val[0], sb->f_fsid.val[1]);
71270096Strasz	if (ret < 0)
72270096Strasz		log_err(1, "asprintf");
73270096Strasz
74270096Strasz	log_debugx("unmounting %s using %s", sb->f_mntonname, fsid_str);
75270096Strasz
76270096Strasz	flags = MNT_BYFSID;
77270096Strasz	if (force)
78270096Strasz		flags |= MNT_FORCE;
79270096Strasz	error = unmount(fsid_str, flags);
80270096Strasz	free(fsid_str);
81270096Strasz	if (error != 0)
82270096Strasz		log_warn("cannot unmount %s", sb->f_mntonname);
83270096Strasz
84270096Strasz	return (error);
85270096Strasz}
86270096Strasz
87270096Straszstatic const struct statfs *
88270096Straszfind_statfs(const struct statfs *mntbuf, int nitems, const char *mountpoint)
89270096Strasz{
90270096Strasz	int i;
91270096Strasz
92270096Strasz	for (i = 0; i < nitems; i++) {
93270096Strasz		if (strcmp(mntbuf[i].f_mntonname, mountpoint) == 0)
94270096Strasz			return (mntbuf + i);
95270096Strasz	}
96270096Strasz
97270096Strasz	return (NULL);
98270096Strasz}
99270096Strasz
100270096Straszstatic void
101270096Straszmount_autofs(const char *from, const char *fspath, const char *options,
102270096Strasz    const char *prefix)
103270096Strasz{
104270096Strasz	struct iovec *iov = NULL;
105270096Strasz	char errmsg[255];
106270096Strasz	int error, iovlen = 0;
107270096Strasz
108270096Strasz	create_directory(fspath);
109270096Strasz
110270096Strasz	log_debugx("mounting %s on %s, prefix \"%s\", options \"%s\"",
111270096Strasz	    from, fspath, prefix, options);
112270096Strasz	memset(errmsg, 0, sizeof(errmsg));
113270096Strasz
114270096Strasz	build_iovec(&iov, &iovlen, "fstype",
115270096Strasz	    __DECONST(void *, "autofs"), (size_t)-1);
116270096Strasz	build_iovec(&iov, &iovlen, "fspath",
117270096Strasz	    __DECONST(void *, fspath), (size_t)-1);
118270096Strasz	build_iovec(&iov, &iovlen, "from",
119270096Strasz	    __DECONST(void *, from), (size_t)-1);
120270096Strasz	build_iovec(&iov, &iovlen, "errmsg",
121270096Strasz	    errmsg, sizeof(errmsg));
122270096Strasz
123270096Strasz	/*
124270096Strasz	 * Append the options and mountpoint defined in auto_master(5);
125270096Strasz	 * this way automountd(8) does not need to parse it.
126270096Strasz	 */
127270096Strasz	build_iovec(&iov, &iovlen, "master_options",
128270096Strasz	    __DECONST(void *, options), (size_t)-1);
129270096Strasz	build_iovec(&iov, &iovlen, "master_prefix",
130270096Strasz	    __DECONST(void *, prefix), (size_t)-1);
131270096Strasz
132270096Strasz	error = nmount(iov, iovlen, 0);
133270096Strasz	if (error != 0) {
134270096Strasz		if (*errmsg != '\0') {
135270096Strasz			log_err(1, "cannot mount %s on %s: %s",
136270096Strasz			    from, fspath, errmsg);
137270096Strasz		} else {
138270096Strasz			log_err(1, "cannot mount %s on %s", from, fspath);
139270096Strasz		}
140270096Strasz	}
141270096Strasz}
142270096Strasz
143270096Straszstatic void
144270096Straszmount_if_not_already(const struct node *n, const char *map,
145270096Strasz    const struct statfs *mntbuf, int nitems)
146270096Strasz{
147270096Strasz	const struct statfs *sb;
148270096Strasz	char *mountpoint;
149270096Strasz	char *from;
150270096Strasz	int ret;
151270096Strasz
152270096Strasz	ret = asprintf(&from, "map %s", map);
153270096Strasz	if (ret < 0)
154270096Strasz		log_err(1, "asprintf");
155270096Strasz
156270096Strasz	mountpoint = node_path(n);
157270096Strasz	sb = find_statfs(mntbuf, nitems, mountpoint);
158270096Strasz	if (sb != NULL) {
159270096Strasz		if (strcmp(sb->f_fstypename, "autofs") != 0) {
160270096Strasz			log_debugx("unknown filesystem mounted "
161270096Strasz			    "on %s; mounting", mountpoint);
162270096Strasz			/*
163270096Strasz			 * XXX: Compare options and 'from',
164270096Strasz			 *	and update the mount if necessary.
165270096Strasz			 */
166270096Strasz		} else {
167270096Strasz			log_debugx("autofs already mounted "
168270096Strasz			    "on %s", mountpoint);
169270096Strasz			free(from);
170270096Strasz			free(mountpoint);
171270096Strasz			return;
172270096Strasz		}
173270096Strasz	} else {
174270096Strasz		log_debugx("nothing mounted on %s; mounting",
175270096Strasz		    mountpoint);
176270096Strasz	}
177270096Strasz
178270096Strasz	mount_autofs(from, mountpoint, n->n_options, n->n_key);
179270096Strasz	free(from);
180270096Strasz	free(mountpoint);
181270096Strasz}
182270096Strasz
183270096Straszstatic void
184270096Straszmount_unmount(struct node *root)
185270096Strasz{
186270096Strasz	struct statfs *mntbuf;
187270096Strasz	struct node *n, *n2, *n3;
188270096Strasz	int i, nitems;
189270096Strasz
190270096Strasz	nitems = getmntinfo(&mntbuf, MNT_WAIT);
191270096Strasz	if (nitems <= 0)
192270096Strasz		log_err(1, "getmntinfo");
193270096Strasz
194270096Strasz	log_debugx("unmounting stale autofs mounts");
195270096Strasz
196270096Strasz	for (i = 0; i < nitems; i++) {
197270096Strasz		if (strcmp(mntbuf[i].f_fstypename, "autofs") != 0) {
198270096Strasz			log_debugx("skipping %s, filesystem type is not autofs",
199270096Strasz			    mntbuf[i].f_mntonname);
200270096Strasz			continue;
201270096Strasz		}
202270096Strasz
203270096Strasz		n = node_find(root, mntbuf[i].f_mntonname);
204270096Strasz		if (n != NULL) {
205270096Strasz			log_debugx("leaving autofs mounted on %s",
206270096Strasz			    mntbuf[i].f_mntonname);
207270096Strasz			continue;
208270096Strasz		}
209270096Strasz
210270096Strasz		log_debugx("autofs mounted on %s not found "
211270096Strasz		    "in new configuration; unmounting", mntbuf[i].f_mntonname);
212270096Strasz		unmount_by_statfs(&(mntbuf[i]), false);
213270096Strasz	}
214270096Strasz
215270096Strasz	log_debugx("mounting new autofs mounts");
216270096Strasz
217270096Strasz	TAILQ_FOREACH(n, &root->n_children, n_next) {
218270096Strasz		if (!node_is_direct_map(n)) {
219270096Strasz			mount_if_not_already(n, n->n_map, mntbuf, nitems);
220270096Strasz			continue;
221270096Strasz		}
222270096Strasz
223270096Strasz		TAILQ_FOREACH(n2, &n->n_children, n_next) {
224270096Strasz			TAILQ_FOREACH(n3, &n2->n_children, n_next) {
225270096Strasz				mount_if_not_already(n3, n->n_map,
226270096Strasz				    mntbuf, nitems);
227270096Strasz			}
228270096Strasz		}
229270096Strasz	}
230270096Strasz}
231270096Strasz
232270096Straszstatic void
233270096Straszunmount_automounted(bool force)
234270096Strasz{
235270096Strasz	struct statfs *mntbuf;
236270096Strasz	int i, nitems;
237270096Strasz
238270096Strasz	nitems = getmntinfo(&mntbuf, MNT_WAIT);
239270096Strasz	if (nitems <= 0)
240270096Strasz		log_err(1, "getmntinfo");
241270096Strasz
242270096Strasz	log_debugx("unmounting automounted filesystems");
243270096Strasz
244270096Strasz	for (i = 0; i < nitems; i++) {
245270096Strasz		if (strcmp(mntbuf[i].f_fstypename, "autofs") == 0) {
246270096Strasz			log_debugx("skipping %s, filesystem type is autofs",
247270096Strasz			    mntbuf[i].f_mntonname);
248270096Strasz			continue;
249270096Strasz		}
250270096Strasz
251270096Strasz		if ((mntbuf[i].f_flags & MNT_AUTOMOUNTED) == 0) {
252270096Strasz			log_debugx("skipping %s, not automounted",
253270096Strasz			    mntbuf[i].f_mntonname);
254270096Strasz			continue;
255270096Strasz		}
256270096Strasz
257270096Strasz		unmount_by_statfs(&(mntbuf[i]), force);
258270096Strasz	}
259270096Strasz}
260270096Strasz
261270096Straszstatic void
262270096Straszusage_automount(void)
263270096Strasz{
264270096Strasz
265270096Strasz	fprintf(stderr, "usage: automount [-D name=value][-o opts][-Lfuv]\n");
266270096Strasz	exit(1);
267270096Strasz}
268270096Strasz
269270096Straszint
270270096Straszmain_automount(int argc, char **argv)
271270096Strasz{
272270096Strasz	struct node *root;
273270096Strasz	int ch, debug = 0, show_maps = 0;
274270096Strasz	char *options = NULL;
275270096Strasz	bool do_unmount = false, force_unmount = false;
276270096Strasz
277270096Strasz	/*
278270096Strasz	 * Note that in automount(8), the only purpose of variable
279270096Strasz	 * handling is to aid in debugging maps (automount -L).
280270096Strasz	 */
281270096Strasz	defined_init();
282270096Strasz
283270096Strasz	while ((ch = getopt(argc, argv, "D:Lfo:uv")) != -1) {
284270096Strasz		switch (ch) {
285270096Strasz		case 'D':
286270096Strasz			defined_parse_and_add(optarg);
287270096Strasz			break;
288270096Strasz		case 'L':
289270096Strasz			show_maps++;
290270096Strasz			break;
291270096Strasz		case 'f':
292270096Strasz			force_unmount = true;
293270096Strasz			break;
294270096Strasz		case 'o':
295270096Strasz			if (options == NULL) {
296270096Strasz				options = checked_strdup(optarg);
297270096Strasz			} else {
298270096Strasz				options =
299270096Strasz				    separated_concat(options, optarg, ',');
300270096Strasz			}
301270096Strasz			break;
302270096Strasz		case 'u':
303270096Strasz			do_unmount = true;
304270096Strasz			break;
305270096Strasz		case 'v':
306270096Strasz			debug++;
307270096Strasz			break;
308270096Strasz		case '?':
309270096Strasz		default:
310270096Strasz			usage_automount();
311270096Strasz		}
312270096Strasz	}
313270096Strasz	argc -= optind;
314270096Strasz	if (argc != 0)
315270096Strasz		usage_automount();
316270096Strasz
317270096Strasz	if (force_unmount && !do_unmount)
318270096Strasz		usage_automount();
319270096Strasz
320270096Strasz	log_init(debug);
321270096Strasz
322270096Strasz	if (do_unmount) {
323270096Strasz		unmount_automounted(force_unmount);
324270096Strasz		return (0);
325270096Strasz	}
326270096Strasz
327270096Strasz	root = node_new_root();
328270096Strasz	parse_master(root, AUTO_MASTER_PATH);
329270096Strasz
330270096Strasz	if (show_maps) {
331270096Strasz		if (options != NULL) {
332270096Strasz			root->n_options = separated_concat(options,
333270096Strasz			    root->n_options, ',');
334270096Strasz		}
335270096Strasz		if (show_maps > 1) {
336270096Strasz			node_expand_indirect_maps(root);
337270096Strasz			node_expand_ampersand(root, NULL);
338270096Strasz		}
339270096Strasz		node_expand_defined(root);
340270096Strasz		node_print(root);
341270096Strasz		return (0);
342270096Strasz	}
343270096Strasz
344270096Strasz	mount_unmount(root);
345270096Strasz
346270096Strasz	return (0);
347270096Strasz}
348