1/*
2 * Copyright (c) 2004 Alfred Perlstein <alfred@FreeBSD.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 * $Id: autodriver.c,v 1.9 2004/09/08 08:12:21 bright Exp $
27 * $FreeBSD$
28 */
29#include <ctype.h>
30#include <err.h>
31#include <errno.h>
32#include <stdio.h>
33#include <stdlib.h>
34#include <string.h>
35#include <unistd.h>
36#include <fcntl.h>
37
38#include <sys/dirent.h>
39#include <sys/types.h>
40#include <sys/param.h>
41#include <sys/mount.h>
42#include <sys/poll.h>
43#include <sys/stat.h>
44
45#include <libautofs.h>
46
47struct autoentry {
48	char *ae_mnt;		/* autofs mountpoint. */
49	char *ae_path;		/* path under mount. */
50	char *ae_type;		/* fs to be mounted type. */
51	char *ae_opts;		/* options passed to mount. */
52	char *ae_rpath;		/* remote path */
53	char *ae_free;		/* freeme! */
54	char *ae_fullpath;	/* full path to mount */
55	int ae_line;		/* line it came from in the conf. */
56	int ae_indirect;	/* is this an indirect mount? */
57	int ae_direct;		/* is this a direct mount? */
58	int ae_browse;		/* browseable? */
59	struct autoentry *ae_next;	/* next. */
60};
61
62struct autoentry *entries;
63const char *mount_prog = "mount";
64const char *fstype = "autofs";
65
66void *xmalloc(size_t);
67void *xcalloc(size_t number, size_t size);
68void parsetab(void);
69void populate_tab(void);
70void doreq(autoh_t, autoreq_t);
71void dotheneedful(autoh_t);
72void eventloop(void);
73int poll_handles(autoh_t *array, int cnt);
74int mount_indirect(struct autofs_userreq *req, struct autoentry *ent);
75int mount_direct(struct autofs_userreq *req, struct autoentry *ent);
76int mount_browse(struct autofs_userreq *req, struct autoentry *ent);
77
78#define DSTR(s)	sizeof(s) - 1, s
79
80struct dirent dumbents[] = {
81	{50, sizeof(struct dirent), DT_DIR, DSTR("one") },
82	{51, sizeof(struct dirent), DT_DIR, DSTR(".") },
83	{52, sizeof(struct dirent), DT_DIR, DSTR("..") },
84	{50, sizeof(struct dirent), DT_DIR, DSTR("two") },
85};
86
87void *
88xmalloc(size_t size)
89{
90	void *ret;
91
92	ret = malloc(size);
93	if (ret == NULL)
94		err(1, "malloc %d", (int) size);
95	return (ret);
96}
97
98void *
99xcalloc(size_t number, size_t size)
100{
101	void *ret;
102
103	ret = calloc(number, size);
104	if (ret == NULL)
105		err(1, "calloc %d %d", (int)number, (int)size);
106	return (ret);
107}
108
109void
110parsetab(void)
111{
112	FILE *fp;
113	const char *tab;
114	char *cp, *p, *line, *opt;
115	size_t len;
116	struct autoentry *ent;
117	int i, lineno, x, gotopt;
118	const char *expecting = "expecting 'direct', 'indirect' or 'browse'";
119	const char *tabfiles[] = {
120		"/etc/autotab", "/usr/local/etc/autotab", "./autotab", NULL
121	};
122
123	lineno = 0;
124	for (i = 0; (tab = tabfiles[i]) != NULL; i++) {
125		tab = tabfiles[i];
126		fp = fopen(tab, "r");
127		if (fp == NULL)
128			warn("fopen %s", tab);
129		if (fp != NULL)
130			break;
131	}
132	if (fp == NULL) {
133		err(1, "no config file available.");
134	}
135
136	fprintf(stderr, "using config file: %s\n", tab);
137
138	while ((cp = fgetln(fp, &len)) != NULL) {
139		lineno++;
140		while (len > 0 && isspace(cp[len - 1]))
141			len--;
142		line = xmalloc(len + 1);
143		bcopy(cp, line, len);
144		line[len] = '\0';
145		cp = line;
146		if ((cp = strchr(line, '#')) != NULL)
147			*cp = '\0';
148		cp = line;
149		while (isspace(*cp))
150			cp++;
151		if (*cp == '\0') {
152			free(line);
153			continue;
154		}
155		ent = xcalloc(1, sizeof(*ent));
156		if ((p = strsep(&cp, " \t")) == NULL)
157			goto bad;
158		ent->ae_mnt = p;
159		if ((p = strsep(&cp, " \t")) == NULL)
160			goto bad;
161		ent->ae_path = p;
162		if ((p = strsep(&cp, " \t")) == NULL)
163			goto bad;
164		ent->ae_type = p;
165		if ((p = strsep(&cp, " \t")) == NULL)
166			goto bad;
167		ent->ae_opts = p;
168		if ((p = strsep(&cp, " \t")) == NULL)
169			goto bad;
170		ent->ae_rpath = p;
171		if ((p = strsep(&cp, " \t")) == NULL)
172			goto bad;
173		gotopt = 0;
174		opt = p;
175		while ((p = strsep(&opt, ",")) != NULL) {
176			if (strcmp(p, "indirect") == 0) {
177				ent->ae_indirect = 1;
178				gotopt = 1;
179			} else if (strcmp(p, "direct") == 0) {
180				ent->ae_direct = 1;
181				gotopt = 1;
182			} else if (strcmp(p, "browse") == 0) {
183				ent->ae_browse = 1;
184				gotopt = 1;
185			} else {
186				warnx("unreconized option '%s', %s",
187				    p, expecting);
188				goto bad2;
189			}
190		}
191		if (!gotopt) {
192			warnx("no options specified %s", expecting);
193			goto bad2;
194		}
195		if (ent->ae_direct && ent->ae_indirect) {
196			warnx("direct and indirect are mutually exclusive");
197			goto bad2;
198
199		}
200		x = asprintf(&ent->ae_fullpath, "%s/%s",
201		    ent->ae_mnt, ent->ae_path);
202		if (x == -1)
203			err(1, "asprintf");
204
205		if (strlen(ent->ae_fullpath) + 1 > PATH_MAX) {
206			warnx("Error in file %s, line %d, "
207			    "mountpath (%s) exceeds PATH_MAX (%d)",
208			    tab, lineno, ent->ae_fullpath, PATH_MAX);
209			goto bad2;
210		}
211		ent->ae_line = lineno;
212		ent->ae_free = line;
213		ent->ae_next = entries;
214		entries = ent;
215		continue;
216bad:
217		warnx("Parse error in file %s, line %d", tab, lineno);
218bad2:
219		free(ent->ae_fullpath);
220		free(line);
221		free(ent);
222	}
223	if (ferror(fp))
224		err(1, "error with file %s", tab);
225}
226
227void
228populate_tab(void)
229{
230	struct autoentry *ent;
231	char *path, *cmd;
232	int error;
233	autoh_t ah;
234
235	path = cmd = NULL;
236
237	for (ent = entries; ent != NULL; ent = ent->ae_next) {
238		free(path);
239		free(cmd);
240		error = asprintf(&path, "%s/%s", ent->ae_mnt, ent->ae_path);
241		if (error == -1)
242			err(1, "asprintf");
243		error = asprintf(&cmd, "mkdir -p %s", path);
244		if (error == -1)
245			err(1, "asprintf");
246		error = system(cmd);
247		if (error) {
248			warn("system: %s", cmd);
249			continue;
250		}
251		if (autoh_get(ent->ae_mnt, &ah)) {
252			warn("autoh_get %s", path);
253			continue;
254		}
255		error = autoh_togglepath(ah, AUTO_MOUNTER, getpid(), path);
256		if (error) {
257			err(1, "AUTO_MOUNTER %s", path);
258			continue;
259		}
260		if (ent->ae_browse) {
261			error = autoh_togglepath(ah, AUTO_BROWSE, getpid(),
262			    path);
263			if (error)
264				err(1, "AUTO_BROWSE %s", path);
265		}
266		if (ent->ae_direct) {
267			error = autoh_togglepath(ah, AUTO_DIRECT, getpid(),
268			    path);
269			if (error)
270				err(1, "AUTO_DIRECT %s", path);
271		}
272		if (ent->ae_indirect) {
273			error = autoh_togglepath(ah, AUTO_INDIRECT, getpid(),
274			    path);
275			if (error)
276				err(1, "AUTO_INDIRECT %s", path);
277		}
278		autoh_free(ah);
279	}
280	free(path);
281	free(cmd);
282}
283
284/*
285 * Process an autofs request, scan the list of entries in the config
286 * looking for our node, if found mount it.
287 */
288void
289doreq(autoh_t ah, autoreq_t req)
290{
291	struct autoentry *ent;
292	int error;
293	int mcmp;
294	int xid;
295	const char *mnt;
296
297	mnt = autoh_mp(ah);
298
299	autoreq_seterrno(req, 0);
300	for (ent = entries; ent != NULL; ent = ent->ae_next) {
301		fprintf(stderr, "comparing {%s,%s} to {%s,%s}\n",
302		    mnt, ent->ae_mnt, autoreq_getpath(req), ent->ae_path);
303		fprintf(stderr, "comparing {%d,%d} to {%d,%d}\n",
304		    (int)strlen(mnt),
305		    (int)strlen(ent->ae_mnt),
306		    (int)strlen(autoreq_getpath(req)),
307		    (int)strlen(ent->ae_path));
308		autoreq_getxid(req, &xid);
309		fprintf(stderr, "req xid %d\n", xid);
310		if ((mcmp = strcmp(mnt, ent->ae_mnt)) != 0) {
311			fprintf(stderr, "mcmp = %d\n", mcmp);
312			continue;
313		}
314		if (mount_direct(req, ent))
315			goto serve;
316		if (mount_indirect(req, ent))
317			goto serve;
318		if (mount_browse(req, ent))
319			goto serve;
320	}
321	fprintf(stderr, "no entry found...\n");
322	autoreq_seterrno(req, ENOENT);
323serve:
324	error = autoreq_serv(ah, req);
325	if (error == -1) {
326		warn("AUTOFS_CTL_SERVREQ");
327	}
328}
329
330int
331mount_indirect(req, ent)
332	struct autofs_userreq *req;
333	struct autoentry *ent;
334{
335	struct stat sb;
336	char *path, *cmd;
337	int error, x;
338
339	if (ent->ae_indirect != 1) {
340		fprintf(stderr, "not indirect.\n");
341		return (0);
342	}
343	fprintf(stderr, "indirect mount...\n");
344	/*
345	 * handle lookups, fake all stat(2) requests... this is bad,
346	 * but we're a driver so we don't care...
347	 * If we don't care about the type of request, then just return.
348	 */
349	switch (autoreq_getop(req)) {
350	case AUTOREQ_OP_LOOKUP:
351		break;
352	case AUTOREQ_OP_STAT:
353		fprintf(stderr, "stat\n");
354		return (1);
355	default:
356		fprintf(stderr, "unknown\n");
357		return (0);
358	}
359	if (stat(ent->ae_fullpath, &sb))
360		return (0);
361	if (sb.st_ino != autoreq_getdirino(req)) {
362		fprintf(stderr, "st_ino %d != dirino %d\n",
363		    (int)sb.st_ino, (int)autoreq_getdirino(req));
364		return (0);
365	}
366	x = asprintf(&path, "%s/%s", ent->ae_fullpath, autoreq_getpath(req));
367	if (x > PATH_MAX) {
368		autoreq_seterrno(req, ENAMETOOLONG);
369		return (1);
370	}
371	if (mkdir(path, 0555) == -1)
372		warn("mkdir %s", path);
373	error = asprintf(&cmd, "%s -t %s -o %s %s/%s %s", mount_prog,
374	    ent->ae_type, ent->ae_opts, ent->ae_rpath, autoreq_getpath(req), path);
375	fprintf(stderr, "running:\n\t%s\n", cmd);
376	error = system(cmd);
377	fprintf(stderr, "error = %d\n", error);
378	free(cmd);
379	if (error) {
380		if (rmdir(path) == -1)
381			warn("rmdir %s", path);
382		autoreq_seterrno(req, ENOENT);
383	} else {
384		if (stat(path, &sb) != -1)
385			autoreq_setino(req, sb.st_ino);
386		/* XXX !!! */
387		/* req->au_flags = 1; */
388	}
389	free(path);
390	return (1);
391}
392
393int
394mount_direct(req, ent)
395	struct autofs_userreq *req;
396	struct autoentry *ent;
397{
398	struct stat sb;
399	char *cmd;
400	int error;
401
402	if (ent->ae_direct != 1) {
403		fprintf(stderr, "not direct.\n");
404		return (0);
405	}
406	fprintf(stderr, "direct mount...\n");
407	/*
408	 * handle lookups, fake all stat(2) requests... this is bad,
409	 * but we're a driver so we don't care...
410	 * If we don't care about the type of request, then just return.
411	 */
412	switch (autoreq_getop(req)) {
413	case AUTOREQ_OP_LOOKUP:
414		break;
415	case AUTOREQ_OP_STAT:
416		return (1);
417	default:
418		return (0);
419	}
420	if (stat(ent->ae_fullpath, &sb))
421		return (0);
422	if (sb.st_ino != autoreq_getino(req))
423		return (0);
424	error = asprintf(&cmd, "%s -t %s -o %s %s %s", mount_prog,
425	    ent->ae_type, ent->ae_opts, ent->ae_rpath, ent->ae_fullpath);
426	if (error == -1)
427		err(1, "asprintf");
428	fprintf(stderr, "running:\n\t%s\n", cmd);
429	error = system(cmd);
430	fprintf(stderr, "error = %d\n", error);
431	free(cmd);
432	if (error) {
433		autoreq_seterrno(req, ENOENT);
434		return (1);
435	}
436	/* XXX: fix ONLIST in kernel */
437	/* req->au_flags = 1; */
438	return (1);
439}
440
441int
442mount_browse(req, ent)
443	struct autofs_userreq *req;
444	struct autoentry *ent;
445{
446	off_t off;
447
448	if (ent->ae_browse != 1)
449		return (0);
450	if (autoreq_getop(req) != AUTOREQ_OP_READDIR)
451		return (0);
452	autoreq_getoffset(req, &off);
453	if (off < sizeof(dumbents))
454		autoreq_setaux(req, dumbents, sizeof(dumbents));
455	fprintf(stderr, "mount_browse: offset %d, size %d\n",
456	    (int)off, (int)sizeof(dumbents));
457	autoreq_seteof(req, 1);
458	return (1);
459}
460
461/*
462 * Ask the filesystem passed in if it has a pending request.
463 * if so process them.
464 */
465void
466dotheneedful(autoh_t ah)
467{
468	int cnt, i;
469	autoreq_t *reqs;
470
471	if (autoreq_get(ah, &reqs, &cnt))
472		err(1, "autoreq_get");
473
474	for (i = 0; i < cnt; i++) {
475		fprintf(stderr, "processing request for '%s' '%s'\n",
476		    autoh_mp(ah), autoreq_getpath(reqs[i]));
477		doreq(ah, reqs[i]);
478	}
479	free(reqs);
480}
481
482int
483poll_handles(autoh_t *array, int cnt)
484{
485	int i, saved_errno, x;
486	static struct pollfd *pfd = NULL;
487
488	pfd = reallocf(pfd, cnt * sizeof(*pfd));
489	if (pfd == NULL)
490		return (-1);
491	for (i = 0; i < cnt; i++) {
492		pfd[i].fd = autoh_fd(array[i]);
493		pfd[i].events = POLLPRI;
494		pfd[i].revents = 0;
495	}
496	fprintf(stderr, "start polling...\n");
497	x = poll(pfd, cnt, 10000);
498	saved_errno = errno;
499	fprintf(stderr, "done polling...\n");
500	errno = saved_errno;
501	if (x == -1)
502		return (-1);
503	/* at least one fs is ready... */
504	if (x > 0)
505		return (0);
506	return (0);
507}
508
509void
510eventloop(void)
511{
512	autoh_t *array;
513	int cnt, i;
514
515	fprintf(stderr, "starting event loop...\n");
516	for ( ;; ) {
517		if (autoh_getall(&array, &cnt))
518			err(1, "autoh_getall");
519		if (poll_handles(array, cnt))
520			err(1, "poll_handles");
521		for (i = 0; i < cnt; i++) {
522			dotheneedful(array[i]);
523		}
524		autoh_freeall(array);
525	}
526}
527
528int
529main(int argc __unused, char **argv __unused)
530{
531
532	if (getuid() != 0)
533		errx(1, "autodriver needs to be run as root to work.");
534	parsetab();
535	populate_tab();
536	eventloop();
537	return (0);
538}
539