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