/* * ***** BEGIN LICENSE BLOCK ***** Copyright (C) 2009-2017 Olof Hagsand and Benny Holmgren This file is part of CLIXON. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Alternatively, the contents of this file may be used under the terms of the GNU General Public License Version 3 or later (the "GPL"), in which case the provisions of the GPL are applicable instead of those above. If you wish to allow use of your version of this file only under the terms of the GPL, and not to allow others to use your version of this file under the terms of Apache License version 2, indicate your decision by deleting the provisions above and replace them with the notice and other provisions required by the GPL. If you do not delete the provisions above, a recipient may use your version of this file under the terms of any one of the Apache License version 2 or the GPL. ***** END LICENSE BLOCK ***** */ #ifdef HAVE_CONFIG_H #include "clixon_config.h" /* generated by config & autoconf */ #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* cligen */ #include /* clicon */ #include #include "clixon_backend_handle.h" #include "backend_socket.h" #include "backend_client.h" #include "backend_commit.h" #include "backend_plugin.h" #include "backend_handle.h" /* Command line options to be passed to getopt(3) */ #ifdef BACKEND_STARTUP_COMPAT #define BACKEND_OPTS "hD:f:d:b:Fzu:P:1s:c:IRCrg:y:x:" /* substitute s: for IRCc:r */ #else #define BACKEND_OPTS "hD:f:d:b:Fzu:P:1s:c:g:y:x:" /* substitute s: for IRCc:r */ #endif /*! Terminate. Cannot use h after this */ static int backend_terminate(clicon_handle h) { yang_spec *yspec; char *pidfile = clicon_backend_pidfile(h); char *sockpath = clicon_sock(h); clicon_debug(1, "%s", __FUNCTION__); if ((yspec = clicon_dbspec_yang(h)) != NULL) yspec_free(yspec); plugin_finish(h); /* Delete all backend plugin RPC callbacks */ backend_rpc_cb_delete_all(); if (pidfile) unlink(pidfile); if (sockpath) unlink(sockpath); xmldb_plugin_unload(h); /* unload storage plugin */ backend_handle_exit(h); /* Cannot use h after this */ event_exit(); clicon_log_register_callback(NULL, NULL); clicon_debug(1, "%s done", __FUNCTION__); return 0; } /*! Unlink pidfile and quit */ static void backend_sig_term(int arg) { static int i=0; if (i++ == 0) clicon_log(LOG_NOTICE, "%s: %s: pid: %u Signal %d", __PROGRAM__, __FUNCTION__, getpid(), arg); clicon_exit_set(); /* checked in event_loop() */ } /*! usage */ static void usage(char *argv0, clicon_handle h) { char *plgdir = clicon_backend_dir(h); char *confsock = clicon_sock(h); char *confpid = clicon_backend_pidfile(h); char *group = clicon_sock_group(h); fprintf(stderr, "usage:%s\n" "where options are\n" " -h\t\tHelp\n" " -D \tDebug level\n" " -f \tCLICON config file (mandatory)\n" " -d \tSpecify backend plugin directory (default: %s)\n" " -b \tSpecify XMLDB database directory\n" " -z\t\tKill other config daemon and exit\n" " -F\t\tRun in foreground, do not run as daemon\n" " -1\t\tRun once and then quit (dont wait for events)\n" " -u \tConfig UNIX domain path / ip address (default: %s)\n" " -P \tPid filename (default: %s)\n" " -s \tSpecify backend startup mode: none|startup|running|init (replaces -IRCr\n" " -c \tLoad extra xml configuration, but don't commit.\n" #ifdef BACKEND_STARTUP_COMPAT " -I\t\tInitialize running state database\n" " -R\t\tCall plugin_reset() in plugins to reset system state in running db (use with -I)\n" " -C\t\tCall plugin_reset() in plugins to reset system state in candidate db (use with -I)\n" " -r\t\tReload running database\n" #endif /* BACKEND_STARTUP_COMPAT */ " -g \tClient membership required to this group (default: %s)\n" " -y \tOverride yang spec file (dont include .yang suffix)\n" " -x \tXMLDB plugin\n", argv0, plgdir ? plgdir : "none", confsock ? confsock : "none", confpid ? confpid : "none", group ? group : "none" ); exit(-1); } static int db_reset(clicon_handle h, char *db) { if (xmldb_exists(h, db) == 1 && xmldb_delete(h, db) != 0 && errno != ENOENT) return -1; if (xmldb_create(h, db) < 0) return -1; return 0; } /*! Merge db1 into db2 without commit */ static int db_merge(clicon_handle h, const char *db1, const char *db2) { int retval = -1; cxobj *xt = NULL; /* Get data as xml from db1 */ if (xmldb_get(h, (char*)db1, NULL, 1, &xt) < 0) goto done; /* Merge xml into db2. WIthout commit */ if (xmldb_put(h, (char*)db2, OP_MERGE, xt) < 0) goto done; retval = 0; done: if (xt) xml_free(xt); return retval; } /*! Create backend server socket and register callback */ static int server_socket(clicon_handle h) { int ss; /* Open control socket */ if ((ss = backend_socket_init(h)) < 0) return -1; /* ss is a server socket that the clients connect to. The callback therefore accepts clients on ss */ if (event_reg_fd(ss, backend_accept_client, h, "server socket") < 0) { close(ss); return -1; } return ss; } /*! Callback for CLICON log events * If you make a subscription to CLICON stream, this function is called for every * log event. */ static int backend_log_cb(int level, char *msg, void *arg) { int retval = -1; size_t n; char *ptr; char *nptr; char *newmsg = NULL; /* backend_notify() will go through all clients and see if any has registered "CLICON", and if so make a clicon_proto notify message to those clients. Sanitize '%' into "%%" to prevent segvfaults in vsnprintf later. At this stage all formatting is already done */ n = 0; for(ptr=msg; *ptr; ptr++) if (*ptr == '%') n++; if ((newmsg = malloc(strlen(msg) + n + 1)) == NULL) { clicon_err(OE_UNIX, errno, "malloc"); return -1; } for(ptr=msg, nptr=newmsg; *ptr; ptr++) { *nptr++ = *ptr; if (*ptr == '%') *nptr++ = '%'; } retval = backend_notify(arg, "CLICON", level, newmsg); free(newmsg); return retval; } /*! Call plugin_start with -- user options */ static int plugin_start_useroptions(clicon_handle h, char *argv0, int argc, char **argv) { char *tmp; tmp = *(argv-1); *(argv-1) = argv0; if (plugin_start_argv(h, argc+1, argv-1) < 0) return -1; *(argv-1) = tmp; return 0; } #ifdef BACKEND_STARTUP_COMPAT /*! Initialize running-config from file application configuration * * @param[in] h clicon handle * @param[in] extraxml_file clicon application configuration file * @param[in] running_db Name of running db * @retval 0 OK * @retval -1 Error. clicon_err set */ static int rundb_main(clicon_handle h, char *extraxml_file) { int retval = -1; int fd = -1; cxobj *xt = NULL; cxobj *xn; if (xmldb_create(h, "tmp") < 0) goto done; if (xmldb_copy(h, "running", "tmp") < 0) goto done; if ((fd = open(extraxml_file, O_RDONLY)) < 0){ clicon_err(OE_UNIX, errno, "open(%s)", extraxml_file); goto done; } if (xml_parse_file(fd, &xt, "") < 0) goto done; if ((xn = xml_child_i(xt, 0)) != NULL) if (xmldb_put(h, "tmp", OP_MERGE, xn) < 0) goto done; if (candidate_commit(h, "tmp") < 0) goto done; if (xmldb_delete(h, "tmp") < 0) goto done; retval = 0; done: if (xt) xml_free(xt); if (fd != -1) close(fd); return retval; } static int candb_reset(clicon_handle h) { int retval = -1; if (xmldb_copy(h, "running", "tmp") < 0) goto done; /* Request plugins to reset system state, eg initiate running from system * -R */ if (plugin_reset_state(h, "tmp") < 0) goto done; if (candidate_commit(h, "tmp") < 0) goto done; retval = 0; done: return retval; } /*! Legacy (old-style) startup mode where flags -IRCcr was used */ static int fragmented_startup_mode(clicon_handle h, char *argv0, int argc, char *argv[], int reload_running, int init_rundb, int reset_state_candidate, int reset_state_running, char *extraxml_file) { int retval = -1; /* First check for startup config */ if (clicon_option_int(h, "CLICON_USE_STARTUP_CONFIG") > 0){ if (xmldb_exists(h, "startup") == 1){ /* copy startup config -> running */ if (xmldb_copy(h, "startup", "running") < 0) goto done; } else if (db_reset(h, "running") < 0) goto done; if (xmldb_create(h, "candidate") < 0) goto done; if (xmldb_copy(h, "running", "candidate") < 0) goto done; } /* If running exists and reload_running set, make a copy to candidate */ if (reload_running){ if (xmldb_exists(h, "running") != 1){ clicon_log(LOG_NOTICE, "%s: -r (reload running) option given but no running_db found, proceeding without", __PROGRAM__); reload_running = 0; /* void it, so we dont commit candidate below */ } else if (xmldb_copy(h, "running", "candidate") < 0) goto done; } /* Init running db * -I or if it isnt there */ if (init_rundb || xmldb_exists(h, "running") != 1){ if (db_reset(h, "running") < 0) goto done; } /* If candidate does not exist, create it from running */ if (xmldb_exists(h, "candidate") != 1){ if (xmldb_create(h, "candidate") < 0) goto done; if (xmldb_copy(h, "running", "candidate") < 0) goto done; } /* Load plugins and call plugin_init() */ if (plugin_initiate(h) != 0) goto done; if (reset_state_candidate){ if (candb_reset(h) < 0) goto done; } else if (reset_state_running){ if (plugin_reset_state(h, "running") < 0) goto done; } if (plugin_start_useroptions(h, argv0, argc, argv) <0) goto done; if (reload_running){ /* This could be a failed validation, and we should not fail for that */ (void)candidate_commit(h, "candidate"); } /* Have we specified a config file to load? eg * -c [] */ if (extraxml_file) if (rundb_main(h, extraxml_file) < 0) goto done; /* Initiate the shared candidate. */ if (xmldb_copy(h, "running", "candidate") < 0) goto done; retval = 0; done: return retval; } #endif /* BACKEND_STARTUP_COMPAT */ /*! Merge xml in filename into database */ static int load_extraxml(clicon_handle h, char *filename, const char *db) { int retval = -1; cxobj *xt = NULL; int fd = -1; if (filename == NULL) return 0; if ((fd = open(filename, O_RDONLY)) < 0){ clicon_err(OE_UNIX, errno, "open(%s)", filename); goto done; } if (xml_parse_file(fd, "", NULL, &xt) < 0) goto done; /* Replace parent w first child */ if (xml_rootchild(xt, 0, &xt) < 0) goto done; /* Merge user reset state */ if (xmldb_put(h, (char*)db, OP_MERGE, xt) < 0) goto done; retval = 0; done: if (fd != -1) close(fd); if (xt) xml_free(xt); return retval; } /*! Clixon none startup modes Do not touch running state */ static int startup_mode_none(clicon_handle h) { int retval = -1; /* If it is not there, create candidate from running */ if (xmldb_exists(h, "candidate") != 1) if (xmldb_copy(h, "running", "candidate") < 0) goto done; /* Load plugins and call plugin_init() */ if (plugin_initiate(h) != 0) goto done; retval = 0; done: return retval; } /*! Clixon init startup modes Start with a completely clean running state */ static int startup_mode_init(clicon_handle h) { int retval = -1; /* Reset running, regardless */ if (db_reset(h, "running") < 0) goto done; /* If it is not there, create candidate from running */ if (xmldb_exists(h, "candidate") != 1) if (xmldb_copy(h, "running", "candidate") < 0) goto done; /* Load plugins and call plugin_init() */ if (plugin_initiate(h) != 0) goto done; retval = 0; done: return retval; } /*! Clixon running startup mode: Commit running db configuration into running * OK: copy reset commit merge running----+ |--------------------+--------+------> \ / / candidate +--------------------+ / / tmp |-------+-----+------------+---| reset extra file COMMIT ERROR: copy reset copy running----+ |--------------------+------> EXIT \ / candidate +--------------------+ * @note: if commit fails, copy candidate to running and exit */ static int startup_mode_running(clicon_handle h, char *extraxml_file) { int retval = -1; /* Stash original running to candidate for later commit */ if (xmldb_copy(h, "running", "candidate") < 0) goto done; /* Load plugins and call plugin_init() */ if (plugin_initiate(h) != 0) goto done; /* Clear tmp db */ if (db_reset(h, "tmp") < 0) goto done; /* Application may define extra xml in its reset function*/ if (plugin_reset_state(h, "tmp") < 0) goto done; /* Get application extra xml from file */ if (load_extraxml(h, extraxml_file, "tmp") < 0) goto done; /* Clear running db */ if (db_reset(h, "running") < 0) goto done; /* Commit original running. Assume -1 is validate fail */ if (candidate_commit(h, "candidate") < 0){ /* (1) We cannot differentiate between fatal errors and validation * failures * (2) If fatal error, we should exit * (3) If validation fails we cannot continue. How could we? * (4) Need to restore the running db since we destroyed it above */ clicon_log(LOG_NOTICE, "%s: Commit of saved running failed, exiting.", __FUNCTION__); /* Reinstate original */ if (xmldb_copy(h, "candidate", "running") < 0) goto done; goto done; } /* Merge user reset state and extra xml file (no commit) */ if (db_merge(h, "tmp", "running") < 0) goto done; retval = 0; done: if (xmldb_delete(h, "tmp") < 0) goto done; return retval; } /*! Clixon startup startup mode: Commit startup configuration into running state backup +--------------------| copy / reset commit merge running |-+----|--------------------+-----+------> / / startup -------------------------+--> / / tmp -----|-------+-----+---------+--| reset extra file COMMIT ERROR: backup +------------------------+--| copy / reset copy \ running |-+----|--------------------+---+------->EXIT error / startup -------------------------+--| * @note: if commit fails, copy backup to commit and exit */ static int startup_mode_startup(clicon_handle h, char *extraxml_file) { int retval = -1; /* Stash original running to backup */ if (xmldb_copy(h, "running", "backup") < 0) goto done; /* If startup does not exist, clear it */ if (xmldb_exists(h, "startup") != 1) /* diff */ if (xmldb_create(h, "startup") < 0) /* diff */ return -1; /* Load plugins and call plugin_init() */ if (plugin_initiate(h) != 0) goto done; /* Clear tmp db */ if (db_reset(h, "tmp") < 0) goto done; /* Application may define extra xml in its reset function*/ if (plugin_reset_state(h, "tmp") < 0) goto done; /* Get application extra xml from file */ if (load_extraxml(h, extraxml_file, "tmp") < 0) goto done; /* Clear running db */ if (db_reset(h, "running") < 0) goto done; /* Commit startup */ if (candidate_commit(h, "startup") < 0){ /* diff */ /* We cannot differentiate between fatal errors and validation * failures * In both cases we copy back the original running and quit */ clicon_log(LOG_NOTICE, "%s: Commit of startup failed, exiting.", __FUNCTION__); if (xmldb_copy(h, "backup", "running") < 0) goto done; goto done; } /* Merge user reset state and extra xml file (no commit) */ if (db_merge(h, "tmp", "running") < 0) goto done; retval = 0; done: if (xmldb_delete(h, "backup") < 0) goto done; if (xmldb_delete(h, "tmp") < 0) goto done; return retval; } int main(int argc, char **argv) { int retval = -1; char c; int zap; int foreground; int once; enum startup_mode_t startup_mode; #ifdef BACKEND_STARTUP_COMPAT int init_rundb = 0; int reset_state_running = 0; int reset_state_candidate = 0; int reload_running = 0; #endif char *extraxml_file; char *config_group; char *argv0 = argv[0]; struct stat st; clicon_handle h; int help = 0; int pid; char *pidfile; char *sock; int sockfamily; char *xmldb_plugin; int xml_cache; /* In the startup, logs to stderr & syslog and debug flag set later */ clicon_log_init(__PROGRAM__, LOG_INFO, CLICON_LOG_STDERR|CLICON_LOG_SYSLOG); /* Initiate CLICON handle */ if ((h = backend_handle_init()) == NULL) return -1; if (backend_plugin_init(h) != 0) return -1; foreground = 0; once = 0; zap = 0; extraxml_file = NULL; /* * Command-line options for help, debug, and config-file */ opterr = 0; optind = 1; while ((c = getopt(argc, argv, BACKEND_OPTS)) != -1) switch (c) { case '?': case 'h': /* Defer the call to usage() to later. Reason is that for helpful text messages, default dirs, etc, are not set until later. But this measn that we need to check if 'help' is set before exiting, and then call usage() before exit. */ help = 1; break; case 'D' : /* debug */ if (sscanf(optarg, "%d", &debug) != 1) usage(argv[0], h); break; case 'f': /* config file */ if (!strlen(optarg)) usage(argv[0], h); clicon_option_str_set(h, "CLICON_CONFIGFILE", optarg); break; } /* * Here we have the debug flag settings, use that. * Syslogs also to stderr, but later turn stderr off in daemon mode. * error only to syslog. debug to syslog * XXX: if started in a start-daemon script, there will be irritating * double syslogs until fork below. */ clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO, CLICON_LOG_SYSLOG); clicon_debug_init(debug, NULL); /* Find and read configfile */ if (clicon_options_main(h) < 0){ if (help) usage(argv[0], h); return -1; } /* Now run through the operational args */ opterr = 1; optind = 1; while ((c = getopt(argc, argv, BACKEND_OPTS)) != -1) switch (c) { case 'D' : /* debug */ case 'f': /* config file */ break; /* see above */ case 'd': /* Plugin directory */ if (!strlen(optarg)) usage(argv[0], h); clicon_option_str_set(h, "CLICON_BACKEND_DIR", optarg); break; case 'b': /* XMLDB database directory */ if (!strlen(optarg)) usage(argv[0], h); clicon_option_str_set(h, "CLICON_XMLDB_DIR", optarg); break; case 'F' : /* foreground */ foreground = 1; break; case '1' : /* Quit after reading database once - dont wait for events */ once = 1; break; case 'z': /* Zap other process */ zap++; break; case 'u': /* config unix domain path / ip address */ if (!strlen(optarg)) usage(argv[0], h); clicon_option_str_set(h, "CLICON_SOCK", optarg); break; case 'P': /* pidfile */ clicon_option_str_set(h, "CLICON_BACKEND_PIDFILE", optarg); break; case 's' : /* startup mode */ clicon_option_str_set(h, "CLICON_STARTUP_MODE", optarg); if (clicon_startup_mode(h) < 0){ fprintf(stderr, "Invalid startup mode: %s\n", optarg); usage(argv[0], h); } break; case 'c': /* Load application config */ extraxml_file = optarg; break; #ifdef BACKEND_STARTUP_COMPAT case 'I': /* Initiate running db */ init_rundb++; break; case 'R': /* Reset state directly into running */ reset_state_running++; break; case 'C': /* Reset state into candidate and then commit it */ reset_state_candidate++; break; case 'r': /* Reload running */ reload_running++; break; #endif /* BACKEND_STARTUP_COMPAT */ case 'g': /* config socket group */ clicon_option_str_set(h, "CLICON_SOCK_GROUP", optarg); break; case 'y' :{ /* Override yang module or absolute filename */ clicon_option_str_set(h, "CLICON_YANG_MODULE_MAIN", optarg); break; } case 'x' :{ /* xmldb plugin */ clicon_option_str_set(h, "CLICON_XMLDB_PLUGIN", optarg); break; } default: usage(argv[0], h); break; } argc -= optind; argv += optind; /* Defer: Wait to the last minute to print help message */ if (help) usage(argv[0], h); /* Check pid-file, if zap kil the old daemon, else return here */ if ((pidfile = clicon_backend_pidfile(h)) == NULL){ clicon_err(OE_FATAL, 0, "pidfile not set"); goto done; } sockfamily = clicon_sock_family(h); if ((sock = clicon_sock(h)) == NULL){ clicon_err(OE_FATAL, 0, "sock not set"); goto done; } if (pidfile_get(pidfile, &pid) < 0) return -1; if (zap){ if (pid && pidfile_zapold(pid) < 0) return -1; if (lstat(pidfile, &st) == 0) unlink(pidfile); if (sockfamily==AF_UNIX && lstat(sock, &st) == 0) unlink(sock); exit(0); /* OK */ } else if (pid){ clicon_err(OE_DEMON, 0, "Daemon already running with pid %d\n(Try killing it with %s -z)", pid, argv0); return -1; /* goto done deletes pidfile */ } /* After this point we can goto done on error * Here there is either no old process or we have killed it,.. */ if (lstat(pidfile, &st) == 0) unlink(pidfile); if (sockfamily==AF_UNIX && lstat(sock, &st) == 0) unlink(sock); /* Sanity check: config group exists */ if ((config_group = clicon_sock_group(h)) == NULL){ clicon_err(OE_FATAL, 0, "clicon_sock_group option not set"); return -1; } if (group_name2gid(config_group, NULL) < 0){ clicon_log(LOG_ERR, "'%s' does not seem to be a valid user group.\n" "The config demon requires a valid group to create a server UNIX socket\n" "Define a valid CLICON_SOCK_GROUP in %s or via the -g option\n" "or create the group and add the user to it. On linux for example:" " sudo groupadd %s\n" " sudo usermod -a -G %s user\n", config_group, clicon_configfile(h), config_group, config_group); return -1; } if ((xmldb_plugin = clicon_xmldb_plugin(h)) == NULL){ clicon_log(LOG_ERR, "No xmldb plugin given (specify option CLICON_XMLDB_PLUGIN).\n"); goto done; } if (xmldb_plugin_load(h, xmldb_plugin) < 0) goto done; /* Connect to plugin to get a handle */ if (xmldb_connect(h) < 0) goto done; /* Parse db spec file */ if (yang_spec_main(h) == NULL) goto done; /* Set options: database dir and yangspec (could be hidden in connect?)*/ if (xmldb_setopt(h, "dbdir", clicon_xmldb_dir(h)) < 0) goto done; if (xmldb_setopt(h, "yangspec", clicon_dbspec_yang(h)) < 0) goto done; if ((xml_cache = clicon_option_bool(h, "CLICON_XMLDB_CACHE")) >= 0) if (xmldb_setopt(h, "xml_cache", (void*)(intptr_t)xml_cache) < 0) goto done; /* If startup mode is not defined, eg via OPTION or -s, assume old method */ startup_mode = clicon_startup_mode(h); if (startup_mode == -1){ /* Old style, fragmented mode, phase out */ #ifdef BACKEND_STARTUP_COMPAT if (fragmented_startup_mode(h, argv0, argc, argv, reload_running, init_rundb, reset_state_candidate, reset_state_running, extraxml_file ) < 0) goto done; #else clicon_log(LOG_ERR, "Startup mode undefined. Specify option CLICON_STARTUP_MODE or specify -s option to clicon_backend.\n"); goto done; #endif } else { /* Init running db if it is not there */ if (xmldb_exists(h, "running") != 1) if (xmldb_create(h, "running") < 0) return -1; switch (startup_mode){ case SM_NONE: if (startup_mode_none(h) < 0) goto done; break; case SM_INIT: /* -I */ if (startup_mode_init(h) < 0) goto done; break; case SM_RUNNING: /* -CIr */ if (startup_mode_running(h, extraxml_file) < 0) goto done; break; case SM_STARTUP: /* startup configuration */ if (startup_mode_startup(h, extraxml_file) < 0) goto done; break; } /* Initiate the shared candidate. */ if (xmldb_copy(h, "running", "candidate") < 0) goto done; /* Call plugin_start with user -- options */ if (plugin_start_useroptions(h, argv0, argc, argv) <0) goto done; } if (once) goto done; /* Daemonize and initiate logging. Note error is initiated here to make demonized errors OK. Before this stage, errors are logged on stderr also */ if (foreground==0){ clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO, CLICON_LOG_SYSLOG); if (daemon(0, 0) < 0){ fprintf(stderr, "config: daemon"); exit(-1); } } /* Write pid-file */ if ((pid = pidfile_write(pidfile)) < 0) goto done; /* Register log notifications */ if (clicon_log_register_callback(backend_log_cb, h) < 0) goto done; clicon_log(LOG_NOTICE, "%s: %u Started", __PROGRAM__, getpid()); if (set_signal(SIGTERM, backend_sig_term, NULL) < 0){ clicon_err(OE_DEMON, errno, "Setting signal"); goto done; } if (set_signal(SIGINT, backend_sig_term, NULL) < 0){ clicon_err(OE_DEMON, errno, "Setting signal"); goto done; } /* Initialize server socket */ if (server_socket(h) < 0) goto done; if (debug) clicon_option_dump(h, debug); if (event_loop() < 0) goto done; retval = 0; done: clicon_log(LOG_NOTICE, "%s: %u Terminated retval:%d", __PROGRAM__, getpid(), retval); backend_terminate(h); /* Cannot use h after this */ return retval; }