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