Add a shorter syntax for plugin names.
[nbdkit/ericb.git] / src / main.c
blob724515ba20f399f1b108ff1bf0d67d86c278c262
1 /* nbdkit
2 * Copyright (C) 2013-2014 Red Hat Inc.
3 * All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
9 * * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
12 * * Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
16 * * Neither the name of Red Hat nor the names of its contributors may be
17 * used to endorse or promote products derived from this software without
18 * specific prior written permission.
20 * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
22 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
23 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
27 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
28 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
30 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
34 #include <config.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <fcntl.h>
40 #include <unistd.h>
41 #include <signal.h>
42 #include <getopt.h>
43 #include <limits.h>
44 #include <pwd.h>
45 #include <grp.h>
46 #include <sys/types.h>
47 #include <errno.h>
49 #include <pthread.h>
51 #include <dlfcn.h>
53 #include "nbdkit-plugin.h"
54 #include "internal.h"
56 static void open_plugin_so (const char *filename);
57 static void start_serving (void);
58 static void set_up_signals (void);
59 static void change_user (void);
60 static void write_pidfile (void);
61 static void fork_into_background (void);
62 static uid_t parseuser (const char *);
63 static gid_t parsegroup (const char *);
65 int foreground; /* -f */
66 const char *ipaddr; /* -i */
67 int listen_stdin; /* -s */
68 char *pidfile; /* -P */
69 const char *port; /* -p */
70 int readonly; /* -r */
71 char *unixsocket; /* -U */
72 const char *user, *group; /* -u & -g */
73 int verbose; /* -v */
75 volatile int quit;
77 enum { HELP_OPTION = CHAR_MAX + 1 };
79 static const char *short_options = "fg:i:p:P:rsu:U:vV";
80 static const struct option long_options[] = {
81 { "help", 0, NULL, HELP_OPTION },
82 { "foreground", 0, NULL, 'f' },
83 { "no-fork", 0, NULL, 'f' },
84 { "group", 1, NULL, 'g' },
85 { "ip-addr", 1, NULL, 'i' },
86 { "ipaddr", 1, NULL, 'i' },
87 { "pid-file", 1, NULL, 'P' },
88 { "pidfile", 1, NULL, 'P' },
89 { "port", 1, NULL, 'p' },
90 { "read-only", 0, NULL, 'r' },
91 { "readonly", 0, NULL, 'r' },
92 { "single", 0, NULL, 's' },
93 { "stdin", 0, NULL, 's' },
94 { "unix", 1, NULL, 'U' },
95 { "user", 1, NULL, 'u' },
96 { "verbose", 0, NULL, 'v' },
97 { "version", 0, NULL, 'V' },
100 static void
101 usage (void)
103 printf ("nbdkit [-f] [-g GROUP] [-i IPADDR] [-P PIDFILE] [-p PORT]\n"
104 " [-r] [-s] [-U SOCKET] [-u USER] [-v] [-V]\n"
105 " PLUGIN [key=value [key=value [...]]]\n"
106 "\n"
107 "Please read the nbdkit(1) manual page for full usage.\n");
110 static void
111 display_version (void)
113 printf ("%s %s\n", PACKAGE_NAME, PACKAGE_VERSION);
117 main (int argc, char *argv[])
119 int c;
120 int option_index;
121 int help = 0, version = 0;
123 tls_init ();
125 for (;;) {
126 c = getopt_long (argc, argv, short_options, long_options, &option_index);
127 if (c == -1)
128 break;
130 switch (c) {
131 case 0: /* options which are long only */
132 fprintf (stderr, "%s: unknown long option: %s (%d)\n",
133 program_name, long_options[option_index].name, option_index);
134 exit (EXIT_FAILURE);
136 case 'f':
137 foreground = 1;
138 break;
140 case 'g':
141 group = optarg;
142 break;
144 case 'i':
145 ipaddr = optarg;
146 break;
148 case 'P':
149 pidfile = nbdkit_absolute_path (optarg);
150 if (pidfile == NULL)
151 exit (EXIT_FAILURE);
152 break;
154 case 'p':
155 port = optarg;
156 break;
158 case 'r':
159 readonly = 1;
160 break;
162 case 's':
163 listen_stdin = 1;
164 break;
166 case 'U':
167 unixsocket = nbdkit_absolute_path (optarg);
168 if (unixsocket == NULL)
169 exit (EXIT_FAILURE);
170 break;
172 case 'u':
173 user = optarg;
174 break;
176 case 'v':
177 verbose = 1;
178 break;
180 case 'V':
181 version = 1;
182 break;
184 case HELP_OPTION:
185 help = 1;
186 break;
188 default:
189 usage ();
190 exit (EXIT_FAILURE);
194 /* No extra parameters. */
195 if (optind >= argc) {
196 if (help) {
197 usage ();
198 exit (EXIT_SUCCESS);
200 if (version) {
201 display_version ();
202 exit (EXIT_SUCCESS);
205 /* Otherwise this is an error. */
206 fprintf (stderr,
207 "%s: no plugins given on the command line.\nRead nbdkit(1) for documentation.\n",
208 program_name);
209 exit (EXIT_FAILURE);
212 /* Remaining command line arguments define the plugins and plugin
213 * configuration. If --help or --version was specified, we still
214 * partially parse these in order that we can display the per-plugin
215 * help/version information. In future (when the new protocol and
216 * export names are permitted) we will allow multiple plugins to be
217 * given, but at the moment only one plugin is allowed.
219 while (optind < argc) {
220 const char *filename = argv[optind];
221 char *p;
223 open_plugin_so (filename);
225 /* Find key=value configuration parameters for this plugin. */
226 ++optind;
227 while (optind < argc && (p = strchr (argv[optind], '=')) != NULL) {
228 if (help || version)
229 continue;
231 *p = '\0';
232 plugin_config (argv[optind], p+1);
234 ++optind;
237 if (help) {
238 usage ();
239 printf ("\n%s:\n\n", filename);
240 plugin_usage ();
241 exit (EXIT_SUCCESS);
244 if (version) {
245 display_version ();
246 plugin_version ();
247 exit (EXIT_SUCCESS);
250 plugin_config_complete ();
252 /* If we supported export names, then we'd continue in the loop
253 * here, but at the moment only one plugin may be used per server
254 * so exit if there are any more.
256 ++optind;
257 if (optind < argc) {
258 fprintf (stderr, "%s: this server only supports a single plugin\n",
259 program_name);
260 exit (EXIT_FAILURE);
264 start_serving ();
266 plugin_cleanup ();
268 free (unixsocket);
269 free (pidfile);
271 exit (EXIT_SUCCESS);
274 static void
275 open_plugin_so (const char *name)
277 char *filename = (char *) name;
278 int free_filename = 0;
279 void *dl;
280 struct nbdkit_plugin *(*plugin_init) (void);
281 char *error;
283 if (strchr (name, '.') == NULL && strchr (name, '/') == NULL) {
284 /* Short names are rewritten relative to libdir. */
285 if (asprintf (&filename, "%s/nbdkit-%s-plugin.so", plugindir, name) == -1) {
286 perror ("asprintf");
287 exit (EXIT_FAILURE);
289 free_filename = 1;
292 dl = dlopen (filename, RTLD_NOW|RTLD_LOCAL);
293 if (dl == NULL) {
294 fprintf (stderr, "%s: %s: %s\n", program_name, filename, dlerror ());
295 exit (EXIT_FAILURE);
298 /* Initialize the plugin. See dlopen(3) to understand C weirdness. */
299 dlerror ();
300 *(void **) (&plugin_init) = dlsym (dl, "plugin_init");
301 if ((error = dlerror ()) != NULL) {
302 fprintf (stderr, "%s: %s: %s\n", program_name, name, dlerror ());
303 exit (EXIT_FAILURE);
305 if (!plugin_init) {
306 fprintf (stderr, "%s: %s: invalid plugin_init\n", program_name, name);
307 exit (EXIT_FAILURE);
310 /* Register the plugin. */
311 plugin_register (filename, dl, plugin_init);
313 if (free_filename)
314 free (filename);
317 static void
318 start_serving (void)
320 int *socks;
321 size_t nr_socks;
323 /* If the user has mixed up -p/-U/-s options, then give an error.
325 * XXX Actually the server could easily be extended to handle both
326 * TCP/IP and Unix sockets, or even multiple TCP/IP ports.
328 if ((port && unixsocket) || (port && listen_stdin) ||
329 (unixsocket && listen_stdin)) {
330 fprintf (stderr, "%s: -p, -U and -s options cannot appear at the same time\n",
331 program_name);
332 exit (EXIT_FAILURE);
335 set_up_signals ();
337 /* Handling a single connection on stdin/stdout. */
338 if (listen_stdin) {
339 change_user ();
340 write_pidfile ();
341 tls_new_server_thread ();
342 if (handle_single_connection (0, 1) == -1)
343 exit (EXIT_FAILURE);
344 return;
347 /* Handling multiple connections on TCP/IP or a Unix domain socket. */
348 if (unixsocket)
349 socks = bind_unix_socket (&nr_socks);
350 else
351 socks = bind_tcpip_socket (&nr_socks);
353 change_user ();
354 fork_into_background ();
355 write_pidfile ();
356 accept_incoming_connections (socks, nr_socks);
358 free_listening_sockets (socks, nr_socks);
361 static void
362 handle_quit (int sig)
364 quit = 1;
367 static void
368 set_up_signals (void)
370 struct sigaction sa;
372 memset (&sa, 0, sizeof sa);
373 sa.sa_flags = SA_RESTART;
374 sa.sa_handler = handle_quit;
375 sigaction (SIGINT, &sa, NULL);
376 sigaction (SIGQUIT, &sa, NULL);
377 sigaction (SIGTERM, &sa, NULL);
378 sigaction (SIGHUP, &sa, NULL);
380 memset (&sa, 0, sizeof sa);
381 sa.sa_flags = SA_RESTART;
382 sa.sa_handler = SIG_IGN;
383 sigaction (SIGPIPE, &sa, NULL);
386 static void
387 change_user (void)
389 if (group) {
390 gid_t gid = parsegroup (group);
392 if (setgid (gid) == -1) {
393 perror ("setgid");
394 exit (EXIT_FAILURE);
397 /* Kill supplemental groups from parent process. */
398 if (setgroups (1, &gid) == -1) {
399 perror ("setgroups");
400 exit (EXIT_FAILURE);
403 debug ("changed group to %s", group);
406 if (user) {
407 uid_t uid = parseuser (user);
409 if (setuid (uid) == -1) {
410 perror ("setuid");
411 exit (EXIT_FAILURE);
414 debug ("changed user to %s", user);
418 static void
419 write_pidfile (void)
421 int fd;
422 pid_t pid;
423 char pidstr[64];
424 size_t len;
426 if (!pidfile)
427 return;
429 pid = getpid ();
430 snprintf (pidstr, sizeof pidstr, "%d\n", pid);
431 len = strlen (pidstr);
433 fd = open (pidfile, O_WRONLY|O_TRUNC|O_CREAT|O_CLOEXEC|O_NOCTTY, 0644);
434 if (fd == -1) {
435 perror (pidfile);
436 exit (EXIT_FAILURE);
439 if (write (fd, pidstr, len) < len ||
440 close (fd) == -1) {
441 perror (pidfile);
442 exit (EXIT_FAILURE);
445 debug ("written pidfile %s", pidfile);
448 static void
449 fork_into_background (void)
451 pid_t pid;
453 if (foreground)
454 return;
456 pid = fork ();
457 if (pid == -1) {
458 perror ("fork");
459 exit (EXIT_FAILURE);
462 if (pid > 0) /* Parent process exits. */
463 exit (EXIT_SUCCESS);
465 chdir ("/");
467 /* Close stdin/stdout and redirect to /dev/null. */
468 close (0);
469 close (1);
470 open ("/dev/null", O_RDONLY);
471 open ("/dev/null", O_WRONLY);
473 /* If not verbose, set stderr to the same as stdout as well. */
474 if (!verbose)
475 dup2 (1, 2);
477 debug ("forked into background (new pid = %d)", getpid ());
480 static uid_t
481 parseuser (const char *id)
483 struct passwd *pwd;
484 int saved_errno;
486 errno = 0;
487 pwd = getpwnam (id);
489 if (NULL == pwd) {
490 int val;
492 saved_errno = errno;
494 if (sscanf (id, "%d", &val) == 1)
495 return val;
497 fprintf (stderr, "%s: -u option: %s is not a valid user name or uid",
498 program_name, id);
499 if (saved_errno != 0)
500 fprintf (stderr, " (getpwnam error: %s)", strerror (saved_errno));
501 fprintf (stderr, "\n");
502 exit (EXIT_FAILURE);
505 return pwd->pw_uid;
508 static gid_t
509 parsegroup (const char *id)
511 struct group *grp;
512 int saved_errno;
514 errno = 0;
515 grp = getgrnam (id);
517 if (NULL == grp) {
518 int val;
520 saved_errno = errno;
522 if (sscanf (id, "%d", &val) == 1)
523 return val;
525 fprintf (stderr, "%s: -g option: %s is not a valid group name or gid",
526 program_name, id);
527 if (saved_errno != 0)
528 fprintf (stderr, " (getgrnam error: %s)", strerror (saved_errno));
529 fprintf (stderr, "\n");
530 exit (EXIT_FAILURE);
533 return grp->gr_gid;