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: releng/10.3/usr.sbin/autofs/automountd.c 283241 2015-05-21 13:41:08Z trasz $");
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 "autofs_ioctl.h"
61270096Strasz
62270096Strasz#include "common.h"
63270096Strasz
64270096Strasz#define AUTOMOUNTD_PIDFILE	"/var/run/automountd.pid"
65270096Strasz
66270096Straszstatic int nchildren = 0;
67270096Straszstatic int autofs_fd;
68270096Straszstatic int request_id;
69270096Strasz
70270096Straszstatic void
71279741Straszdone(int request_error, bool wildcards)
72270096Strasz{
73270096Strasz	struct autofs_daemon_done add;
74270096Strasz	int error;
75270096Strasz
76270096Strasz	memset(&add, 0, sizeof(add));
77270096Strasz	add.add_id = request_id;
78279741Strasz	add.add_wildcards = wildcards;
79270096Strasz	add.add_error = request_error;
80270096Strasz
81270096Strasz	log_debugx("completing request %d with error %d",
82270096Strasz	    request_id, request_error);
83270096Strasz
84270096Strasz	error = ioctl(autofs_fd, AUTOFSDONE, &add);
85279743Strasz	if (error != 0)
86270096Strasz		log_warn("AUTOFSDONE");
87270096Strasz}
88270096Strasz
89270096Strasz/*
90270096Strasz * Remove "fstype=whatever" from optionsp and return the "whatever" part.
91270096Strasz */
92270096Straszstatic char *
93270096Straszpick_option(const char *option, char **optionsp)
94270096Strasz{
95270096Strasz	char *tofree, *pair, *newoptions;
96270096Strasz	char *picked = NULL;
97270096Strasz	bool first = true;
98270096Strasz
99270096Strasz	tofree = *optionsp;
100270096Strasz
101270096Strasz	newoptions = calloc(strlen(*optionsp) + 1, 1);
102270096Strasz	if (newoptions == NULL)
103270096Strasz		log_err(1, "calloc");
104270096Strasz
105270096Strasz	while ((pair = strsep(optionsp, ",")) != NULL) {
106270096Strasz		/*
107270096Strasz		 * XXX: strncasecmp(3) perhaps?
108270096Strasz		 */
109270096Strasz		if (strncmp(pair, option, strlen(option)) == 0) {
110270096Strasz			picked = checked_strdup(pair + strlen(option));
111270096Strasz		} else {
112270096Strasz			if (first == false)
113270096Strasz				strcat(newoptions, ",");
114270096Strasz			else
115270096Strasz				first = false;
116270096Strasz			strcat(newoptions, pair);
117270096Strasz		}
118270096Strasz	}
119270096Strasz
120270096Strasz	free(tofree);
121270096Strasz	*optionsp = newoptions;
122270096Strasz
123270096Strasz	return (picked);
124270096Strasz}
125270096Strasz
126270096Straszstatic void
127270096Straszcreate_subtree(const struct node *node, bool incomplete)
128270096Strasz{
129270096Strasz	const struct node *child;
130270096Strasz	char *path;
131270096Strasz	bool wildcard_found = false;
132270096Strasz
133270096Strasz	/*
134270096Strasz	 * Skip wildcard nodes.
135270096Strasz	 */
136270096Strasz	if (strcmp(node->n_key, "*") == 0)
137270096Strasz		return;
138270096Strasz
139270096Strasz	path = node_path(node);
140270096Strasz	log_debugx("creating subtree at %s", path);
141270096Strasz	create_directory(path);
142270096Strasz
143270096Strasz	if (incomplete) {
144270096Strasz		TAILQ_FOREACH(child, &node->n_children, n_next) {
145270096Strasz			if (strcmp(child->n_key, "*") == 0) {
146270096Strasz				wildcard_found = true;
147270096Strasz				break;
148270096Strasz			}
149270096Strasz		}
150270096Strasz
151270096Strasz		if (wildcard_found) {
152270096Strasz			log_debugx("node %s contains wildcard entry; "
153270096Strasz			    "not creating its subdirectories due to -d flag",
154270096Strasz			    path);
155270096Strasz			free(path);
156270096Strasz			return;
157270096Strasz		}
158270096Strasz	}
159270096Strasz
160270096Strasz	free(path);
161270096Strasz
162270096Strasz	TAILQ_FOREACH(child, &node->n_children, n_next)
163270096Strasz		create_subtree(child, incomplete);
164270096Strasz}
165270096Strasz
166270096Straszstatic void
167270096Straszexit_callback(void)
168270096Strasz{
169270096Strasz
170279741Strasz	done(EIO, true);
171270096Strasz}
172270096Strasz
173270096Straszstatic void
174270096Straszhandle_request(const struct autofs_daemon_request *adr, char *cmdline_options,
175270096Strasz    bool incomplete_hierarchy)
176270096Strasz{
177270096Strasz	const char *map;
178270096Strasz	struct node *root, *parent, *node;
179270096Strasz	FILE *f;
180283241Strasz	char *key, *options, *fstype, *nobrowse, *retrycnt, *tmp;
181270096Strasz	int error;
182279741Strasz	bool wildcards;
183270096Strasz
184270096Strasz	log_debugx("got request %d: from %s, path %s, prefix \"%s\", "
185270096Strasz	    "key \"%s\", options \"%s\"", adr->adr_id, adr->adr_from,
186270096Strasz	    adr->adr_path, adr->adr_prefix, adr->adr_key, adr->adr_options);
187270096Strasz
188270096Strasz	/*
189270096Strasz	 * Try to notify the kernel about any problems.
190270096Strasz	 */
191270096Strasz	request_id = adr->adr_id;
192270096Strasz	atexit(exit_callback);
193270096Strasz
194270096Strasz	if (strncmp(adr->adr_from, "map ", 4) != 0) {
195270096Strasz		log_errx(1, "invalid mountfrom \"%s\"; failing request",
196270096Strasz		    adr->adr_from);
197270096Strasz	}
198270096Strasz
199270096Strasz	map = adr->adr_from + 4; /* 4 for strlen("map "); */
200270096Strasz	root = node_new_root();
201270096Strasz	if (adr->adr_prefix[0] == '\0' || strcmp(adr->adr_prefix, "/") == 0) {
202283241Strasz		/*
203283241Strasz		 * Direct map.  autofs(4) doesn't have a way to determine
204283241Strasz		 * correct map key, but since it's a direct map, we can just
205283241Strasz		 * use adr_path instead.
206283241Strasz		 */
207270096Strasz		parent = root;
208283241Strasz		key = checked_strdup(adr->adr_path);
209270096Strasz	} else {
210283241Strasz		/*
211283241Strasz		 * Indirect map.
212283241Strasz		 */
213270096Strasz		parent = node_new_map(root, checked_strdup(adr->adr_prefix),
214283235Strasz		    NULL,  checked_strdup(map),
215270096Strasz		    checked_strdup("[kernel request]"), lineno);
216283241Strasz
217283241Strasz		if (adr->adr_key[0] == '\0')
218283241Strasz			key = NULL;
219283241Strasz		else
220283241Strasz			key = checked_strdup(adr->adr_key);
221270096Strasz	}
222279741Strasz
223279741Strasz	/*
224279741Strasz	 * "Wildcards" here actually means "make autofs(4) request
225279741Strasz	 * automountd(8) action if the node being looked up does not
226279741Strasz	 * exist, even though the parent is marked as cached".  This
227279741Strasz	 * needs to be done for maps with wildcard entries, but also
228279741Strasz	 * for special and executable maps.
229279741Strasz	 */
230283241Strasz	parse_map(parent, map, key, &wildcards);
231279741Strasz	if (!wildcards)
232279741Strasz		wildcards = node_has_wildcards(parent);
233279741Strasz	if (wildcards)
234279741Strasz		log_debugx("map may contain wildcard entries");
235279741Strasz	else
236279741Strasz		log_debugx("map does not contain wildcard entries");
237279741Strasz
238283241Strasz	if (key != NULL)
239283241Strasz		node_expand_wildcard(root, key);
240279741Strasz
241270096Strasz	node = node_find(root, adr->adr_path);
242270096Strasz	if (node == NULL) {
243270096Strasz		log_errx(1, "map %s does not contain key for \"%s\"; "
244270096Strasz		    "failing mount", map, adr->adr_path);
245270096Strasz	}
246270096Strasz
247283235Strasz	options = node_options(node);
248283235Strasz
249283235Strasz	/*
250283237Strasz	 * Append options from auto_master.
251283237Strasz	 */
252283237Strasz	options = concat(options, ',', adr->adr_options);
253283237Strasz
254283237Strasz	/*
255283235Strasz	 * Prepend options passed via automountd(8) command line.
256283235Strasz	 */
257283238Strasz	options = concat(cmdline_options, ',', options);
258283235Strasz
259270096Strasz	if (node->n_location == NULL) {
260270096Strasz		log_debugx("found node defined at %s:%d; not a mountpoint",
261270096Strasz		    node->n_config_file, node->n_config_line);
262270096Strasz
263270902Strasz		nobrowse = pick_option("nobrowse", &options);
264283241Strasz		if (nobrowse != NULL && key == NULL) {
265270902Strasz			log_debugx("skipping map %s due to \"nobrowse\" "
266270902Strasz			    "option; exiting", map);
267279741Strasz			done(0, true);
268270902Strasz
269270902Strasz			/*
270270902Strasz			 * Exit without calling exit_callback().
271270902Strasz			 */
272270902Strasz			quick_exit(0);
273270902Strasz		}
274270902Strasz
275270902Strasz		/*
276270096Strasz		 * Not a mountpoint; create directories in the autofs mount
277270096Strasz		 * and complete the request.
278270096Strasz		 */
279270096Strasz		create_subtree(node, incomplete_hierarchy);
280270096Strasz
281283241Strasz		if (incomplete_hierarchy && key != NULL) {
282270096Strasz			/*
283270096Strasz			 * We still need to create the single subdirectory
284270096Strasz			 * user is trying to access.
285270096Strasz			 */
286283241Strasz			tmp = concat(adr->adr_path, '/', key);
287270096Strasz			node = node_find(root, tmp);
288270096Strasz			if (node != NULL)
289270096Strasz				create_subtree(node, false);
290270096Strasz		}
291270096Strasz
292270096Strasz		log_debugx("nothing to mount; exiting");
293279741Strasz		done(0, wildcards);
294270096Strasz
295270096Strasz		/*
296270096Strasz		 * Exit without calling exit_callback().
297270096Strasz		 */
298270096Strasz		quick_exit(0);
299270096Strasz	}
300270096Strasz
301270096Strasz	log_debugx("found node defined at %s:%d; it is a mountpoint",
302270096Strasz	    node->n_config_file, node->n_config_line);
303270096Strasz
304283241Strasz	if (key != NULL)
305283241Strasz		node_expand_ampersand(node, key);
306270096Strasz	error = node_expand_defined(node);
307270096Strasz	if (error != 0) {
308270096Strasz		log_errx(1, "variable expansion failed for %s; "
309270096Strasz		    "failing mount", adr->adr_path);
310270096Strasz	}
311270096Strasz
312270096Strasz	/*
313270096Strasz	 * Append "automounted".
314270096Strasz	 */
315283231Strasz	options = concat(options, ',', "automounted");
316270096Strasz
317270096Strasz	/*
318270902Strasz	 * Remove "nobrowse", mount(8) doesn't understand it.
319270902Strasz	 */
320270902Strasz	pick_option("nobrowse", &options);
321270902Strasz
322270902Strasz	/*
323270096Strasz	 * Figure out fstype.
324270096Strasz	 */
325270096Strasz	fstype = pick_option("fstype=", &options);
326270096Strasz	if (fstype == NULL) {
327270096Strasz		log_debugx("fstype not specified in options; "
328270096Strasz		    "defaulting to \"nfs\"");
329270096Strasz		fstype = checked_strdup("nfs");
330270096Strasz	}
331270096Strasz
332270096Strasz	if (strcmp(fstype, "nfs") == 0) {
333270096Strasz		/*
334270096Strasz		 * The mount_nfs(8) command defaults to retry undefinitely.
335270096Strasz		 * We do not want that behaviour, because it leaves mount_nfs(8)
336270096Strasz		 * instances and automountd(8) children hanging forever.
337270096Strasz		 * Disable retries unless the option was passed explicitly.
338270096Strasz		 */
339270096Strasz		retrycnt = pick_option("retrycnt=", &options);
340270096Strasz		if (retrycnt == NULL) {
341270096Strasz			log_debugx("retrycnt not specified in options; "
342270096Strasz			    "defaulting to 1");
343283231Strasz			options = concat(options, ',', "retrycnt=1");
344270096Strasz		} else {
345283231Strasz			options = concat(options, ',',
346283231Strasz			    concat("retrycnt", '=', retrycnt));
347270096Strasz		}
348270096Strasz	}
349270096Strasz
350270096Strasz	f = auto_popen("mount", "-t", fstype, "-o", options,
351270096Strasz	    node->n_location, adr->adr_path, NULL);
352270096Strasz	assert(f != NULL);
353270096Strasz	error = auto_pclose(f);
354270096Strasz	if (error != 0)
355270096Strasz		log_errx(1, "mount failed");
356270096Strasz
357270902Strasz	log_debugx("mount done; exiting");
358279741Strasz	done(0, wildcards);
359270096Strasz
360270096Strasz	/*
361270096Strasz	 * Exit without calling exit_callback().
362270096Strasz	 */
363270096Strasz	quick_exit(0);
364270096Strasz}
365270096Strasz
366274548Straszstatic void
367274548Straszsigchld_handler(int dummy __unused)
368274548Strasz{
369274548Strasz
370274548Strasz	/*
371274548Strasz	 * The only purpose of this handler is to make SIGCHLD
372274548Strasz	 * interrupt the AUTOFSREQUEST ioctl(2), so we can call
373274548Strasz	 * wait_for_children().
374274548Strasz	 */
375274548Strasz}
376274548Strasz
377274548Straszstatic void
378274548Straszregister_sigchld(void)
379274548Strasz{
380274548Strasz	struct sigaction sa;
381274548Strasz	int error;
382274548Strasz
383274548Strasz	bzero(&sa, sizeof(sa));
384274548Strasz	sa.sa_handler = sigchld_handler;
385274548Strasz	sigfillset(&sa.sa_mask);
386274548Strasz	error = sigaction(SIGCHLD, &sa, NULL);
387274548Strasz	if (error != 0)
388274548Strasz		log_err(1, "sigaction");
389274548Strasz
390274548Strasz}
391274548Strasz
392274548Strasz
393270096Straszstatic int
394270096Straszwait_for_children(bool block)
395270096Strasz{
396270096Strasz	pid_t pid;
397270096Strasz	int status;
398270096Strasz	int num = 0;
399270096Strasz
400270096Strasz	for (;;) {
401270096Strasz		/*
402270096Strasz		 * If "block" is true, wait for at least one process.
403270096Strasz		 */
404270096Strasz		if (block && num == 0)
405270096Strasz			pid = wait4(-1, &status, 0, NULL);
406270096Strasz		else
407270096Strasz			pid = wait4(-1, &status, WNOHANG, NULL);
408270096Strasz		if (pid <= 0)
409270096Strasz			break;
410270096Strasz		if (WIFSIGNALED(status)) {
411270096Strasz			log_warnx("child process %d terminated with signal %d",
412270096Strasz			    pid, WTERMSIG(status));
413270096Strasz		} else if (WEXITSTATUS(status) != 0) {
414274498Strasz			log_debugx("child process %d terminated with exit status %d",
415270096Strasz			    pid, WEXITSTATUS(status));
416270096Strasz		} else {
417270096Strasz			log_debugx("child process %d terminated gracefully", pid);
418270096Strasz		}
419270096Strasz		num++;
420270096Strasz	}
421270096Strasz
422270096Strasz	return (num);
423270096Strasz}
424270096Strasz
425270096Straszstatic void
426270096Straszusage_automountd(void)
427270096Strasz{
428270096Strasz
429270096Strasz	fprintf(stderr, "usage: automountd [-D name=value][-m maxproc]"
430270096Strasz	    "[-o opts][-Tidv]\n");
431270096Strasz	exit(1);
432270096Strasz}
433270096Strasz
434270096Straszint
435270096Straszmain_automountd(int argc, char **argv)
436270096Strasz{
437270096Strasz	struct pidfh *pidfh;
438270096Strasz	pid_t pid, otherpid;
439270096Strasz	const char *pidfile_path = AUTOMOUNTD_PIDFILE;
440270096Strasz	char *options = NULL;
441270096Strasz	struct autofs_daemon_request request;
442270096Strasz	int ch, debug = 0, error, maxproc = 30, retval, saved_errno;
443270096Strasz	bool dont_daemonize = false, incomplete_hierarchy = false;
444270096Strasz
445270096Strasz	defined_init();
446270096Strasz
447270096Strasz	while ((ch = getopt(argc, argv, "D:Tdim:o:v")) != -1) {
448270096Strasz		switch (ch) {
449270096Strasz		case 'D':
450270096Strasz			defined_parse_and_add(optarg);
451270096Strasz			break;
452270096Strasz		case 'T':
453270096Strasz			/*
454270096Strasz			 * For compatibility with other implementations,
455270096Strasz			 * such as OS X.
456270096Strasz			 */
457270096Strasz			debug++;
458270096Strasz			break;
459270096Strasz		case 'd':
460270096Strasz			dont_daemonize = true;
461270096Strasz			debug++;
462270096Strasz			break;
463270096Strasz		case 'i':
464270096Strasz			incomplete_hierarchy = true;
465270096Strasz			break;
466270096Strasz		case 'm':
467270096Strasz			maxproc = atoi(optarg);
468270096Strasz			break;
469270096Strasz		case 'o':
470283238Strasz			options = concat(options, ',', optarg);
471270096Strasz			break;
472270096Strasz		case 'v':
473270096Strasz			debug++;
474270096Strasz			break;
475270096Strasz		case '?':
476270096Strasz		default:
477270096Strasz			usage_automountd();
478270096Strasz		}
479270096Strasz	}
480270096Strasz	argc -= optind;
481270096Strasz	if (argc != 0)
482270096Strasz		usage_automountd();
483270096Strasz
484270096Strasz	log_init(debug);
485270096Strasz
486270096Strasz	pidfh = pidfile_open(pidfile_path, 0600, &otherpid);
487270096Strasz	if (pidfh == NULL) {
488270096Strasz		if (errno == EEXIST) {
489270096Strasz			log_errx(1, "daemon already running, pid: %jd.",
490270096Strasz			    (intmax_t)otherpid);
491270096Strasz		}
492270096Strasz		log_err(1, "cannot open or create pidfile \"%s\"",
493270096Strasz		    pidfile_path);
494270096Strasz	}
495270096Strasz
496270096Strasz	autofs_fd = open(AUTOFS_PATH, O_RDWR | O_CLOEXEC);
497270096Strasz	if (autofs_fd < 0 && errno == ENOENT) {
498270096Strasz		saved_errno = errno;
499270096Strasz		retval = kldload("autofs");
500270096Strasz		if (retval != -1)
501270096Strasz			autofs_fd = open(AUTOFS_PATH, O_RDWR | O_CLOEXEC);
502270096Strasz		else
503270096Strasz			errno = saved_errno;
504270096Strasz	}
505270096Strasz	if (autofs_fd < 0)
506270096Strasz		log_err(1, "failed to open %s", AUTOFS_PATH);
507270096Strasz
508270096Strasz	if (dont_daemonize == false) {
509270096Strasz		if (daemon(0, 0) == -1) {
510270096Strasz			log_warn("cannot daemonize");
511270096Strasz			pidfile_remove(pidfh);
512270096Strasz			exit(1);
513270096Strasz		}
514270096Strasz	} else {
515270096Strasz		lesser_daemon();
516270096Strasz	}
517270096Strasz
518270096Strasz	pidfile_write(pidfh);
519270096Strasz
520274548Strasz	register_sigchld();
521274548Strasz
522270096Strasz	for (;;) {
523270096Strasz		log_debugx("waiting for request from the kernel");
524270096Strasz
525270096Strasz		memset(&request, 0, sizeof(request));
526270096Strasz		error = ioctl(autofs_fd, AUTOFSREQUEST, &request);
527270096Strasz		if (error != 0) {
528270096Strasz			if (errno == EINTR) {
529270096Strasz				nchildren -= wait_for_children(false);
530270096Strasz				assert(nchildren >= 0);
531270096Strasz				continue;
532270096Strasz			}
533270096Strasz
534270096Strasz			log_err(1, "AUTOFSREQUEST");
535270096Strasz		}
536270096Strasz
537270096Strasz		if (dont_daemonize) {
538270096Strasz			log_debugx("not forking due to -d flag; "
539270096Strasz			    "will exit after servicing a single request");
540270096Strasz		} else {
541270096Strasz			nchildren -= wait_for_children(false);
542270096Strasz			assert(nchildren >= 0);
543270096Strasz
544270096Strasz			while (maxproc > 0 && nchildren >= maxproc) {
545270096Strasz				log_debugx("maxproc limit of %d child processes hit; "
546270096Strasz				    "waiting for child process to exit", maxproc);
547270096Strasz				nchildren -= wait_for_children(true);
548270096Strasz				assert(nchildren >= 0);
549270096Strasz			}
550270096Strasz			log_debugx("got request; forking child process #%d",
551270096Strasz			    nchildren);
552270096Strasz			nchildren++;
553270096Strasz
554270096Strasz			pid = fork();
555270096Strasz			if (pid < 0)
556270096Strasz				log_err(1, "fork");
557270096Strasz			if (pid > 0)
558270096Strasz				continue;
559270096Strasz		}
560270096Strasz
561270096Strasz		pidfile_close(pidfh);
562270096Strasz		handle_request(&request, options, incomplete_hierarchy);
563270096Strasz	}
564270096Strasz
565270096Strasz	pidfile_close(pidfh);
566270096Strasz
567270096Strasz	return (0);
568270096Strasz}
569270096Strasz
570