Merge remote-tracking branch 'origin/vendor/LIBEDIT'
[dragonfly.git] / usr.sbin / autofs / automountd.c
1 /*-
2  * Copyright (c) 2016 The DragonFly Project
3  * Copyright (c) 2014 The FreeBSD Foundation
4  * All rights reserved.
5  *
6  * This software was developed by Edward Tomasz Napierala under sponsorship
7  * from the FreeBSD Foundation.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28  * SUCH DAMAGE.
29  *
30  */
31
32 #include <sys/types.h>
33 #include <sys/ioctl.h>
34 #include <sys/linker.h>
35 #include <sys/wait.h>
36 #include <assert.h>
37 #include <errno.h>
38 #include <fcntl.h>
39 #include <signal.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <unistd.h>
44 #include <libutil.h>
45 #include <vfs/autofs/autofs_ioctl.h>
46
47 #include "common.h"
48
49 #define AUTOMOUNTD_PIDFILE      "/var/run/automountd.pid"
50
51 static int nchildren = 0;
52 static int autofs_fd;
53 static int request_id;
54
55 static void
56 done(int request_error, bool wildcards)
57 {
58         struct autofs_daemon_done add;
59         int error;
60
61         memset(&add, 0, sizeof(add));
62         add.add_id = request_id;
63         add.add_wildcards = wildcards;
64         add.add_error = request_error;
65
66         log_debugx("completing request %d with error %d",
67             request_id, request_error);
68
69         error = ioctl(autofs_fd, AUTOFSDONE, &add);
70         if (error != 0)
71                 log_warn("AUTOFSDONE");
72 }
73
74 /*
75  * Remove "fstype=whatever" from optionsp and return the "whatever" part.
76  */
77 static char *
78 pick_option(const char *option, char **optionsp)
79 {
80         char *tofree, *pair, *newoptions;
81         char *picked = NULL;
82         bool first = true;
83
84         tofree = *optionsp;
85
86         newoptions = calloc(1, strlen(*optionsp) + 1);
87         if (newoptions == NULL)
88                 log_err(1, "calloc");
89
90         while ((pair = strsep(optionsp, ",")) != NULL) {
91                 /*
92                  * XXX: strncasecmp(3) perhaps?
93                  */
94                 if (strncmp(pair, option, strlen(option)) == 0) {
95                         picked = checked_strdup(pair + strlen(option));
96                 } else {
97                         if (first == false)
98                                 strcat(newoptions, ",");
99                         else
100                                 first = false;
101                         strcat(newoptions, pair);
102                 }
103         }
104
105         free(tofree);
106         *optionsp = newoptions;
107
108         return (picked);
109 }
110
111 static void
112 create_subtree(const struct node *node, bool incomplete)
113 {
114         const struct node *child;
115         char *path;
116         bool wildcard_found = false;
117
118         /*
119          * Skip wildcard nodes.
120          */
121         if (strcmp(node->n_key, "*") == 0)
122                 return;
123
124         path = node_path(node);
125         log_debugx("creating subtree at %s", path);
126         create_directory(path);
127
128         if (incomplete) {
129                 TAILQ_FOREACH(child, &node->n_children, n_next) {
130                         if (strcmp(child->n_key, "*") == 0) {
131                                 wildcard_found = true;
132                                 break;
133                         }
134                 }
135
136                 if (wildcard_found) {
137                         log_debugx("node %s contains wildcard entry; "
138                             "not creating its subdirectories due to -d flag",
139                             path);
140                         free(path);
141                         return;
142                 }
143         }
144
145         free(path);
146
147         TAILQ_FOREACH(child, &node->n_children, n_next)
148                 create_subtree(child, incomplete);
149 }
150
151 static void
152 exit_callback(void)
153 {
154
155         done(EIO, true);
156 }
157
158 static void
159 handle_request(const struct autofs_daemon_request *adr, char *cmdline_options,
160     bool incomplete_hierarchy)
161 {
162         const char *map;
163         struct node *root, *parent, *node;
164         FILE *f;
165         char *key, *options, *fstype, *nobrowse, *retrycnt, *tmp;
166         int error;
167         bool wildcards;
168
169         log_debugx("got request %d: from %s, path %s, prefix \"%s\", "
170             "key \"%s\", options \"%s\"", adr->adr_id, adr->adr_from,
171             adr->adr_path, adr->adr_prefix, adr->adr_key, adr->adr_options);
172
173         /*
174          * Try to notify the kernel about any problems.
175          */
176         request_id = adr->adr_id;
177         atexit(exit_callback);
178
179         if (strncmp(adr->adr_from, "map ", 4) != 0) {
180                 log_errx(1, "invalid mountfrom \"%s\"; failing request",
181                     adr->adr_from);
182         }
183
184         map = adr->adr_from + 4; /* 4 for strlen("map "); */
185         root = node_new_root();
186         if (adr->adr_prefix[0] == '\0' || strcmp(adr->adr_prefix, "/") == 0) {
187                 /*
188                  * Direct map.  autofs(4) doesn't have a way to determine
189                  * correct map key, but since it's a direct map, we can just
190                  * use adr_path instead.
191                  */
192                 parent = root;
193                 key = checked_strdup(adr->adr_path);
194         } else {
195                 /*
196                  * Indirect map.
197                  */
198                 parent = node_new_map(root, checked_strdup(adr->adr_prefix),
199                     NULL,  checked_strdup(map),
200                     checked_strdup("[kernel request]"), lineno);
201
202                 if (adr->adr_key[0] == '\0')
203                         key = NULL;
204                 else
205                         key = checked_strdup(adr->adr_key);
206         }
207
208         /*
209          * "Wildcards" here actually means "make autofs(4) request
210          * automountd(8) action if the node being looked up does not
211          * exist, even though the parent is marked as cached".  This
212          * needs to be done for maps with wildcard entries, but also
213          * for special and executable maps.
214          */
215         parse_map(parent, map, key, &wildcards);
216         if (!wildcards)
217                 wildcards = node_has_wildcards(parent);
218         if (wildcards)
219                 log_debugx("map may contain wildcard entries");
220         else
221                 log_debugx("map does not contain wildcard entries");
222
223         if (key != NULL)
224                 node_expand_wildcard(root, key);
225
226         node = node_find(root, adr->adr_path);
227         if (node == NULL) {
228                 log_errx(1, "map %s does not contain key for \"%s\"; "
229                     "failing mount", map, adr->adr_path);
230         }
231
232         options = node_options(node);
233
234         /*
235          * Append options from auto_master.
236          */
237         options = concat(options, ',', adr->adr_options);
238
239         /*
240          * Prepend options passed via automountd(8) command line.
241          */
242         options = concat(cmdline_options, ',', options);
243
244         if (node->n_location == NULL) {
245                 log_debugx("found node defined at %s:%d; not a mountpoint",
246                     node->n_config_file, node->n_config_line);
247
248                 nobrowse = pick_option("nobrowse", &options);
249                 if (nobrowse != NULL && key == NULL) {
250                         log_debugx("skipping map %s due to \"nobrowse\" "
251                             "option; exiting", map);
252                         done(0, true);
253
254                         /*
255                          * Exit without calling exit_callback().
256                          */
257                         quick_exit(0);
258                 }
259
260                 /*
261                  * Not a mountpoint; create directories in the autofs mount
262                  * and complete the request.
263                  */
264                 create_subtree(node, incomplete_hierarchy);
265
266                 if (incomplete_hierarchy && key != NULL) {
267                         /*
268                          * We still need to create the single subdirectory
269                          * user is trying to access.
270                          */
271                         tmp = concat(adr->adr_path, '/', key);
272                         node = node_find(root, tmp);
273                         if (node != NULL)
274                                 create_subtree(node, false);
275                 }
276
277                 log_debugx("nothing to mount; exiting");
278                 done(0, wildcards);
279
280                 /*
281                  * Exit without calling exit_callback().
282                  */
283                 quick_exit(0);
284         }
285
286         log_debugx("found node defined at %s:%d; it is a mountpoint",
287             node->n_config_file, node->n_config_line);
288
289         if (key != NULL)
290                 node_expand_ampersand(node, key);
291         error = node_expand_defined(node);
292         if (error != 0) {
293                 log_errx(1, "variable expansion failed for %s; "
294                     "failing mount", adr->adr_path);
295         }
296
297         /*
298          * Append "automounted".
299          */
300         options = concat(options, ',', "automounted");
301
302         /*
303          * Remove "nobrowse", mount(8) doesn't understand it.
304          */
305         pick_option("nobrowse", &options);
306
307         /*
308          * Figure out fstype.
309          */
310         fstype = pick_option("fstype=", &options);
311         if (fstype == NULL) {
312                 log_debugx("fstype not specified in options; "
313                     "defaulting to \"nfs\"");
314                 fstype = checked_strdup("nfs");
315         }
316
317         if (strcmp(fstype, "nfs") == 0) {
318                 /*
319                  * The mount_nfs(8) command defaults to retry undefinitely.
320                  * We do not want that behaviour, because it leaves mount_nfs(8)
321                  * instances and automountd(8) children hanging forever.
322                  * Disable retries unless the option was passed explicitly.
323                  */
324                 retrycnt = pick_option("retrycnt=", &options);
325                 if (retrycnt == NULL) {
326                         log_debugx("retrycnt not specified in options; "
327                             "defaulting to 1");
328                         options = concat(options, ',', "retrycnt=1");
329                 } else {
330                         options = concat(options, ',',
331                             concat("retrycnt", '=', retrycnt));
332                 }
333         }
334
335         f = auto_popen("mount", "-t", fstype, "-o", options,
336             node->n_location, adr->adr_path, NULL);
337         assert(f != NULL);
338         error = auto_pclose(f);
339         if (error != 0)
340                 log_errx(1, "mount failed");
341
342         log_debugx("mount done; exiting");
343         done(0, wildcards);
344
345         /*
346          * Exit without calling exit_callback().
347          */
348         quick_exit(0);
349 }
350
351 static void
352 sigchld_handler(int dummy __unused)
353 {
354
355         /*
356          * The only purpose of this handler is to make SIGCHLD
357          * interrupt the AUTOFSREQUEST ioctl(2), so we can call
358          * wait_for_children().
359          */
360 }
361
362 static void
363 register_sigchld(void)
364 {
365         struct sigaction sa;
366         int error;
367
368         bzero(&sa, sizeof(sa));
369         sa.sa_handler = sigchld_handler;
370         sigfillset(&sa.sa_mask);
371         error = sigaction(SIGCHLD, &sa, NULL);
372         if (error != 0)
373                 log_err(1, "sigaction");
374 }
375
376
377 static int
378 wait_for_children(bool block)
379 {
380         pid_t pid;
381         int status;
382         int num = 0;
383
384         for (;;) {
385                 /*
386                  * If "block" is true, wait for at least one process.
387                  */
388                 if (block && num == 0)
389                         pid = wait4(-1, &status, 0, NULL);
390                 else
391                         pid = wait4(-1, &status, WNOHANG, NULL);
392                 if (pid <= 0)
393                         break;
394                 if (WIFSIGNALED(status)) {
395                         log_warnx("child process %d terminated with signal %d",
396                             pid, WTERMSIG(status));
397                 } else if (WEXITSTATUS(status) != 0) {
398                         log_debugx("child process %d terminated with exit status %d",
399                             pid, WEXITSTATUS(status));
400                 } else {
401                         log_debugx("child process %d terminated gracefully", pid);
402                 }
403                 num++;
404         }
405
406         return (num);
407 }
408
409 static void
410 usage_automountd(void)
411 {
412
413         fprintf(stderr, "usage: automountd [-D name=value][-m maxproc]"
414             "[-o opts][-Tidv]\n");
415         exit(1);
416 }
417
418 void
419 main_automountd(int argc, char **argv)
420 {
421         struct pidfh *pidfh;
422         pid_t pid, otherpid;
423         const char *pidfile_path = AUTOMOUNTD_PIDFILE;
424         char *options = NULL;
425         struct autofs_daemon_request request;
426         int ch, debug = 0, error, maxproc = 30, retval, saved_errno;
427         bool dont_daemonize = false, incomplete_hierarchy = false;
428
429         defined_init();
430
431         while ((ch = getopt(argc, argv, "D:Tdim:o:v")) != -1) {
432                 switch (ch) {
433                 case 'D':
434                         defined_parse_and_add(optarg);
435                         break;
436                 case 'T':
437                         /*
438                          * For compatibility with other implementations,
439                          * such as OS X.
440                          */
441                         debug++;
442                         break;
443                 case 'd':
444                         dont_daemonize = true;
445                         debug++;
446                         break;
447                 case 'i':
448                         incomplete_hierarchy = true;
449                         break;
450                 case 'm':
451                         maxproc = atoi(optarg);
452                         break;
453                 case 'o':
454                         options = concat(options, ',', optarg);
455                         break;
456                 case 'v':
457                         debug++;
458                         break;
459                 case '?':
460                 default:
461                         usage_automountd();
462                 }
463         }
464         argc -= optind;
465         if (argc != 0)
466                 usage_automountd();
467
468         log_init(debug);
469
470         pidfh = pidfile_open(pidfile_path, 0600, &otherpid);
471         if (pidfh == NULL) {
472                 if (errno == EEXIST) {
473                         log_errx(1, "daemon already running, pid: %jd.",
474                             (intmax_t)otherpid);
475                 }
476                 log_err(1, "cannot open or create pidfile \"%s\"",
477                     pidfile_path);
478         }
479
480         autofs_fd = open(AUTOFS_PATH, O_RDWR | O_CLOEXEC);
481         if (autofs_fd < 0 && errno == ENOENT) {
482                 saved_errno = errno;
483                 retval = kldload("autofs");
484                 if (retval != -1)
485                         autofs_fd = open(AUTOFS_PATH, O_RDWR | O_CLOEXEC);
486                 else
487                         errno = saved_errno;
488         }
489         if (autofs_fd < 0)
490                 log_err(1, "failed to open %s", AUTOFS_PATH);
491
492         if (dont_daemonize == false) {
493                 if (daemon(0, 0) == -1) {
494                         log_warn("cannot daemonize");
495                         pidfile_remove(pidfh);
496                         exit(1);
497                 }
498         } else {
499                 lesser_daemon();
500         }
501
502         pidfile_write(pidfh);
503
504         register_sigchld();
505
506         for (;;) {
507                 log_debugx("waiting for request from the kernel");
508
509                 memset(&request, 0, sizeof(request));
510                 error = ioctl(autofs_fd, AUTOFSREQUEST, &request);
511                 if (error != 0) {
512                         if (errno == EINTR) {
513                                 nchildren -= wait_for_children(false);
514                                 assert(nchildren >= 0);
515                                 continue;
516                         }
517
518                         log_err(1, "AUTOFSREQUEST");
519                 }
520
521                 if (dont_daemonize) {
522                         log_debugx("not forking due to -d flag; "
523                             "will exit after servicing a single request");
524                 } else {
525                         nchildren -= wait_for_children(false);
526                         assert(nchildren >= 0);
527
528                         while (maxproc > 0 && nchildren >= maxproc) {
529                                 log_debugx("maxproc limit of %d child processes hit; "
530                                     "waiting for child process to exit", maxproc);
531                                 nchildren -= wait_for_children(true);
532                                 assert(nchildren >= 0);
533                         }
534                         log_debugx("got request; forking child process #%d",
535                             nchildren);
536                         nchildren++;
537
538                         pid = fork();
539                         if (pid < 0)
540                                 log_err(1, "fork");
541                         if (pid > 0)
542                                 continue;
543                 }
544
545                 pidfile_close(pidfh);
546                 handle_request(&request, options, incomplete_hierarchy);
547         }
548 }