/* g13.c - Disk Key management with GnuPG * Copyright (C) 2009 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #include #include #include #include #include #define INCLUDED_BY_MAIN_MODULE 1 #include "g13.h" #include #include #include "../common/i18n.h" #include "../common/sysutils.h" #include "../common/gc-opt-flags.h" #include "../common/asshelp.h" #include "../common/init.h" #include "keyblob.h" #include "server.h" #include "runner.h" #include "create.h" #include "mount.h" #include "suspend.h" #include "mountinfo.h" #include "backend.h" #include "call-syshelp.h" enum cmd_and_opt_values { aNull = 0, oQuiet = 'q', oVerbose = 'v', oRecipient = 'r', aGPGConfList = 500, aGPGConfTest, aCreate, aMount, aUmount, aSuspend, aResume, aServer, aFindDevice, oOptions, oDebug, oDebugLevel, oDebugAll, oDebugNone, oDebugWait, oDebugAllowCoreDump, oLogFile, oNoLogFile, oAuditLog, oOutput, oAgentProgram, oGpgProgram, oType, oDisplay, oTTYname, oTTYtype, oLCctype, oLCmessages, oXauthority, oStatusFD, oLoggerFD, oNoVerbose, oNoSecmemWarn, oNoGreeting, oNoTTY, oNoOptions, oHomedir, oWithColons, oDryRun, oNoDetach, oNoRandomSeedFile, oFakedSystemTime }; static ARGPARSE_OPTS opts[] = { ARGPARSE_group (300, N_("@Commands:\n ")), ARGPARSE_c (aCreate, "create", N_("Create a new file system container")), ARGPARSE_c (aMount, "mount", N_("Mount a file system container") ), ARGPARSE_c (aUmount, "umount", N_("Unmount a file system container") ), ARGPARSE_c (aSuspend, "suspend", N_("Suspend a file system container") ), ARGPARSE_c (aResume, "resume", N_("Resume a file system container") ), ARGPARSE_c (aServer, "server", N_("Run in server mode")), ARGPARSE_c (aFindDevice, "find-device", "@"), ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"), ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"), ARGPARSE_group (301, N_("@\nOptions:\n ")), ARGPARSE_s_s (oRecipient, "recipient", N_("|USER-ID|encrypt for USER-ID")), ARGPARSE_s_s (oType, "type", N_("|NAME|use container format NAME")), ARGPARSE_s_s (oOutput, "output", N_("|FILE|write output to FILE")), ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")), ARGPARSE_s_n (oNoTTY, "no-tty", N_("don't use the terminal at all")), ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")), ARGPARSE_s_s (oLogFile, "log-file", N_("|FILE|write log output to FILE")), ARGPARSE_s_n (oNoLogFile, "no-log-file", "@"), ARGPARSE_s_i (oLoggerFD, "logger-fd", "@"), ARGPARSE_s_n (oDryRun, "dry-run", N_("do not make any changes")), ARGPARSE_conffile (oOptions, "options", N_("|FILE|read options from FILE")), ARGPARSE_s_s (oDebug, "debug", "@"), ARGPARSE_s_s (oDebugLevel, "debug-level", N_("|LEVEL|set the debugging level to LEVEL")), ARGPARSE_s_n (oDebugAll, "debug-all", "@"), ARGPARSE_s_n (oDebugNone, "debug-none", "@"), ARGPARSE_s_i (oDebugWait, "debug-wait", "@"), ARGPARSE_s_n (oDebugAllowCoreDump, "debug-allow-core-dump", "@"), ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")), ARGPARSE_group (302, N_( "@\n(See the man page for a complete listing of all commands and options)\n" )), ARGPARSE_group (303, N_("@\nExamples:\n\n" " blurb\n" " blurb\n")), /* Hidden options. */ ARGPARSE_s_n (oNoVerbose, "no-verbose", "@"), ARGPARSE_s_n (oNoSecmemWarn, "no-secmem-warning", "@"), ARGPARSE_s_n (oNoGreeting, "no-greeting", "@"), ARGPARSE_noconffile (oNoOptions, "no-options", "@"), ARGPARSE_s_s (oHomedir, "homedir", "@"), ARGPARSE_s_s (oAgentProgram, "agent-program", "@"), ARGPARSE_s_s (oGpgProgram, "gpg-program", "@"), ARGPARSE_s_s (oDisplay, "display", "@"), ARGPARSE_s_s (oTTYname, "ttyname", "@"), ARGPARSE_s_s (oTTYtype, "ttytype", "@"), ARGPARSE_s_s (oLCctype, "lc-ctype", "@"), ARGPARSE_s_s (oLCmessages, "lc-messages", "@"), ARGPARSE_s_s (oXauthority, "xauthority", "@"), ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"), ARGPARSE_s_n (oWithColons, "with-colons", "@"), ARGPARSE_s_n (oNoRandomSeedFile, "no-random-seed-file", "@"), /* Command aliases. */ ARGPARSE_end () }; /* The list of supported debug flags. */ static struct debug_flags_s debug_flags [] = { { DBG_MOUNT_VALUE , "mount" }, { DBG_CRYPTO_VALUE , "crypto" }, { DBG_MEMORY_VALUE , "memory" }, { DBG_MEMSTAT_VALUE, "memstat" }, { DBG_IPC_VALUE , "ipc" }, { 0, NULL } }; /* The timer tick interval used by the idle task. */ #define TIMERTICK_INTERVAL_SEC (1) /* It is possible that we are currently running under setuid permissions. */ static int maybe_setuid = 1; /* Helper to implement --debug-level and --debug. */ static const char *debug_level; static unsigned int debug_value; /* Flag to indicate that a shutdown was requested. */ static int shutdown_pending; /* The thread id of the idle task. */ static npth_t idle_task_thread; /* The container type as specified on the command line. */ static int cmdline_conttype; static void set_cmd (enum cmd_and_opt_values *ret_cmd, enum cmd_and_opt_values new_cmd ); static void start_idle_task (void); static void join_idle_task (void); /* Begin NPth wrapper functions. */ ASSUAN_SYSTEM_NPTH_IMPL; static const char * my_strusage( int level ) { const char *p; switch (level) { case 9: p = "GPL-3.0-or-later"; break; case 11: p = "@G13@ (@GNUPG@)"; break; case 13: p = VERSION; break; case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; case 17: p = PRINTABLE_OS_NAME; break; case 19: p = _("Please report bugs to <" PACKAGE_BUGREPORT ">.\n"); break; case 1: case 40: p = _("Usage: @G13@ [options] [files] (-h for help)"); break; case 41: p = _("Syntax: @G13@ [options] [files]\n" "Create, mount or unmount an encrypted file system container\n"); break; case 31: p = "\nHome: "; break; case 32: p = gnupg_homedir (); break; default: p = NULL; break; } return p; } static void wrong_args (const char *text) { fprintf (stderr, _("usage: %s [options] "), G13_NAME); fputs (text, stderr); putc ('\n', stderr); g13_exit (2); } /* Setup the debugging. With a DEBUG_LEVEL of NULL only the active debug flags are propagated to the subsystems. With DEBUG_LEVEL set, a specific set of debug flags is set; and individual debugging flags will be added on top. */ static void set_debug (void) { int numok = (debug_level && digitp (debug_level)); int numlvl = numok? atoi (debug_level) : 0; if (!debug_level) ; else if (!strcmp (debug_level, "none") || (numok && numlvl < 1)) opt.debug = 0; else if (!strcmp (debug_level, "basic") || (numok && numlvl <= 2)) opt.debug = DBG_IPC_VALUE|DBG_MOUNT_VALUE; else if (!strcmp (debug_level, "advanced") || (numok && numlvl <= 5)) opt.debug = DBG_IPC_VALUE|DBG_MOUNT_VALUE; else if (!strcmp (debug_level, "expert") || (numok && numlvl <= 8)) opt.debug = (DBG_IPC_VALUE|DBG_MOUNT_VALUE|DBG_CRYPTO_VALUE); else if (!strcmp (debug_level, "guru") || numok) { opt.debug = ~0; /* if (numok) */ /* opt.debug &= ~(DBG_HASHING_VALUE); */ } else { log_error (_("invalid debug-level '%s' given\n"), debug_level); g13_exit(2); } opt.debug |= debug_value; if (opt.debug && !opt.verbose) opt.verbose = 1; if (opt.debug) opt.quiet = 0; if (opt.debug & DBG_CRYPTO_VALUE ) gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1); gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); if (opt.debug) parse_debug_flag (NULL, &opt.debug, debug_flags); } static void set_cmd (enum cmd_and_opt_values *ret_cmd, enum cmd_and_opt_values new_cmd) { enum cmd_and_opt_values cmd = *ret_cmd; if (!cmd || cmd == new_cmd) cmd = new_cmd; else { log_error (_("conflicting commands\n")); g13_exit (2); } *ret_cmd = cmd; } int main ( int argc, char **argv) { ARGPARSE_ARGS pargs; int orig_argc; char **orig_argv; gpg_error_t err = 0; /* const char *fname; */ int may_coredump; char *last_configname = NULL; const char *configname = NULL; int debug_argparser = 0; int no_more_options = 0; char *logfile = NULL; int greeting = 0; int nogreeting = 0; /* int debug_wait = 0; */ int use_random_seed = 1; /* int nodetach = 0; */ /* int nokeysetup = 0; */ enum cmd_and_opt_values cmd = 0; struct server_control_s ctrl; strlist_t recipients = NULL; early_system_init (); gnupg_reopen_std (G13_NAME); set_strusage (my_strusage); gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); log_set_prefix (G13_NAME, GPGRT_LOG_WITH_PREFIX); /* Make sure that our subsystems are ready. */ i18n_init (); init_common_subsystems (&argc, &argv); npth_init (); /* Take extra care of the random pool. */ gcry_control (GCRYCTL_USE_SECURE_RNDPOOL); may_coredump = disable_core_dumps (); g13_init_signals (); dotlock_create (NULL, 0); /* Register locking cleanup. */ opt.session_env = session_env_new (); if (!opt.session_env) log_fatal ("error allocating session environment block: %s\n", strerror (errno)); /* First check whether we have a config file on the commandline. */ orig_argc = argc; orig_argv = argv; pargs.argc = &argc; pargs.argv = &argv; pargs.flags= 1|(1<<6); /* Do not remove the args, ignore version. */ pargs.flags= (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION); while (gnupg_argparse (NULL, &pargs, opts)) { switch (pargs.r_opt) { case oDebug: case oDebugAll: debug_argparser++; break; case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break; } } /* Reset the flags. */ pargs.flags &= ~(ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION); /* Initialize the secure memory. */ gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0); maybe_setuid = 0; /* * Now we are now working under our real uid */ /* Setup malloc hooks. */ { struct assuan_malloc_hooks malloc_hooks; malloc_hooks.malloc = gcry_malloc; malloc_hooks.realloc = gcry_realloc; malloc_hooks.free = gcry_free; assuan_set_malloc_hooks (&malloc_hooks); } /* Prepare libassuan. */ assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH); setup_libassuan_logging (&opt.debug, NULL); /* Setup a default control structure for command line mode. */ memset (&ctrl, 0, sizeof ctrl); g13_init_default_ctrl (&ctrl); ctrl.no_server = 1; ctrl.status_fd = -1; /* No status output. */ /* The configuraton directories for use by gpgrt_argparser. */ gnupg_set_confdir (GNUPG_CONFDIR_SYS, gnupg_sysconfdir ()); gnupg_set_confdir (GNUPG_CONFDIR_USER, gnupg_homedir ()); argc = orig_argc; argv = orig_argv; pargs.argc = &argc; pargs.argv = &argv; pargs.flags |= (ARGPARSE_FLAG_RESET | ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_SYS | ARGPARSE_FLAG_USER); while (!no_more_options && gnupg_argparser (&pargs, opts, G13_NAME EXTSEP_S "conf")) { switch (pargs.r_opt) { case ARGPARSE_CONFFILE: { if (debug_argparser) log_info (_("reading options from '%s'\n"), pargs.r_type? pargs.r.ret_str: "[cmdline]"); if (pargs.r_type) { xfree (last_configname); last_configname = xstrdup (pargs.r.ret_str); configname = last_configname; } else configname = NULL; } break; case aGPGConfList: case aGPGConfTest: set_cmd (&cmd, pargs.r_opt); nogreeting = 1; /* nokeysetup = 1; */ break; case aServer: case aMount: case aUmount: case aSuspend: case aResume: case aCreate: case aFindDevice: set_cmd (&cmd, pargs.r_opt); break; case oOutput: opt.outfile = pargs.r.ret_str; break; case oQuiet: opt.quiet = 1; break; case oNoGreeting: nogreeting = 1; break; case oNoTTY: break; case oDryRun: opt.dry_run = 1; break; case oVerbose: opt.verbose++; gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); break; case oNoVerbose: opt.verbose = 0; gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); break; case oLogFile: logfile = pargs.r.ret_str; break; case oNoLogFile: logfile = NULL; break; case oNoDetach: /*nodetach = 1; */break; case oDebug: if (parse_debug_flag (pargs.r.ret_str, &opt.debug, debug_flags)) { pargs.r_opt = ARGPARSE_INVALID_ARG; pargs.err = ARGPARSE_PRINT_ERROR; } break; case oDebugAll: debug_value = ~0; break; case oDebugNone: debug_value = 0; break; case oDebugLevel: debug_level = pargs.r.ret_str; break; case oDebugWait: /*debug_wait = pargs.r.ret_int; */break; case oDebugAllowCoreDump: may_coredump = enable_core_dumps (); break; case oStatusFD: ctrl.status_fd = pargs.r.ret_int; break; case oLoggerFD: log_set_fd (pargs.r.ret_int ); break; case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break; case oAgentProgram: opt.agent_program = pargs.r.ret_str; break; case oGpgProgram: opt.gpg_program = pargs.r.ret_str; break; case oDisplay: opt.display = xstrdup (pargs.r.ret_str); break; case oTTYname: opt.ttyname = xstrdup (pargs.r.ret_str); break; case oTTYtype: opt.ttytype = xstrdup (pargs.r.ret_str); break; case oLCctype: opt.lc_ctype = xstrdup (pargs.r.ret_str); break; case oLCmessages: opt.lc_messages = xstrdup (pargs.r.ret_str); break; case oXauthority: opt.xauthority = xstrdup (pargs.r.ret_str); break; case oFakedSystemTime: { time_t faked_time = isotime2epoch (pargs.r.ret_str); if (faked_time == (time_t)(-1)) faked_time = (time_t)strtoul (pargs.r.ret_str, NULL, 10); gnupg_set_time (faked_time, 0); } break; case oNoSecmemWarn: gcry_control (GCRYCTL_DISABLE_SECMEM_WARN); break; case oNoRandomSeedFile: use_random_seed = 0; break; case oRecipient: /* Store the encryption key. */ add_to_strlist (&recipients, pargs.r.ret_str); break; case oType: if (!strcmp (pargs.r.ret_str, "help")) { be_parse_conttype_name (NULL); g13_exit (0); } cmdline_conttype = be_parse_conttype_name (pargs.r.ret_str); if (!cmdline_conttype) { pargs.r_opt = ARGPARSE_INVALID_ARG; pargs.err = ARGPARSE_PRINT_ERROR; } break; default: if (configname) pargs.err = ARGPARSE_PRINT_WARNING; else pargs.err = ARGPARSE_PRINT_ERROR; break; } } gnupg_argparse (NULL, &pargs, NULL); /* Release internal state. */ /* Construct GPG arguments. */ { strlist_t last; last = append_to_strlist (&opt.gpg_arguments, "-z"); last = append_to_strlist (&last, "0"); last = append_to_strlist (&last, "--trust-model"); last = append_to_strlist (&last, "always"); (void) last; } if (!last_configname) opt.config_filename = make_filename (gnupg_homedir (), G13_NAME EXTSEP_S "conf", NULL); else { opt.config_filename = last_configname; last_configname = NULL; } if (log_get_errorcount(0)) g13_exit(2); /* Now that we have the options parsed we need to update the default control structure. */ g13_init_default_ctrl (&ctrl); ctrl.recipients = recipients; recipients = NULL; if (nogreeting) greeting = 0; if (greeting) { fprintf (stderr, "%s %s; %s\n", strusage(11), strusage(13), strusage(14) ); fprintf (stderr, "%s\n", strusage(15) ); } if (may_coredump && !opt.quiet) log_info (_("WARNING: program may create a core file!\n")); /* Print a warning if an argument looks like an option. */ if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN)) { int i; for (i=0; i < argc; i++) if (argv[i][0] == '-' && argv[i][1] == '-') log_info (_("Note: '%s' is not considered an option\n"), argv[i]); } if (logfile) { log_set_file (logfile); log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID )); } if (gnupg_faked_time_p ()) { gnupg_isotime_t tbuf; log_info (_("WARNING: running with faked system time: ")); gnupg_get_isotime (tbuf); dump_isotime (tbuf); log_printf ("\n"); } /* Print any pending secure memory warnings. */ gcry_control (GCRYCTL_RESUME_SECMEM_WARN); /* Setup the debug flags for all subsystems. */ set_debug (); /* Install emergency cleanup handler. */ g13_install_emergency_cleanup (); /* Terminate if we found any error until now. */ if (log_get_errorcount(0)) g13_exit (2); /* Set the standard GnuPG random seed file. */ if (use_random_seed) { char *p = make_filename (gnupg_homedir (), "random_seed", NULL); gcry_control (GCRYCTL_SET_RANDOM_SEED_FILE, p); xfree(p); } /* Store given filename into FNAME. */ /* fname = argc? *argv : NULL; */ /* Parse all given encryption keys. This does a lookup of the keys and stops if any of the given keys was not found. */ #if 0 /* Currently not implemented. */ if (!nokeysetup) { strlist_t sl; int failed = 0; for (sl = ctrl->recipients; sl; sl = sl->next) if (check_encryption_key ()) failed = 1; if (failed) g13_exit (1); } #endif /*0*/ /* Dispatch command. */ err = 0; switch (cmd) { case aGPGConfList: { /* List options and default values in the GPG Conf format. */ char *config_filename_esc = percent_escape (opt.config_filename, NULL); printf ("gpgconf-g13.conf:%lu:\"%s\n", GC_OPT_FLAG_DEFAULT, config_filename_esc); xfree (config_filename_esc); printf ("verbose:%lu:\n", GC_OPT_FLAG_NONE); printf ("quiet:%lu:\n", GC_OPT_FLAG_NONE); printf ("debug-level:%lu:\"none:\n", GC_OPT_FLAG_DEFAULT); printf ("log-file:%lu:\n", GC_OPT_FLAG_NONE); } break; case aGPGConfTest: /* This is merely a dummy command to test whether the configuration file is valid. */ break; case aServer: { start_idle_task (); ctrl.no_server = 0; err = g13_server (&ctrl); if (err) log_error ("server exited with error: %s <%s>\n", gpg_strerror (err), gpg_strsource (err)); else g13_request_shutdown (); } break; case aFindDevice: { char *blockdev; if (argc != 1) wrong_args ("--find-device name"); err = call_syshelp_find_device (&ctrl, argv[0], &blockdev); if (err) log_error ("error finding device '%s': %s <%s>\n", argv[0], gpg_strerror (err), gpg_strsource (err)); else puts (blockdev); } break; case aCreate: /* Create a new container. */ { if (argc != 1) wrong_args ("--create filename"); start_idle_task (); err = g13_create_container (&ctrl, argv[0]); if (err) log_error ("error creating a new container: %s <%s>\n", gpg_strerror (err), gpg_strsource (err)); else g13_request_shutdown (); } break; case aMount: /* Mount a container. */ { if (argc != 1 && argc != 2 ) wrong_args ("--mount filename [mountpoint]"); start_idle_task (); err = g13_mount_container (&ctrl, argv[0], argc == 2?argv[1]:NULL); if (err) log_error ("error mounting container '%s': %s <%s>\n", *argv, gpg_strerror (err), gpg_strsource (err)); } break; case aUmount: /* Unmount a mounted container. */ { if (argc != 1) wrong_args ("--umount filename"); err = g13_umount_container (&ctrl, argv[0], NULL); if (err) log_error ("error unmounting container '%s': %s <%s>\n", *argv, gpg_strerror (err), gpg_strsource (err)); } break; case aSuspend: /* Suspend a container. */ { /* Fixme: Should we add a suspend all container option? */ if (argc != 1) wrong_args ("--suspend filename"); err = g13_suspend_container (&ctrl, argv[0]); if (err) log_error ("error suspending container '%s': %s <%s>\n", *argv, gpg_strerror (err), gpg_strsource (err)); } break; case aResume: /* Resume a suspended container. */ { /* Fixme: Should we add a resume all container option? */ if (argc != 1) wrong_args ("--resume filename"); err = g13_resume_container (&ctrl, argv[0]); if (err) log_error ("error resuming container '%s': %s <%s>\n", *argv, gpg_strerror (err), gpg_strsource (err)); } break; default: log_error (_("invalid command (there is no implicit command)\n")); break; } g13_deinit_default_ctrl (&ctrl); if (!err) join_idle_task (); /* Cleanup. */ g13_exit (0); return 8; /*NOTREACHED*/ } /* Store defaults into the per-connection CTRL object. */ void g13_init_default_ctrl (ctrl_t ctrl) { ctrl->conttype = cmdline_conttype? cmdline_conttype : CONTTYPE_ENCFS; } /* Release remaining resources allocated in the CTRL object. */ void g13_deinit_default_ctrl (ctrl_t ctrl) { call_syshelp_release (ctrl); FREE_STRLIST (ctrl->recipients); } /* Request a shutdown. This can be used when the process should * finish instead of running the idle task. */ void g13_request_shutdown (void) { shutdown_pending++; } /* This function is called for each signal we catch. It is run in the main context or the one of a NPth thread and thus it is not restricted in what it may do. */ static void handle_signal (int signo) { switch (signo) { #ifndef HAVE_W32_SYSTEM case SIGHUP: log_info ("SIGHUP received - re-reading configuration\n"); /* Fixme: Not yet implemented. */ break; case SIGUSR1: log_info ("SIGUSR1 received - printing internal information:\n"); /* Fixme: We need to see how to integrate pth dumping into our logging system. */ /* pth_ctrl (PTH_CTRL_DUMPSTATE, log_get_stream ()); */ mountinfo_dump_all (); break; case SIGUSR2: log_info ("SIGUSR2 received - no action defined\n"); break; case SIGTERM: if (!shutdown_pending) log_info ("SIGTERM received - shutting down ...\n"); else log_info ("SIGTERM received - still %u runners active\n", runner_get_threads ()); shutdown_pending++; if (shutdown_pending > 2) { log_info ("shutdown forced\n"); log_info ("%s %s stopped\n", strusage(11), strusage(13) ); g13_exit (0); } break; case SIGINT: log_info ("SIGINT received - immediate shutdown\n"); log_info( "%s %s stopped\n", strusage(11), strusage(13)); g13_exit (0); break; #endif /*!HAVE_W32_SYSTEM*/ default: log_info ("signal %d received - no action defined\n", signo); } } /* This ticker function is called about every TIMERTICK_INTERVAL_SEC seconds. */ static void handle_tick (void) { /* log_debug ("TICK\n"); */ } /* The idle task. We use a separate thread to do idle stuff and to catch signals. */ static void * idle_task (void *dummy_arg) { int signo; /* The number of a raised signal is stored here. */ int saved_errno; struct timespec abstime; struct timespec curtime; struct timespec timeout; int ret; (void)dummy_arg; /* Create the event to catch the signals. */ #ifndef HAVE_W32_SYSTEM npth_sigev_init (); npth_sigev_add (SIGHUP); npth_sigev_add (SIGUSR1); npth_sigev_add (SIGUSR2); npth_sigev_add (SIGINT); npth_sigev_add (SIGTERM); npth_sigev_fini (); #endif npth_clock_gettime (&abstime); abstime.tv_sec += TIMERTICK_INTERVAL_SEC; for (;;) { /* The shutdown flag allows us to terminate the idle task. */ if (shutdown_pending) { runner_cancel_all (); if (!runner_get_threads ()) break; /* ready */ } npth_clock_gettime (&curtime); if (!(npth_timercmp (&curtime, &abstime, <))) { /* Timeout. */ handle_tick (); npth_clock_gettime (&abstime); abstime.tv_sec += TIMERTICK_INTERVAL_SEC; } npth_timersub (&abstime, &curtime, &timeout); #ifndef HAVE_W32_SYSTEM ret = npth_pselect (0, NULL, NULL, NULL, &timeout, npth_sigev_sigmask()); saved_errno = errno; while (npth_sigev_get_pending(&signo)) handle_signal (signo); #else ret = npth_eselect (0, NULL, NULL, NULL, &timeout, NULL, NULL); saved_errno = errno; #endif if (ret == -1 && saved_errno != EINTR) { log_error (_("npth_pselect failed: %s - waiting 1s\n"), strerror (saved_errno)); npth_sleep (1); continue; } if (ret <= 0) { /* Interrupt or timeout. Will be handled when calculating the next timeout. */ continue; } /* Here one would add processing of file descriptors. */ } log_info (_("%s %s stopped\n"), strusage(11), strusage(13)); return NULL; } /* Start the idle task. */ static void start_idle_task (void) { npth_attr_t tattr; npth_t thread; sigset_t sigs; /* The set of signals we want to catch. */ int err; #ifndef HAVE_W32_SYSTEM /* These signals should always go to the idle task, so they need to be blocked everywhere else. We assume start_idle_task is called from the main thread before any other threads are created. */ sigemptyset (&sigs); sigaddset (&sigs, SIGHUP); sigaddset (&sigs, SIGUSR1); sigaddset (&sigs, SIGUSR2); sigaddset (&sigs, SIGINT); sigaddset (&sigs, SIGTERM); npth_sigmask (SIG_BLOCK, &sigs, NULL); #endif npth_attr_init (&tattr); npth_attr_setdetachstate (&tattr, NPTH_CREATE_JOINABLE); err = npth_create (&thread, &tattr, idle_task, NULL); if (err) { log_fatal ("error starting idle task: %s\n", strerror (err)); return; /*NOTREACHED*/ } npth_setname_np (thread, "idle-task"); idle_task_thread = thread; npth_attr_destroy (&tattr); } /* Wait for the idle task to finish. */ static void join_idle_task (void) { int err; /* FIXME: This assumes that a valid pthread_t is non-null. That is not guaranteed. */ if (idle_task_thread) { err = npth_join (idle_task_thread, NULL); if (err) log_error ("waiting for idle task thread failed: %s\n", strerror (err)); } }