usr.sbin/autofs: Use proper argument order for calloc(3). (FreeBSD@GitHub 6add75fe)
[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
378 static int
379 wait_for_children(bool block)
380 {
381         pid_t pid;
382         int status;
383         int num = 0;
384
385         for (;;) {
386                 /*
387                  * If "block" is true, wait for at least one process.
388                  */
389                 if (block && num == 0)
390                         pid = wait4(-1, &status, 0, NULL);
391                 else
392                         pid = wait4(-1, &status, WNOHANG, NULL);
393                 if (pid <= 0)
394                         break;
395                 if (WIFSIGNALED(status)) {
396                         log_warnx("child process %d terminated with signal %d",
397                             pid, WTERMSIG(status));
398                 } else if (WEXITSTATUS(status) != 0) {
399                         log_debugx("child process %d terminated with exit status %d",
400                             pid, WEXITSTATUS(status));
401                 } else {
402                         log_debugx("child process %d terminated gracefully", pid);
403                 }
404                 num++;
405         }
406
407         return (num);
408 }
409
410 static void
411 usage_automountd(void)
412 {
413
414         fprintf(stderr, "usage: automountd [-D name=value][-m maxproc]"
415             "[-o opts][-Tidv]\n");
416         exit(1);
417 }
418
419 int
420 main_automountd(int argc, char **argv)
421 {
422         struct pidfh *pidfh;
423         pid_t pid, otherpid;
424         const char *pidfile_path = AUTOMOUNTD_PIDFILE;
425         char *options = NULL;
426         struct autofs_daemon_request request;
427         int ch, debug = 0, error, maxproc = 30, retval, saved_errno;
428         bool dont_daemonize = false, incomplete_hierarchy = false;
429
430         defined_init();
431
432         while ((ch = getopt(argc, argv, "D:Tdim:o:v")) != -1) {
433                 switch (ch) {
434                 case 'D':
435                         defined_parse_and_add(optarg);
436                         break;
437                 case 'T':
438                         /*
439                          * For compatibility with other implementations,
440                          * such as OS X.
441                          */
442                         debug++;
443                         break;
444                 case 'd':
445                         dont_daemonize = true;
446                         debug++;
447                         break;
448                 case 'i':
449                         incomplete_hierarchy = true;
450                         break;
451                 case 'm':
452                         maxproc = atoi(optarg);
453                         break;
454                 case 'o':
455                         options = concat(options, ',', optarg);
456                         break;
457                 case 'v':
458                         debug++;
459                         break;
460                 case '?':
461                 default:
462                         usage_automountd();
463                 }
464         }
465         argc -= optind;
466         if (argc != 0)
467                 usage_automountd();
468
469         log_init(debug);
470
471         pidfh = pidfile_open(pidfile_path, 0600, &otherpid);
472         if (pidfh == NULL) {
473                 if (errno == EEXIST) {
474                         log_errx(1, "daemon already running, pid: %jd.",
475                             (intmax_t)otherpid);
476                 }
477                 log_err(1, "cannot open or create pidfile \"%s\"",
478                     pidfile_path);
479         }
480
481         autofs_fd = open(AUTOFS_PATH, O_RDWR | O_CLOEXEC);
482         if (autofs_fd < 0 && errno == ENOENT) {
483                 saved_errno = errno;
484                 retval = kldload("autofs");
485                 if (retval != -1)
486                         autofs_fd = open(AUTOFS_PATH, O_RDWR | O_CLOEXEC);
487                 else
488                         errno = saved_errno;
489         }
490         if (autofs_fd < 0)
491                 log_err(1, "failed to open %s", AUTOFS_PATH);
492
493         if (dont_daemonize == false) {
494                 if (daemon(0, 0) == -1) {
495                         log_warn("cannot daemonize");
496                         pidfile_remove(pidfh);
497                         exit(1);
498                 }
499         } else {
500                 lesser_daemon();
501         }
502
503         pidfile_write(pidfh);
504
505         register_sigchld();
506
507         for (;;) {
508                 log_debugx("waiting for request from the kernel");
509
510                 memset(&request, 0, sizeof(request));
511                 error = ioctl(autofs_fd, AUTOFSREQUEST, &request);
512                 if (error != 0) {
513                         if (errno == EINTR) {
514                                 nchildren -= wait_for_children(false);
515                                 assert(nchildren >= 0);
516                                 continue;
517                         }
518
519                         log_err(1, "AUTOFSREQUEST");
520                 }
521
522                 if (dont_daemonize) {
523                         log_debugx("not forking due to -d flag; "
524                             "will exit after servicing a single request");
525                 } else {
526                         nchildren -= wait_for_children(false);
527                         assert(nchildren >= 0);
528
529                         while (maxproc > 0 && nchildren >= maxproc) {
530                                 log_debugx("maxproc limit of %d child processes hit; "
531                                     "waiting for child process to exit", maxproc);
532                                 nchildren -= wait_for_children(true);
533                                 assert(nchildren >= 0);
534                         }
535                         log_debugx("got request; forking child process #%d",
536                             nchildren);
537                         nchildren++;
538
539                         pid = fork();
540                         if (pid < 0)
541                                 log_err(1, "fork");
542                         if (pid > 0)
543                                 continue;
544                 }
545
546                 pidfile_close(pidfh);
547                 handle_request(&request, options, incomplete_hierarchy);
548         }
549
550         pidfile_close(pidfh);
551
552         return (0);
553 }
554