aboutsummaryrefslogtreecommitdiff
path: root/external/unbound/winrc/win_svc.c
diff options
context:
space:
mode:
authorRiccardo Spagni <ric@spagni.net>2014-10-05 23:44:31 +0200
committerRiccardo Spagni <ric@spagni.net>2014-10-05 23:44:31 +0200
commit9ef094b356b4da7542c3cab898dac7e135b76903 (patch)
tree99b5876712b0b1551fc042fe75447b998e4b0fc1 /external/unbound/winrc/win_svc.c
parentsplit mnemonic printout over 3 lines (diff)
downloadmonero-9ef094b356b4da7542c3cab898dac7e135b76903.tar.xz
added unbound to external deps
Diffstat (limited to 'external/unbound/winrc/win_svc.c')
-rw-r--r--external/unbound/winrc/win_svc.c620
1 files changed, 620 insertions, 0 deletions
diff --git a/external/unbound/winrc/win_svc.c b/external/unbound/winrc/win_svc.c
new file mode 100644
index 000000000..57a160d6a
--- /dev/null
+++ b/external/unbound/winrc/win_svc.c
@@ -0,0 +1,620 @@
+/*
+ * winrc/win_svc.c - windows services API implementation for unbound
+ *
+ * Copyright (c) 2009, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * \file
+ *
+ * This file contains functions to integrate with the windows services API.
+ * This means it handles the commandline switches to install and remove
+ * the service (via CreateService and DeleteService), it handles
+ * the ServiceMain() main service entry point when started as a service,
+ * and it handles the Handler[_ex]() to process requests to the service
+ * (such as start and stop and status).
+ */
+#include "config.h"
+#include "winrc/win_svc.h"
+#include "winrc/w_inst.h"
+#include "daemon/daemon.h"
+#include "daemon/worker.h"
+#include "daemon/remote.h"
+#include "util/config_file.h"
+#include "util/netevent.h"
+#include "util/winsock_event.h"
+
+/** global service status */
+static SERVICE_STATUS service_status;
+/** global service status handle */
+static SERVICE_STATUS_HANDLE service_status_handle;
+/** global service stop event */
+static WSAEVENT service_stop_event = NULL;
+/** event struct for stop callbacks */
+static struct event service_stop_ev;
+/** if stop even means shutdown or restart */
+static int service_stop_shutdown = 0;
+/** config file to open. global communication to service_main() */
+static char* service_cfgfile = CONFIGFILE;
+/** commandline verbosity. global communication to service_main() */
+static int service_cmdline_verbose = 0;
+/** the cron callback */
+static struct comm_timer* service_cron = NULL;
+/** the cron thread */
+static ub_thread_t cron_thread = NULL;
+/** if cron has already done its quick check */
+static int cron_was_quick = 0;
+
+/**
+ * Report current service status to service control manager
+ * @param state: current state
+ * @param exitcode: error code (when stopped)
+ * @param wait: pending operation estimated time in milliseconds.
+ */
+static void report_status(DWORD state, DWORD exitcode, DWORD wait)
+{
+ static DWORD checkpoint = 1;
+ service_status.dwCurrentState = state;
+ service_status.dwWin32ExitCode = exitcode;
+ service_status.dwWaitHint = wait;
+ if(state == SERVICE_START_PENDING)
+ service_status.dwControlsAccepted = 0;
+ else service_status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
+ if(state == SERVICE_RUNNING || state == SERVICE_STOPPED)
+ service_status.dwCheckPoint = 0;
+ else service_status.dwCheckPoint = checkpoint++;
+ SetServiceStatus(service_status_handle, &service_status);
+}
+
+/**
+ * Service control handler. Called by serviceControlManager when a control
+ * code is sent to the service (with ControlService).
+ * @param ctrl: control code
+ */
+static void
+hdlr(DWORD ctrl)
+{
+ if(ctrl == SERVICE_CONTROL_STOP) {
+ report_status(SERVICE_STOP_PENDING, NO_ERROR, 0);
+ service_stop_shutdown = 1;
+ /* send signal to stop */
+ if(!WSASetEvent(service_stop_event))
+ log_err("Could not WSASetEvent: %s",
+ wsa_strerror(WSAGetLastError()));
+ return;
+ } else {
+ /* ctrl == SERVICE_CONTROL_INTERROGATE or whatever */
+ /* update status */
+ report_status(service_status.dwCurrentState, NO_ERROR, 0);
+ }
+}
+
+/**
+ * report event to system event log
+ * For use during startup and shutdown.
+ * @param str: the error
+ */
+static void
+reportev(const char* str)
+{
+ char b[256];
+ char e[256];
+ HANDLE* s;
+ LPCTSTR msg = b;
+ /* print quickly to keep GetLastError value */
+ wsvc_err2str(e, sizeof(e), str, GetLastError());
+ snprintf(b, sizeof(b), "%s: %s", SERVICE_NAME, e);
+ s = RegisterEventSource(NULL, SERVICE_NAME);
+ if(!s) return;
+ ReportEvent(s, /* event log */
+ EVENTLOG_ERROR_TYPE, /* event type */
+ 0, /* event category */
+ MSG_GENERIC_ERR, /* event ID (from gen_msg.mc) */
+ NULL, /* user security context */
+ 1, /* numstrings */
+ 0, /* binary size */
+ &msg, /* strings */
+ NULL); /* binary data */
+ DeregisterEventSource(s);
+}
+
+/**
+ * Obtain registry string (if it exists).
+ * @param key: key string
+ * @param name: name of value to fetch.
+ * @return malloced string with the result or NULL if it did not
+ * exist on an error (logged) was encountered.
+ */
+static char*
+lookup_reg_str(const char* key, const char* name)
+{
+ HKEY hk = NULL;
+ DWORD type = 0;
+ BYTE buf[1024];
+ DWORD len = (DWORD)sizeof(buf);
+ LONG ret;
+ char* result = NULL;
+ ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, key, 0, KEY_READ, &hk);
+ if(ret == ERROR_FILE_NOT_FOUND)
+ return NULL; /* key does not exist */
+ else if(ret != ERROR_SUCCESS) {
+ reportev("RegOpenKeyEx failed");
+ return NULL;
+ }
+ ret = RegQueryValueEx(hk, (LPCTSTR)name, 0, &type, buf, &len);
+ if(RegCloseKey(hk))
+ reportev("RegCloseKey");
+ if(ret == ERROR_FILE_NOT_FOUND)
+ return NULL; /* name does not exist */
+ else if(ret != ERROR_SUCCESS) {
+ reportev("RegQueryValueEx failed");
+ return NULL;
+ }
+ if(type == REG_SZ || type == REG_MULTI_SZ || type == REG_EXPAND_SZ) {
+ buf[sizeof(buf)-1] = 0;
+ buf[sizeof(buf)-2] = 0; /* for multi_sz */
+ result = strdup((char*)buf);
+ if(!result) reportev("out of memory");
+ }
+ return result;
+}
+
+/**
+ * Obtain registry integer (if it exists).
+ * @param key: key string
+ * @param name: name of value to fetch.
+ * @return integer value (if it exists), or 0 on error.
+ */
+static int
+lookup_reg_int(const char* key, const char* name)
+{
+ HKEY hk = NULL;
+ DWORD type = 0;
+ BYTE buf[1024];
+ DWORD len = (DWORD)sizeof(buf);
+ LONG ret;
+ int result = 0;
+ ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, key, 0, KEY_READ, &hk);
+ if(ret == ERROR_FILE_NOT_FOUND)
+ return 0; /* key does not exist */
+ else if(ret != ERROR_SUCCESS) {
+ reportev("RegOpenKeyEx failed");
+ return 0;
+ }
+ ret = RegQueryValueEx(hk, (LPCTSTR)name, 0, &type, buf, &len);
+ if(RegCloseKey(hk))
+ reportev("RegCloseKey");
+ if(ret == ERROR_FILE_NOT_FOUND)
+ return 0; /* name does not exist */
+ else if(ret != ERROR_SUCCESS) {
+ reportev("RegQueryValueEx failed");
+ return 0;
+ }
+ if(type == REG_SZ || type == REG_MULTI_SZ || type == REG_EXPAND_SZ) {
+ buf[sizeof(buf)-1] = 0;
+ buf[sizeof(buf)-2] = 0; /* for multi_sz */
+ result = atoi((char*)buf);
+ } else if(type == REG_DWORD) {
+ DWORD r;
+ memmove(&r, buf, sizeof(r));
+ result = r;
+ }
+ return result;
+}
+
+/** wait for unbound-anchor process to finish */
+static void
+waitforubanchor(PROCESS_INFORMATION* pinfo)
+{
+ /* we have 5 seconds scheduled for it, usually it will be very fast,
+ * with only a UDP message or two (100 msec or so), but the https
+ * connections could take some time */
+ DWORD count = 7900;
+ DWORD ret = WAIT_TIMEOUT;
+ /* decrease timer every 1/10 second, we are still starting up */
+ while(ret == WAIT_TIMEOUT) {
+ ret = WaitForSingleObject(pinfo->hProcess, 100);
+ if(count > 4000) count -= 100;
+ else count--; /* go slow, it is taking long */
+ if(count > 3000)
+ report_status(SERVICE_START_PENDING, NO_ERROR, count);
+ }
+ verbose(VERB_ALGO, "unbound-anchor done");
+ if(ret != WAIT_OBJECT_0) {
+ return; /* did not end successfully */
+ }
+ if(!GetExitCodeProcess(pinfo->hProcess, &ret)) {
+ log_err("GetExitCodeProcess failed");
+ return;
+ }
+ verbose(VERB_ALGO, "unbound-anchor exit code is %d", (int)ret);
+ if(ret != 0) {
+ log_info("The root trust anchor has been updated.");
+ }
+}
+
+
+/**
+ * Perform root anchor update if so configured, by calling that process
+ */
+static void
+call_root_update(void)
+{
+ char* rootanchor;
+ rootanchor = lookup_reg_str("Software\\Unbound", "RootAnchor");
+ if(rootanchor && strlen(rootanchor)>0) {
+ STARTUPINFO sinfo;
+ PROCESS_INFORMATION pinfo;
+ memset(&pinfo, 0, sizeof(pinfo));
+ memset(&sinfo, 0, sizeof(sinfo));
+ sinfo.cb = sizeof(sinfo);
+ verbose(VERB_ALGO, "rootanchor: %s", rootanchor);
+ report_status(SERVICE_START_PENDING, NO_ERROR, 8000);
+ if(!CreateProcess(NULL, rootanchor, NULL, NULL, 0,
+ CREATE_NO_WINDOW, NULL, NULL, &sinfo, &pinfo))
+ log_err("CreateProcess error for unbound-anchor.exe");
+ else {
+ waitforubanchor(&pinfo);
+ CloseHandle(pinfo.hProcess);
+ CloseHandle(pinfo.hThread);
+ }
+ }
+ free(rootanchor);
+}
+
+/**
+ * Init service. Keeps calling status pending to tell service control
+ * manager that this process is not hanging.
+ * @param r: restart, true on restart
+ * @param d: daemon returned here.
+ * @param c: config file returned here.
+ * @return false if failed.
+ */
+static int
+service_init(int r, struct daemon** d, struct config_file** c)
+{
+ struct config_file* cfg = NULL;
+ struct daemon* daemon = NULL;
+
+ if(!service_cfgfile) {
+ char* newf = lookup_reg_str("Software\\Unbound", "ConfigFile");
+ if(newf) service_cfgfile = newf;
+ else service_cfgfile = strdup(CONFIGFILE);
+ if(!service_cfgfile) fatal_exit("out of memory");
+ }
+
+ /* create daemon */
+ if(r) daemon = *d;
+ else daemon = daemon_init();
+ if(!daemon) return 0;
+ if(!r) report_status(SERVICE_START_PENDING, NO_ERROR, 2800);
+
+ /* read config */
+ cfg = config_create();
+ if(!cfg) return 0;
+ if(!config_read(cfg, service_cfgfile, daemon->chroot)) {
+ if(errno != ENOENT) {
+ log_err("error in config file");
+ return 0;
+ }
+ log_warn("could not open config file, using defaults");
+ }
+ if(!r) report_status(SERVICE_START_PENDING, NO_ERROR, 2600);
+
+ verbose(VERB_QUERY, "winservice - apply settings");
+ /* apply settings and init */
+ verbosity = cfg->verbosity + service_cmdline_verbose;
+ if(cfg->directory && cfg->directory[0]) {
+ if(chdir(cfg->directory)) {
+ log_err("could not chdir to %s: %s",
+ cfg->directory, strerror(errno));
+ if(errno != ENOENT)
+ return 0;
+ log_warn("could not change directory - continuing");
+ } else
+ verbose(VERB_QUERY, "chdir to %s", cfg->directory);
+ }
+ log_init(cfg->logfile, cfg->use_syslog, cfg->chrootdir);
+ if(!r) report_status(SERVICE_START_PENDING, NO_ERROR, 2400);
+ verbose(VERB_QUERY, "winservice - apply cfg");
+ daemon_apply_cfg(daemon, cfg);
+
+ if(!r) report_status(SERVICE_START_PENDING, NO_ERROR, 2300);
+ if(!(daemon->rc = daemon_remote_create(cfg))) {
+ log_err("could not set up remote-control");
+ daemon_delete(daemon);
+ config_delete(cfg);
+ return 0;
+ }
+
+ /* open ports */
+ /* keep reporting that we are busy starting */
+ if(!r) report_status(SERVICE_START_PENDING, NO_ERROR, 2200);
+ verbose(VERB_QUERY, "winservice - open ports");
+ if(!daemon_open_shared_ports(daemon)) return 0;
+ verbose(VERB_QUERY, "winservice - ports opened");
+ if(!r) report_status(SERVICE_START_PENDING, NO_ERROR, 2000);
+
+ *d = daemon;
+ *c = cfg;
+ return 1;
+}
+
+/**
+ * Deinit the service
+ */
+static void
+service_deinit(struct daemon* daemon, struct config_file* cfg)
+{
+ daemon_cleanup(daemon);
+ config_delete(cfg);
+ daemon_delete(daemon);
+}
+
+#ifdef DOXYGEN
+#define ATTR_UNUSED(x) x
+#endif
+/**
+ * The main function for the service.
+ * Called by the services API when starting unbound on windows in background.
+ * Arguments could have been present in the string 'path'.
+ * @param argc: nr args
+ * @param argv: arg text.
+ */
+static void
+service_main(DWORD ATTR_UNUSED(argc), LPTSTR* ATTR_UNUSED(argv))
+{
+ struct config_file* cfg = NULL;
+ struct daemon* daemon = NULL;
+
+ service_status_handle = RegisterServiceCtrlHandler(SERVICE_NAME,
+ (LPHANDLER_FUNCTION)hdlr);
+ if(!service_status_handle) {
+ reportev("Could not RegisterServiceCtrlHandler");
+ return;
+ }
+
+ service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
+ service_status.dwServiceSpecificExitCode = 0;
+
+ /* see if we have root anchor update enabled */
+ call_root_update();
+
+ /* we are now starting up */
+ report_status(SERVICE_START_PENDING, NO_ERROR, 3000);
+ if(!service_init(0, &daemon, &cfg)) {
+ reportev("Could not service_init");
+ report_status(SERVICE_STOPPED, NO_ERROR, 0);
+ return;
+ }
+
+ /* event that gets signalled when we want to quit; it
+ * should get registered in the worker-0 waiting loop. */
+ service_stop_event = WSACreateEvent();
+ if(service_stop_event == WSA_INVALID_EVENT) {
+ log_err("WSACreateEvent: %s", wsa_strerror(WSAGetLastError()));
+ reportev("Could not WSACreateEvent");
+ report_status(SERVICE_STOPPED, NO_ERROR, 0);
+ return;
+ }
+ if(!WSAResetEvent(service_stop_event)) {
+ log_err("WSAResetEvent: %s", wsa_strerror(WSAGetLastError()));
+ }
+
+ /* SetServiceStatus SERVICE_RUNNING;*/
+ report_status(SERVICE_RUNNING, NO_ERROR, 0);
+ verbose(VERB_QUERY, "winservice - init complete");
+
+ /* daemon performs work */
+ while(!service_stop_shutdown) {
+ daemon_fork(daemon);
+ if(!service_stop_shutdown) {
+ daemon_cleanup(daemon);
+ config_delete(cfg); cfg=NULL;
+ if(!service_init(1, &daemon, &cfg)) {
+ reportev("Could not service_init");
+ report_status(SERVICE_STOPPED, NO_ERROR, 0);
+ return;
+ }
+ }
+ }
+
+ /* exit */
+ verbose(VERB_ALGO, "winservice - cleanup.");
+ report_status(SERVICE_STOP_PENDING, NO_ERROR, 0);
+ service_deinit(daemon, cfg);
+ free(service_cfgfile);
+ if(service_stop_event) (void)WSACloseEvent(service_stop_event);
+ verbose(VERB_QUERY, "winservice - full stop");
+ report_status(SERVICE_STOPPED, NO_ERROR, 0);
+}
+
+/** start the service */
+static void
+service_start(const char* cfgfile, int v, int c)
+{
+ SERVICE_TABLE_ENTRY myservices[2] = {
+ {SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION)service_main},
+ {NULL, NULL} };
+ verbosity=v;
+ if(verbosity >= VERB_QUERY) {
+ /* log to file about start sequence */
+ fclose(fopen("C:\\unbound.log", "w"));
+ log_init("C:\\unbound.log", 0, 0);
+ verbose(VERB_QUERY, "open logfile");
+ } else log_init(0, 1, 0); /* otherwise, use Application log */
+ if(c) {
+ service_cfgfile = strdup(cfgfile);
+ if(!service_cfgfile) fatal_exit("out of memory");
+ } else service_cfgfile = NULL;
+ service_cmdline_verbose = v;
+ /* this call returns when service has stopped. */
+ if(!StartServiceCtrlDispatcher(myservices)) {
+ reportev("Could not StartServiceCtrlDispatcher");
+ }
+}
+
+void
+wsvc_command_option(const char* wopt, const char* cfgfile, int v, int c)
+{
+ if(strcmp(wopt, "install") == 0)
+ wsvc_install(stdout, NULL);
+ else if(strcmp(wopt, "remove") == 0)
+ wsvc_remove(stdout);
+ else if(strcmp(wopt, "service") == 0)
+ service_start(cfgfile, v, c);
+ else if(strcmp(wopt, "start") == 0)
+ wsvc_rc_start(stdout);
+ else if(strcmp(wopt, "stop") == 0)
+ wsvc_rc_stop(stdout);
+ else fatal_exit("unknown option: %s", wopt);
+ exit(0);
+}
+
+void
+worker_win_stop_cb(int ATTR_UNUSED(fd), short ATTR_UNUSED(ev), void* arg)
+{
+ struct worker* worker = (struct worker*)arg;
+ verbose(VERB_QUERY, "caught stop signal (wsaevent)");
+ worker->need_to_exit = 1;
+ comm_base_exit(worker->base);
+}
+
+/** wait for cron process to finish */
+static void
+waitforit(PROCESS_INFORMATION* pinfo)
+{
+ DWORD ret = WaitForSingleObject(pinfo->hProcess, INFINITE);
+ verbose(VERB_ALGO, "cronaction done");
+ if(ret != WAIT_OBJECT_0) {
+ return; /* did not end successfully */
+ }
+ if(!GetExitCodeProcess(pinfo->hProcess, &ret)) {
+ log_err("GetExitCodeProcess failed");
+ return;
+ }
+ verbose(VERB_ALGO, "exit code is %d", (int)ret);
+ if(ret != 1) {
+ if(!WSASetEvent(service_stop_event))
+ log_err("Could not WSASetEvent: %s",
+ wsa_strerror(WSAGetLastError()));
+ }
+}
+
+/** Do the cron action and wait for result exit value */
+static void*
+win_do_cron(void* ATTR_UNUSED(arg))
+{
+ int mynum=65;
+ char* cronaction;
+ log_thread_set(&mynum);
+ cronaction = lookup_reg_str("Software\\Unbound", "CronAction");
+ if(cronaction && strlen(cronaction)>0) {
+ STARTUPINFO sinfo;
+ PROCESS_INFORMATION pinfo;
+ memset(&pinfo, 0, sizeof(pinfo));
+ memset(&sinfo, 0, sizeof(sinfo));
+ sinfo.cb = sizeof(sinfo);
+ verbose(VERB_ALGO, "cronaction: %s", cronaction);
+ if(!CreateProcess(NULL, cronaction, NULL, NULL, 0,
+ CREATE_NO_WINDOW, NULL, NULL, &sinfo, &pinfo))
+ log_err("CreateProcess error");
+ else {
+ waitforit(&pinfo);
+ CloseHandle(pinfo.hProcess);
+ CloseHandle(pinfo.hThread);
+ }
+ }
+ free(cronaction);
+ /* stop self */
+ CloseHandle(cron_thread);
+ cron_thread = NULL;
+ return NULL;
+}
+
+/** Set the timer for cron for the next wake up */
+static void
+set_cron_timer()
+{
+ struct timeval tv;
+ int crontime;
+ if(cron_was_quick == 0) {
+ cron_was_quick = 1;
+ crontime = 3600; /* first update some time after boot */
+ } else {
+ crontime = lookup_reg_int("Software\\Unbound", "CronTime");
+ if(crontime == 0) crontime = 60*60*24; /* 24 hours */
+ }
+ memset(&tv, 0, sizeof(tv));
+ tv.tv_sec = (time_t)crontime;
+ comm_timer_set(service_cron, &tv);
+}
+
+void
+wsvc_cron_cb(void* arg)
+{
+ struct worker* worker = (struct worker*)arg;
+ /* perform cronned operation */
+ verbose(VERB_ALGO, "cron timer callback");
+ if(cron_thread == NULL) {
+ /* create new thread to do it */
+ ub_thread_create(&cron_thread, win_do_cron, worker);
+ }
+ /* reschedule */
+ set_cron_timer();
+}
+
+void wsvc_setup_worker(struct worker* worker)
+{
+ /* if not started with -w service, do nothing */
+ if(!service_stop_event)
+ return;
+ if(!winsock_register_wsaevent(comm_base_internal(worker->base),
+ &service_stop_ev, service_stop_event,
+ &worker_win_stop_cb, worker)) {
+ fatal_exit("could not register wsaevent");
+ return;
+ }
+ if(!service_cron) {
+ service_cron = comm_timer_create(worker->base,
+ wsvc_cron_cb, worker);
+ if(!service_cron)
+ fatal_exit("could not create cron timer");
+ set_cron_timer();
+ }
+}
+
+void wsvc_desetup_worker(struct worker* ATTR_UNUSED(worker))
+{
+ comm_timer_delete(service_cron);
+ service_cron = NULL;
+}