123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867 |
- /* strongSwan IPsec starter
- * Copyright (C) 2001-2002 Mathieu Lafon - Arkoon Network Security
- *
- * This program 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 2 of the License, or (at your
- * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
- *
- * This program 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.
- */
- #define _GNU_SOURCE
- #include <sys/select.h>
- #include <sys/types.h>
- #include <sys/wait.h>
- #include <sys/stat.h>
- #include <stdlib.h>
- #include <stdio.h>
- #include <signal.h>
- #include <syslog.h>
- #include <unistd.h>
- #include <sys/time.h>
- #include <time.h>
- #include <string.h>
- #include <errno.h>
- #include <fcntl.h>
- #include <pwd.h>
- #include <grp.h>
- #include <pthread.h>
- #include <library.h>
- #include <utils/backtrace.h>
- #include <threading/thread.h>
- #include <utils/debug.h>
- #include "confread.h"
- #include "files.h"
- #include "starterstroke.h"
- #include "invokecharon.h"
- #include "cmp.h"
- #ifndef LOG_AUTHPRIV
- #define LOG_AUTHPRIV LOG_AUTH
- #endif
- #define CHARON_RESTART_DELAY 5
- static const char* cmd_default = IPSEC_DIR "/charon";
- static const char* pid_file_default = IPSEC_PIDDIR "/charon.pid";
- static const char* starter_pid_file_default = IPSEC_PIDDIR "/starter.pid";
- char *daemon_name = NULL;
- char *cmd = NULL;
- char *pid_file = NULL;
- char *starter_pid_file = NULL;
- static char *config_file = NULL;
- /* logging */
- static bool log_to_stderr = TRUE;
- static bool log_to_syslog = TRUE;
- static level_t current_loglevel = 1;
- /**
- * logging function for scepclient
- */
- static void starter_dbg(debug_t group, level_t level, char *fmt, ...)
- {
- char buffer[8192];
- char *current = buffer, *next;
- va_list args;
- if (level <= current_loglevel)
- {
- if (log_to_stderr)
- {
- va_start(args, fmt);
- vfprintf(stderr, fmt, args);
- va_end(args);
- fprintf(stderr, "\n");
- }
- if (log_to_syslog)
- {
- /* write in memory buffer first */
- va_start(args, fmt);
- vsnprintf(buffer, sizeof(buffer), fmt, args);
- va_end(args);
- /* do a syslog with every line */
- while (current)
- {
- next = strchr(current, '\n');
- if (next)
- {
- *(next++) = '\0';
- }
- syslog(LOG_INFO, "%s\n", current);
- current = next;
- }
- }
- }
- }
- /**
- * Initialize logging to stderr/syslog
- */
- static void init_log(const char *program)
- {
- dbg = starter_dbg;
- if (log_to_stderr)
- {
- setbuf(stderr, NULL);
- }
- if (log_to_syslog)
- {
- openlog(program, LOG_CONS | LOG_NDELAY | LOG_PID, LOG_AUTHPRIV);
- }
- }
- /**
- * Deinitialize logging to syslog
- */
- static void close_log()
- {
- if (log_to_syslog)
- {
- closelog();
- }
- }
- /**
- * Return codes defined by Linux Standard Base Core Specification 3.1
- * in section 20.2. Init Script Actions
- */
- #define LSB_RC_SUCCESS 0 /* success */
- #define LSB_RC_FAILURE 1 /* generic or unspecified error */
- #define LSB_RC_INVALID_ARGUMENT 2 /* invalid or excess argument(s) */
- #define LSB_RC_NOT_IMPLEMENTED 3 /* unimplemented feature (reload) */
- #define LSB_RC_NOT_ALLOWED 4 /* user had insufficient privilege */
- #define LSB_RC_NOT_INSTALLED 5 /* program is not installed */
- #define LSB_RC_NOT_CONFIGURED 6 /* program is not configured */
- #define LSB_RC_NOT_RUNNING 7 /* program is not running */
- #define FLAG_ACTION_START_PLUTO 0x01
- #define FLAG_ACTION_UPDATE 0x02
- #define FLAG_ACTION_RELOAD 0x04
- #define FLAG_ACTION_QUIT 0x08
- #define FLAG_ACTION_LISTEN 0x10
- #define FLAG_ACTION_START_CHARON 0x20
- static unsigned int _action_ = 0;
- /**
- * Handle signals in the main thread
- */
- static void signal_handler(int signal)
- {
- switch (signal)
- {
- case SIGCHLD:
- {
- int status, exit_status = 0;
- pid_t pid;
- char *name = NULL;
- while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
- {
- if (pid == starter_charon_pid())
- {
- if (asprintf(&name, " (%s)", daemon_name) < 0)
- {
- name = NULL;
- }
- }
- if (WIFSIGNALED(status))
- {
- DBG2(DBG_APP, "child %d%s has been killed by sig %d\n",
- pid, name?name:"", WTERMSIG(status));
- }
- else if (WIFSTOPPED(status))
- {
- DBG2(DBG_APP, "child %d%s has been stopped by sig %d\n",
- pid, name?name:"", WSTOPSIG(status));
- }
- else if (WIFEXITED(status))
- {
- exit_status = WEXITSTATUS(status);
- if (exit_status >= SS_RC_FIRST && exit_status <= SS_RC_LAST)
- {
- _action_ = FLAG_ACTION_QUIT;
- }
- DBG2(DBG_APP, "child %d%s has quit (exit code %d)\n",
- pid, name?name:"", exit_status);
- }
- else
- {
- DBG2(DBG_APP, "child %d%s has quit", pid, name?name:"");
- }
- if (pid == starter_charon_pid())
- {
- starter_charon_sigchild(pid, exit_status);
- }
- }
- if (name)
- {
- free(name);
- }
- }
- break;
- case SIGALRM:
- _action_ |= FLAG_ACTION_START_CHARON;
- break;
- case SIGHUP:
- _action_ |= FLAG_ACTION_UPDATE;
- break;
- case SIGTERM:
- case SIGQUIT:
- case SIGINT:
- _action_ |= FLAG_ACTION_QUIT;
- break;
- case SIGUSR1:
- _action_ |= FLAG_ACTION_RELOAD;
- _action_ |= FLAG_ACTION_UPDATE;
- break;
- default:
- DBG1(DBG_APP, "fsig(): unknown signal %d -- investigate", signal);
- break;
- }
- }
- /**
- * Handle fatal signals raised by threads
- */
- static void fatal_signal_handler(int signal)
- {
- backtrace_t *backtrace;
- DBG1(DBG_APP, "thread %u received %d", thread_current_id(), signal);
- backtrace = backtrace_create(2);
- backtrace->log(backtrace, stderr, TRUE);
- backtrace->destroy(backtrace);
- DBG1(DBG_APP, "killing ourself, received critical signal");
- abort();
- }
- static bool check_pid(char *pid_file)
- {
- struct stat stb;
- FILE *pidfile;
- if (stat(pid_file, &stb) == 0)
- {
- pidfile = fopen(pid_file, "r");
- if (pidfile)
- {
- char buf[64];
- pid_t pid = 0;
- memset(buf, 0, sizeof(buf));
- if (fread(buf, 1, sizeof(buf), pidfile))
- {
- buf[sizeof(buf) - 1] = '\0';
- pid = atoi(buf);
- }
- fclose(pidfile);
- if (pid && pid != getpid() && kill(pid, 0) == 0)
- { /* such a process is running */
- return TRUE;
- }
- }
- DBG1(DBG_APP, "removing pidfile '%s', process not running", pid_file);
- unlink(pid_file);
- }
- return FALSE;
- }
- /* Set daemon name and adjust command and pid filenames accordingly */
- static bool set_daemon_name()
- {
- if (!daemon_name)
- {
- daemon_name = "charon";
- }
- if (asprintf(&cmd, IPSEC_DIR"/%s", daemon_name) < 0)
- {
- cmd = (char*)cmd_default;
- }
- if (asprintf(&pid_file, IPSEC_PIDDIR"/%s.pid", daemon_name) < 0)
- {
- pid_file = (char*)pid_file_default;
- }
- if (asprintf(&starter_pid_file, IPSEC_PIDDIR"/starter.%s.pid",
- daemon_name) < 0)
- {
- starter_pid_file = (char*)starter_pid_file_default;
- }
- return TRUE;
- }
- static void cleanup()
- {
- if (cmd != cmd_default)
- {
- free(cmd);
- }
- if (pid_file != pid_file_default)
- {
- free(pid_file);
- }
- if (starter_pid_file != starter_pid_file_default)
- {
- free(starter_pid_file);
- }
- }
- static void usage(char *name)
- {
- fprintf(stderr, "Usage: starter [--nofork] [--auto-update <sec>]\n"
- " [--debug|--debug-more|--debug-all|--nolog]\n"
- " [--attach-gdb] [--daemon <name>]\n"
- " [--conf <path to ipsec.conf>]\n");
- exit(LSB_RC_INVALID_ARGUMENT);
- }
- int main (int argc, char **argv)
- {
- starter_config_t *cfg = NULL;
- starter_config_t *new_cfg;
- starter_conn_t *conn, *conn2;
- starter_ca_t *ca, *ca2;
- struct sigaction action;
- struct stat stb;
- int i;
- int id = 1;
- struct timespec ts;
- unsigned long auto_update = 0;
- time_t last_reload;
- bool no_fork = FALSE;
- bool attach_gdb = FALSE;
- bool load_warning = FALSE;
- bool conftest = FALSE;
- library_init(NULL, "starter");
- atexit(library_deinit);
- /* parse command line */
- for (i = 1; i < argc; i++)
- {
- if (streq(argv[i], "--debug"))
- {
- current_loglevel = 2;
- }
- else if (streq(argv[i], "--debug-more"))
- {
- current_loglevel = 3;
- }
- else if (streq(argv[i], "--debug-all"))
- {
- current_loglevel = 4;
- }
- else if (streq(argv[i], "--nolog"))
- {
- current_loglevel = 0;
- }
- else if (streq(argv[i], "--nofork"))
- {
- no_fork = TRUE;
- }
- else if (streq(argv[i], "--attach-gdb"))
- {
- no_fork = TRUE;
- attach_gdb = TRUE;
- }
- else if (streq(argv[i], "--auto-update") && i+1 < argc)
- {
- auto_update = atoi(argv[++i]);
- if (!auto_update)
- usage(argv[0]);
- }
- else if (streq(argv[i], "--daemon") && i+1 < argc)
- {
- daemon_name = argv[++i];
- }
- else if (streq(argv[i], "--conf") && i+1 < argc)
- {
- config_file = argv[++i];
- }
- else if (streq(argv[i], "--conftest"))
- {
- conftest = TRUE;
- }
- else
- {
- usage(argv[0]);
- }
- }
- if (!set_daemon_name())
- {
- DBG1(DBG_APP, "unable to set daemon name");
- exit(LSB_RC_FAILURE);
- }
- if (!config_file)
- {
- config_file = lib->settings->get_str(lib->settings,
- "starter.config_file", CONFIG_FILE);
- }
- init_log("ipsec_starter");
- if (conftest)
- {
- int status = LSB_RC_SUCCESS;
- cfg = confread_load(config_file);
- if (cfg == NULL || cfg->err > 0)
- {
- DBG1(DBG_APP, "config invalid!");
- status = LSB_RC_INVALID_ARGUMENT;
- }
- else
- {
- DBG1(DBG_APP, "config OK");
- }
- if (cfg)
- {
- confread_free(cfg);
- }
- cleanup();
- exit(status);
- }
- if (stat(cmd, &stb) != 0)
- {
- DBG1(DBG_APP, "IKE daemon '%s' not found", cmd);
- cleanup();
- exit(LSB_RC_FAILURE);
- }
- DBG1(DBG_APP, "Starting %sSwan "VERSION" IPsec [starter]...",
- lib->settings->get_bool(lib->settings,
- "charon.i_dont_care_about_security_and_use_aggressive_mode_psk",
- FALSE) ? "weak" : "strong");
- #ifdef LOAD_WARNING
- load_warning = TRUE;
- #endif
- if (lib->settings->get_bool(lib->settings, "starter.load_warning", load_warning))
- {
- if (lib->settings->get_str(lib->settings, "charon.load", NULL))
- {
- DBG1(DBG_APP, "!! Your strongswan.conf contains manual plugin load options for charon.");
- DBG1(DBG_APP, "!! This is recommended for experts only, see");
- DBG1(DBG_APP, "!! http://wiki.strongswan.org/projects/strongswan/wiki/PluginLoad");
- }
- }
- #ifndef STARTER_ALLOW_NON_ROOT
- /* verify that we can start */
- if (getuid() != 0)
- {
- DBG1(DBG_APP, "permission denied (must be superuser)");
- cleanup();
- exit(LSB_RC_NOT_ALLOWED);
- }
- #endif
- if (check_pid(pid_file))
- {
- DBG1(DBG_APP, "%s is already running (%s exists) -- skipping daemon start",
- daemon_name, pid_file);
- }
- else
- {
- _action_ |= FLAG_ACTION_START_CHARON;
- }
- if (stat(DEV_RANDOM, &stb) != 0)
- {
- DBG1(DBG_APP, "unable to start strongSwan IPsec -- no %s!", DEV_RANDOM);
- cleanup();
- exit(LSB_RC_FAILURE);
- }
- if (stat(DEV_URANDOM, &stb)!= 0)
- {
- DBG1(DBG_APP, "unable to start strongSwan IPsec -- no %s!", DEV_URANDOM);
- cleanup();
- exit(LSB_RC_FAILURE);
- }
- cfg = confread_load(config_file);
- if (cfg == NULL || cfg->err > 0)
- {
- DBG1(DBG_APP, "unable to start strongSwan -- fatal errors in config");
- if (cfg)
- {
- confread_free(cfg);
- }
- cleanup();
- exit(LSB_RC_INVALID_ARGUMENT);
- }
- last_reload = time_monotonic(NULL);
- if (check_pid(starter_pid_file))
- {
- DBG1(DBG_APP, "starter is already running (%s exists) -- no fork done",
- starter_pid_file);
- confread_free(cfg);
- cleanup();
- exit(LSB_RC_SUCCESS);
- }
- /* fork if we're not debugging stuff */
- if (!no_fork)
- {
- log_to_stderr = FALSE;
- switch (fork())
- {
- case 0:
- {
- int fnull;
- close_log();
- fnull = open("/dev/null", O_RDWR);
- if (fnull >= 0)
- {
- dup2(fnull, STDIN_FILENO);
- dup2(fnull, STDOUT_FILENO);
- dup2(fnull, STDERR_FILENO);
- close(fnull);
- }
- setsid();
- init_log("ipsec_starter");
- }
- break;
- case -1:
- DBG1(DBG_APP, "can't fork: %s", strerror(errno));
- break;
- default:
- confread_free(cfg);
- cleanup();
- exit(LSB_RC_SUCCESS);
- }
- }
- /* save pid file in /var/run/starter[.daemon_name].pid */
- {
- FILE *fd = fopen(starter_pid_file, "w");
- if (fd)
- {
- fprintf(fd, "%u\n", getpid());
- fclose(fd);
- }
- }
- /* we handle these signals only in pselect() */
- memset(&action, 0, sizeof(action));
- sigemptyset(&action.sa_mask);
- sigaddset(&action.sa_mask, SIGHUP);
- sigaddset(&action.sa_mask, SIGINT);
- sigaddset(&action.sa_mask, SIGTERM);
- sigaddset(&action.sa_mask, SIGQUIT);
- sigaddset(&action.sa_mask, SIGALRM);
- sigaddset(&action.sa_mask, SIGUSR1);
- pthread_sigmask(SIG_SETMASK, &action.sa_mask, NULL);
- /* install a handler for fatal signals */
- action.sa_handler = fatal_signal_handler;
- sigaction(SIGSEGV, &action, NULL);
- sigaction(SIGILL, &action, NULL);
- sigaction(SIGBUS, &action, NULL);
- action.sa_handler = SIG_IGN;
- sigaction(SIGPIPE, &action, NULL);
- /* install main signal handler */
- action.sa_handler = signal_handler;
- sigaction(SIGHUP, &action, NULL);
- sigaction(SIGINT, &action, NULL);
- sigaction(SIGTERM, &action, NULL);
- sigaction(SIGQUIT, &action, NULL);
- sigaction(SIGALRM, &action, NULL);
- sigaction(SIGUSR1, &action, NULL);
- /* this is not blocked above as we want to receive it asynchronously */
- sigaction(SIGCHLD, &action, NULL);
- /* empty mask for pselect() call below */
- sigemptyset(&action.sa_mask);
- for (;;)
- {
- /*
- * Stop charon (if started) and exit
- */
- if (_action_ & FLAG_ACTION_QUIT)
- {
- if (starter_charon_pid())
- {
- starter_stop_charon();
- }
- confread_free(cfg);
- unlink(starter_pid_file);
- cleanup();
- DBG1(DBG_APP, "ipsec starter stopped");
- close_log();
- exit(LSB_RC_SUCCESS);
- }
- /*
- * Delete all connections. Will be added below
- */
- if (_action_ & FLAG_ACTION_RELOAD)
- {
- _action_ &= ~FLAG_ACTION_RELOAD;
- if (starter_charon_pid())
- {
- for (conn = cfg->conn_first; conn; conn = conn->next)
- {
- if (conn->state == STATE_ADDED)
- {
- if (starter_charon_pid())
- {
- if (conn->startup == STARTUP_ROUTE)
- {
- starter_stroke_unroute_conn(conn);
- }
- starter_stroke_del_conn(conn);
- }
- conn->state = STATE_TO_ADD;
- }
- }
- for (ca = cfg->ca_first; ca; ca = ca->next)
- {
- if (ca->state == STATE_ADDED)
- {
- if (starter_charon_pid())
- {
- starter_stroke_del_ca(ca);
- }
- ca->state = STATE_TO_ADD;
- }
- }
- }
- }
- /*
- * Update configuration
- */
- if (_action_ & FLAG_ACTION_UPDATE)
- {
- _action_ &= ~FLAG_ACTION_UPDATE;
- DBG2(DBG_APP, "Reloading config...");
- new_cfg = confread_load(config_file);
- if (new_cfg && (new_cfg->err == 0))
- {
- /* Switch to new config. New conn will be loaded below */
- /* Look for new connections that are already loaded */
- for (conn = cfg->conn_first; conn; conn = conn->next)
- {
- if (conn->state == STATE_ADDED)
- {
- for (conn2 = new_cfg->conn_first; conn2; conn2 = conn2->next)
- {
- if (conn2->state == STATE_TO_ADD && starter_cmp_conn(conn, conn2))
- {
- conn->state = STATE_REPLACED;
- conn2->state = STATE_ADDED;
- conn2->id = conn->id;
- break;
- }
- }
- }
- }
- /* Remove conn sections that have become unused */
- for (conn = cfg->conn_first; conn; conn = conn->next)
- {
- if (conn->state == STATE_ADDED)
- {
- if (starter_charon_pid())
- {
- if (conn->startup == STARTUP_ROUTE)
- {
- starter_stroke_unroute_conn(conn);
- }
- starter_stroke_del_conn(conn);
- }
- }
- }
- /* Look for new ca sections that are already loaded */
- for (ca = cfg->ca_first; ca; ca = ca->next)
- {
- if (ca->state == STATE_ADDED)
- {
- for (ca2 = new_cfg->ca_first; ca2; ca2 = ca2->next)
- {
- if (ca2->state == STATE_TO_ADD && starter_cmp_ca(ca, ca2))
- {
- ca->state = STATE_REPLACED;
- ca2->state = STATE_ADDED;
- break;
- }
- }
- }
- }
- /* Remove ca sections that have become unused */
- for (ca = cfg->ca_first; ca; ca = ca->next)
- {
- if (ca->state == STATE_ADDED)
- {
- if (starter_charon_pid())
- {
- starter_stroke_del_ca(ca);
- }
- }
- }
- confread_free(cfg);
- cfg = new_cfg;
- }
- else
- {
- DBG1(DBG_APP, "can't reload config file due to errors -- keeping old one");
- if (new_cfg)
- {
- confread_free(new_cfg);
- }
- }
- last_reload = time_monotonic(NULL);
- }
- /*
- * Start daemon
- */
- if (_action_ & FLAG_ACTION_START_CHARON)
- {
- _action_ &= ~FLAG_ACTION_START_CHARON;
- if (!starter_charon_pid())
- {
- DBG2(DBG_APP, "Attempting to start %s...", daemon_name);
- if (starter_start_charon(cfg, no_fork, attach_gdb))
- {
- /* schedule next try */
- alarm(CHARON_RESTART_DELAY);
- }
- starter_stroke_configure(cfg);
- }
- for (ca = cfg->ca_first; ca; ca = ca->next)
- {
- if (ca->state == STATE_ADDED)
- {
- ca->state = STATE_TO_ADD;
- }
- }
- for (conn = cfg->conn_first; conn; conn = conn->next)
- {
- if (conn->state == STATE_ADDED)
- {
- conn->state = STATE_TO_ADD;
- }
- }
- }
- /*
- * Add stale conn and ca sections
- */
- if (starter_charon_pid())
- {
- for (ca = cfg->ca_first; ca; ca = ca->next)
- {
- if (ca->state == STATE_TO_ADD)
- {
- if (starter_charon_pid())
- {
- starter_stroke_add_ca(ca);
- }
- ca->state = STATE_ADDED;
- }
- }
- for (conn = cfg->conn_first; conn; conn = conn->next)
- {
- if (conn->state == STATE_TO_ADD)
- {
- if (conn->id == 0)
- {
- /* affect new unique id */
- conn->id = id++;
- }
- if (starter_charon_pid())
- {
- starter_stroke_add_conn(cfg, conn);
- }
- conn->state = STATE_ADDED;
- if (conn->startup == STARTUP_START)
- {
- if (starter_charon_pid())
- {
- starter_stroke_initiate_conn(conn);
- }
- }
- else if (conn->startup == STARTUP_ROUTE)
- {
- if (starter_charon_pid())
- {
- starter_stroke_route_conn(conn);
- }
- }
- }
- }
- }
- /*
- * If auto_update activated, when to stop select
- */
- if (auto_update)
- {
- time_t now = time_monotonic(NULL);
- ts.tv_sec = (now < last_reload + auto_update) ?
- (last_reload + auto_update - now) : 0;
- ts.tv_nsec = 0;
- }
- /*
- * Wait for something to happen
- */
- if (!_action_ &&
- pselect(0, NULL, NULL, NULL, auto_update ? &ts : NULL,
- &action.sa_mask) == 0)
- {
- /* timeout -> auto_update */
- _action_ |= FLAG_ACTION_UPDATE;
- }
- }
- exit(LSB_RC_SUCCESS);
- }
|