File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / smartmontools / os_win32 / daemon_win32.cpp
Revision 1.1.1.3 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Mon Oct 14 07:54:04 2013 UTC (10 years, 8 months ago) by misho
Branches: smartmontools, elwix, MAIN
CVS tags: v6_2, HEAD
v 6.2

    1: /*
    2:  * os_win32/daemon_win32.cpp
    3:  *
    4:  * Home page of code is: http://smartmontools.sourceforge.net
    5:  *
    6:  * Copyright (C) 2004-13 Christian Franke <smartmontools-support@lists.sourceforge.net>
    7:  *
    8:  * This program is free software; you can redistribute it and/or modify
    9:  * it under the terms of the GNU General Public License as published by
   10:  * the Free Software Foundation; either version 2, or (at your option)
   11:  * any later version.
   12:  *
   13:  * You should have received a copy of the GNU General Public License
   14:  * (for example COPYING); If not, see <http://www.gnu.org/licenses/>.
   15:  *
   16:  */
   17: 
   18: #define WINVER 0x0600
   19: #define _WIN32_WINNT WINVER
   20: 
   21: #include "daemon_win32.h"
   22: 
   23: const char * daemon_win32_cpp_cvsid = "$Id: daemon_win32.cpp,v 1.1.1.3 2013/10/14 07:54:04 misho Exp $"
   24:   DAEMON_WIN32_H_CVSID;
   25: 
   26: #include <stdio.h>
   27: #include <stdlib.h>
   28: #include <signal.h>
   29: #include <io.h>
   30: 
   31: #define WIN32_LEAN_AND_MEAN
   32: #include <windows.h>
   33: #ifdef _DEBUG
   34: #include <crtdbg.h>
   35: #endif
   36: 
   37: #ifndef SERVICE_CONFIG_DELAYED_AUTO_START_INFO
   38: // Missing in older MinGW headers
   39: #define SERVICE_CONFIG_DELAYED_AUTO_START_INFO 3
   40: #endif
   41: 
   42: 
   43: /////////////////////////////////////////////////////////////////////////////
   44: 
   45: // Prevent spawning of child process if debugging
   46: #ifdef _DEBUG
   47: #define debugging() IsDebuggerPresent()
   48: #else
   49: #define debugging() FALSE
   50: #endif
   51: 
   52: 
   53: #define EVT_NAME_LEN 260
   54: 
   55: // Internal events (must be > SIGUSRn)
   56: #define EVT_RUNNING   100 // Exists when running, signaled on creation
   57: #define EVT_DETACHED  101 // Signaled when child detaches from console
   58: #define EVT_RESTART   102 // Signaled when child should restart
   59: 
   60: static void make_name(char * name, int sig)
   61: {
   62:   int i;
   63:   if (!GetModuleFileNameA(NULL, name, EVT_NAME_LEN-10))
   64:     strcpy(name, "DaemonEvent");
   65:   for (i = 0; name[i]; i++) {
   66:     char c = name[i];
   67:     if (!(   ('0' <= c && c <= '9')
   68:           || ('A' <= c && c <= 'Z')
   69:           || ('a' <= c && c <= 'z')))
   70:         name[i] = '_';
   71:   }
   72:   sprintf(name+strlen(name), "-%d", sig);
   73: }
   74: 
   75: 
   76: static HANDLE create_event(int sig, BOOL initial, BOOL errmsg, BOOL * exists)
   77: {
   78:   char name[EVT_NAME_LEN];
   79:   HANDLE h;
   80:   if (sig >= 0)
   81:     make_name(name, sig);
   82:   else
   83:     name[0] = 0;
   84:   if (exists)
   85:     *exists = FALSE;
   86:   if (!(h = CreateEventA(NULL, FALSE, initial, (name[0] ? name : NULL)))) {
   87:     if (errmsg)
   88:       fprintf(stderr, "CreateEvent(.,\"%s\"): Error=%ld\n", name, GetLastError());
   89:     return 0;
   90:   }
   91: 
   92:   if (GetLastError() == ERROR_ALREADY_EXISTS) {
   93:     if (!exists) {
   94:       if (errmsg)
   95:         fprintf(stderr, "CreateEvent(.,\"%s\"): Exists\n", name);
   96:       CloseHandle(h);
   97:       return 0;
   98:     }
   99:     *exists = TRUE;
  100:   }
  101:   return h;
  102: }
  103: 
  104: 
  105: static HANDLE open_event(int sig)
  106: {
  107:   char name[EVT_NAME_LEN];
  108:   make_name(name, sig);
  109:   return OpenEventA(EVENT_MODIFY_STATE, FALSE, name);
  110: }
  111: 
  112: 
  113: static int event_exists(int sig)
  114: {
  115:   char name[EVT_NAME_LEN];
  116:   HANDLE h;
  117:   make_name(name, sig);
  118:   if (!(h = OpenEventA(EVENT_MODIFY_STATE, FALSE, name)))
  119:     return 0;
  120:   CloseHandle(h);
  121:   return 1;
  122: }
  123: 
  124: 
  125: static int sig_event(int sig)
  126: {
  127:   char name[EVT_NAME_LEN];
  128:   HANDLE h;
  129:   make_name(name, sig);
  130:   if (!(h = OpenEventA(EVENT_MODIFY_STATE, FALSE, name))) {
  131:     make_name(name, EVT_RUNNING);
  132:     if (!(h = OpenEvent(EVENT_MODIFY_STATE, FALSE, name)))
  133:       return -1;
  134:     CloseHandle(h);
  135:     return 0;
  136:   }
  137:   SetEvent(h);
  138:   CloseHandle(h);
  139:   return 1;
  140: }
  141: 
  142: 
  143: static void daemon_help(FILE * f, const char * ident, const char * message)
  144: {
  145:   fprintf(f,
  146:     "%s: %s.\n"
  147:     "Use \"%s status|stop|reload|restart|sigusr1|sigusr2\" to control daemon.\n",
  148:     ident, message, ident);
  149:   fflush(f);
  150: }
  151: 
  152: 
  153: /////////////////////////////////////////////////////////////////////////////
  154: // Parent Process
  155: 
  156: 
  157: static BOOL WINAPI parent_console_handler(DWORD event)
  158: {
  159:   switch (event) {
  160:     case CTRL_C_EVENT:
  161:     case CTRL_BREAK_EVENT:
  162:       return TRUE; // Ignore
  163:   }
  164:   return FALSE; // continue with next handler ...
  165: }
  166: 
  167: 
  168: static int parent_main(HANDLE rev)
  169: {
  170:   HANDLE dev;
  171:   HANDLE ht[2];
  172:   char * cmdline;
  173:   STARTUPINFO si;
  174:   PROCESS_INFORMATION pi;
  175:   DWORD rc, exitcode;
  176: 
  177:   // Ignore ^C, ^BREAK in parent
  178:   SetConsoleCtrlHandler(parent_console_handler, TRUE/*add*/);
  179: 
  180:   // Create event used by child to signal daemon_detach()
  181:   if (!(dev = create_event(EVT_DETACHED, FALSE/*not signaled*/, TRUE, NULL/*must not exist*/))) {
  182:     CloseHandle(rev);
  183:     return 101;
  184:   }
  185: 
  186:   // Restart process with same args
  187:   cmdline = GetCommandLineA();
  188:   memset(&si, 0, sizeof(si)); si.cb = sizeof(si);
  189: 
  190:   if (!CreateProcessA(
  191:     NULL, cmdline,
  192:     NULL, NULL, TRUE/*inherit*/,
  193:     0, NULL, NULL, &si, &pi)) {
  194:     fprintf(stderr, "CreateProcess(.,\"%s\",.) failed, Error=%ld\n", cmdline, GetLastError());
  195:     CloseHandle(rev); CloseHandle(dev);
  196:     return 101;
  197:   }
  198:   CloseHandle(pi.hThread);
  199: 
  200:   // Wait for daemon_detach() or exit()
  201:   ht[0] = dev; ht[1] = pi.hProcess;
  202:   rc = WaitForMultipleObjects(2, ht, FALSE/*or*/, INFINITE);
  203:   if (!(/*WAIT_OBJECT_0(0) <= rc && */ rc < WAIT_OBJECT_0+2)) {
  204:     fprintf(stderr, "WaitForMultipleObjects returns %lX\n", rc);
  205:     TerminateProcess(pi.hProcess, 200);
  206:   }
  207:   CloseHandle(rev); CloseHandle(dev);
  208: 
  209:   // Get exit code
  210:   if (!GetExitCodeProcess(pi.hProcess, &exitcode))
  211:     exitcode = 201;
  212:   else if (exitcode == STILL_ACTIVE) // detach()ed, assume OK
  213:     exitcode = 0;
  214: 
  215:   CloseHandle(pi.hProcess);
  216:   return exitcode;
  217: }
  218: 
  219: 
  220: /////////////////////////////////////////////////////////////////////////////
  221: // Child Process
  222: 
  223: 
  224: static int svc_mode;   // Running as service?
  225: static int svc_paused; // Service paused?
  226: 
  227: static void service_report_status(int state, int waithint);
  228: 
  229: 
  230: // Tables of signal handler and corresponding events
  231: typedef void (*sigfunc_t)(int);
  232: 
  233: #define MAX_SIG_HANDLERS 8
  234: 
  235: static int num_sig_handlers = 0;
  236: static sigfunc_t sig_handlers[MAX_SIG_HANDLERS];
  237: static int sig_numbers[MAX_SIG_HANDLERS];
  238: static HANDLE sig_events[MAX_SIG_HANDLERS];
  239: 
  240: static HANDLE sighup_handle, sigint_handle, sigbreak_handle;
  241: static HANDLE sigterm_handle, sigusr1_handle;
  242: 
  243: static HANDLE running_event;
  244: 
  245: static int reopen_stdin, reopen_stdout, reopen_stderr;
  246: 
  247: 
  248: // Handler for windows console events
  249: 
  250: static BOOL WINAPI child_console_handler(DWORD event)
  251: {
  252:   // Caution: runs in a new thread
  253:   // TODO: Guard with a mutex
  254:   HANDLE h = 0;
  255:   switch (event) {
  256:     case CTRL_C_EVENT: // <CONTROL-C> (SIGINT)
  257:       h = sigint_handle; break;
  258:     case CTRL_BREAK_EVENT: // <CONTROL-Break> (SIGBREAK/SIGQUIT)
  259:     case CTRL_CLOSE_EVENT: // User closed console or abort via task manager
  260:       h = sigbreak_handle; break;
  261:     case CTRL_LOGOFF_EVENT: // Logout/Shutdown (SIGTERM)
  262:     case CTRL_SHUTDOWN_EVENT:
  263:       h = sigterm_handle; break;
  264:   }
  265:   if (!h)
  266:     return FALSE; // continue with next handler
  267:   // Signal event
  268:   if (!SetEvent(h))
  269:     return FALSE;
  270:   return TRUE;
  271: }
  272: 
  273: 
  274: static void child_exit(void)
  275: {
  276:   int i;
  277:   char * cmdline;
  278:   HANDLE rst;
  279:   STARTUPINFO si;
  280:   PROCESS_INFORMATION pi;
  281: 
  282:   for (i = 0; i < num_sig_handlers; i++)
  283:     CloseHandle(sig_events[i]);
  284:   num_sig_handlers = 0;
  285:   CloseHandle(running_event); running_event = 0;
  286: 
  287:   // Restart?
  288:   if (!(rst = open_event(EVT_RESTART)))
  289:     return; // No => normal exit
  290: 
  291:   // Yes => Signal exit and restart process
  292:   Sleep(500);
  293:   SetEvent(rst);
  294:   CloseHandle(rst);
  295:   Sleep(500);
  296: 
  297:   cmdline = GetCommandLineA();
  298:   memset(&si, 0, sizeof(si)); si.cb = sizeof(si);
  299:   si.dwFlags = STARTF_USESHOWWINDOW; si.wShowWindow = SW_HIDE;
  300: 
  301:   if (!CreateProcessA(
  302:     NULL, cmdline,
  303:     NULL, NULL, TRUE/*inherit*/,
  304:     0, NULL, NULL, &si, &pi)) {
  305:     fprintf(stderr, "CreateProcess(.,\"%s\",.) failed, Error=%ld\n", cmdline, GetLastError());
  306:   }
  307:   CloseHandle(pi.hThread); CloseHandle(pi.hProcess);
  308: }
  309: 
  310: static int child_main(HANDLE hev,int (*main_func)(int, char **), int argc, char **argv)
  311: {
  312:   // Keep EVT_RUNNING open until exit
  313:   running_event = hev;
  314: 
  315:   // Install console handler
  316:   SetConsoleCtrlHandler(child_console_handler, TRUE/*add*/);
  317: 
  318:   // Install restart handler
  319:   atexit(child_exit);
  320: 
  321:   // Continue in main_func() to do the real work
  322:   return main_func(argc, argv);
  323: }
  324: 
  325: 
  326: // Simulate signal()
  327: 
  328: sigfunc_t daemon_signal(int sig, sigfunc_t func)
  329: {
  330:   int i;
  331:   HANDLE h;
  332:   if (func == SIG_DFL || func == SIG_IGN)
  333:     return func; // TODO
  334:   for (i = 0; i < num_sig_handlers; i++) {
  335:     if (sig_numbers[i] == sig) {
  336:       sigfunc_t old = sig_handlers[i];
  337:       sig_handlers[i] = func;
  338:       return old;
  339:     }
  340:   }
  341:   if (num_sig_handlers >= MAX_SIG_HANDLERS)
  342:     return SIG_ERR;
  343:   if (!(h = create_event((!svc_mode ? sig : -1), FALSE, TRUE, NULL)))
  344:     return SIG_ERR;
  345:   sig_events[num_sig_handlers]   = h;
  346:   sig_numbers[num_sig_handlers]  = sig;
  347:   sig_handlers[num_sig_handlers] = func;
  348:   switch (sig) {
  349:     case SIGHUP:   sighup_handle   = h; break;
  350:     case SIGINT:   sigint_handle   = h; break;
  351:     case SIGTERM:  sigterm_handle  = h; break;
  352:     case SIGBREAK: sigbreak_handle = h; break;
  353:     case SIGUSR1:  sigusr1_handle  = h; break;
  354:   }
  355:   num_sig_handlers++;
  356:   return SIG_DFL;
  357: }
  358: 
  359: 
  360: // strsignal()
  361: 
  362: const char * daemon_strsignal(int sig)
  363: {
  364:   switch (sig) {
  365:     case SIGHUP:  return "SIGHUP";
  366:     case SIGINT:  return "SIGINT";
  367:     case SIGTERM: return "SIGTERM";
  368:     case SIGBREAK:return "SIGBREAK";
  369:     case SIGUSR1: return "SIGUSR1";
  370:     case SIGUSR2: return "SIGUSR2";
  371:     default:      return "*UNKNOWN*";
  372:   }
  373: }
  374: 
  375: 
  376: // Simulate sleep()
  377: 
  378: void daemon_sleep(int seconds)
  379: {
  380:   do {
  381:     if (num_sig_handlers <= 0) {
  382:       Sleep(seconds*1000L);
  383:     }
  384:     else {
  385:       // Wait for any signal or timeout
  386:       DWORD rc = WaitForMultipleObjects(num_sig_handlers, sig_events,
  387:         FALSE/*OR*/, seconds*1000L);
  388:       if (rc != WAIT_TIMEOUT) {
  389:         if (!(/*WAIT_OBJECT_0(0) <= rc && */ rc < WAIT_OBJECT_0+(unsigned)num_sig_handlers)) {
  390:           fprintf(stderr,"WaitForMultipleObjects returns %lu\n", rc);
  391:           Sleep(seconds*1000L);
  392:           return;
  393:         }
  394:         // Call Handler
  395:         sig_handlers[rc-WAIT_OBJECT_0](sig_numbers[rc-WAIT_OBJECT_0]);
  396:         break;
  397:       }
  398:     }
  399:   } while (svc_paused);
  400: }
  401: 
  402: 
  403: // Disable/Enable console
  404: 
  405: void daemon_disable_console()
  406: {
  407:   SetConsoleCtrlHandler(child_console_handler, FALSE/*remove*/);
  408:   reopen_stdin = reopen_stdout = reopen_stderr = 0;
  409:   if (isatty(fileno(stdin))) {
  410:     fclose(stdin); reopen_stdin = 1;
  411:   }
  412:   if (isatty(fileno(stdout))) {
  413:     fclose(stdout); reopen_stdout = 1;
  414:   }
  415:   if (isatty(fileno(stderr))) {
  416:     fclose(stderr); reopen_stderr = 1;
  417:   }
  418:   FreeConsole();
  419:   SetConsoleCtrlHandler(child_console_handler, TRUE/*add*/);
  420: }
  421: 
  422: int daemon_enable_console(const char * title)
  423: {
  424:   BOOL ok;
  425:   SetConsoleCtrlHandler(child_console_handler, FALSE/*remove*/);
  426:   ok = AllocConsole();
  427:   SetConsoleCtrlHandler(child_console_handler, TRUE/*add*/);
  428:   if (!ok)
  429:     return -1;
  430:   if (title)
  431:     SetConsoleTitleA(title);
  432:   if (reopen_stdin)
  433:     freopen("conin$",  "r", stdin);
  434:   if (reopen_stdout)
  435:     freopen("conout$", "w", stdout);
  436:   if (reopen_stderr)
  437:     freopen("conout$", "w", stderr);
  438:   reopen_stdin = reopen_stdout = reopen_stderr = 0;
  439:   return 0;
  440: }
  441: 
  442: 
  443: // Detach daemon from console & parent
  444: 
  445: int daemon_detach(const char * ident)
  446: {
  447:   if (!svc_mode) {
  448:     if (ident) {
  449:       // Print help
  450:       FILE * f = ( isatty(fileno(stdout)) ? stdout
  451:              : isatty(fileno(stderr)) ? stderr : NULL);
  452:       if (f)
  453:         daemon_help(f, ident, "now detaches from console into background mode");
  454:     }
  455:     // Signal detach to parent
  456:     if (sig_event(EVT_DETACHED) != 1) {
  457:       if (!debugging())
  458:         return -1;
  459:     }
  460:     daemon_disable_console();
  461:   }
  462:   else {
  463:     // Signal end of initialization to service control manager
  464:     service_report_status(SERVICE_RUNNING, 0);
  465:     reopen_stdin = reopen_stdout = reopen_stderr = 1;
  466:   }
  467: 
  468:   return 0;
  469: }
  470: 
  471: 
  472: /////////////////////////////////////////////////////////////////////////////
  473: 
  474: // Spawn a command and redirect <inpbuf >outbuf
  475: // return command's exitcode or -1 on error
  476: 
  477: int daemon_spawn(const char * cmd,
  478:                  const char * inpbuf, int inpsize,
  479:                  char *       outbuf, int outsize )
  480: {
  481:   HANDLE self = GetCurrentProcess();
  482: 
  483:   // Create stdin pipe with inheritable read side
  484:   SECURITY_ATTRIBUTES sa;
  485:   memset(&sa, 0, sizeof(sa)); sa.nLength = sizeof(sa);
  486:   sa.bInheritHandle = TRUE;
  487:   HANDLE pipe_inp_r, pipe_inp_w, h;
  488:   if (!CreatePipe(&pipe_inp_r, &h, &sa/*inherit*/, inpsize*2+13))
  489:     return -1;
  490:   if (!DuplicateHandle(self, h, self, &pipe_inp_w,
  491:     0, FALSE/*!inherit*/, DUPLICATE_SAME_ACCESS|DUPLICATE_CLOSE_SOURCE)) {
  492:     CloseHandle(pipe_inp_r);
  493:     return -1;
  494:   }
  495: 
  496:   // Create stdout pipe with inheritable write side
  497:   memset(&sa, 0, sizeof(sa)); sa.nLength = sizeof(sa);
  498:   sa.bInheritHandle = TRUE;
  499:   HANDLE pipe_out_w;
  500:   if (!CreatePipe(&h, &pipe_out_w, &sa/*inherit*/, outsize)) {
  501:     CloseHandle(pipe_inp_r); CloseHandle(pipe_inp_w);
  502:     return -1;
  503:   }
  504: 
  505:   HANDLE pipe_out_r;
  506:   if (!DuplicateHandle(self, h, self, &pipe_out_r,
  507:     GENERIC_READ, FALSE/*!inherit*/, DUPLICATE_CLOSE_SOURCE)) {
  508:     CloseHandle(pipe_out_w); CloseHandle(pipe_inp_r); CloseHandle(pipe_inp_w);
  509:     return -1;
  510:   }
  511: 
  512:   // Create stderr handle as dup of stdout write side
  513:   HANDLE pipe_err_w;
  514:   if (!DuplicateHandle(self, pipe_out_w, self, &pipe_err_w,
  515:     0, TRUE/*inherit*/, DUPLICATE_SAME_ACCESS)) {
  516:     CloseHandle(pipe_out_r); CloseHandle(pipe_out_w);
  517:     CloseHandle(pipe_inp_r); CloseHandle(pipe_inp_w);
  518:     return -1;
  519:   }
  520: 
  521:   // Create process with pipes as stdio
  522:   STARTUPINFO si;
  523:   memset(&si, 0, sizeof(si)); si.cb = sizeof(si);
  524:   si.hStdInput  = pipe_inp_r;
  525:   si.hStdOutput = pipe_out_w;
  526:   si.hStdError  = pipe_err_w;
  527:   si.dwFlags = STARTF_USESTDHANDLES;
  528:   PROCESS_INFORMATION pi;
  529:   if (!CreateProcessA(
  530:     NULL, (char*)cmd,
  531:     NULL, NULL, TRUE/*inherit*/,
  532:     CREATE_NO_WINDOW, // DETACHED_PROCESS does not work
  533:     NULL, NULL, &si, &pi)) {
  534:     CloseHandle(pipe_err_w);
  535:     CloseHandle(pipe_out_r); CloseHandle(pipe_out_w);
  536:     CloseHandle(pipe_inp_r); CloseHandle(pipe_inp_w);
  537:     return -1;
  538:   }
  539:   CloseHandle(pi.hThread);
  540:   // Close inherited handles
  541:   CloseHandle(pipe_inp_r);
  542:   CloseHandle(pipe_out_w);
  543:   CloseHandle(pipe_err_w);
  544: 
  545:   // Copy inpbuf to stdin
  546:   // convert \n => \r\n
  547:   DWORD num_io;
  548:   int i;
  549:   for (i = 0; i < inpsize; ) {
  550:     int len = 0;
  551:     while (i+len < inpsize && inpbuf[i+len] != '\n')
  552:       len++;
  553:     if (len > 0)
  554:       WriteFile(pipe_inp_w, inpbuf+i, len, &num_io, NULL);
  555:     i += len;
  556:     if (i < inpsize) {
  557:       WriteFile(pipe_inp_w, "\r\n", 2, &num_io, NULL);
  558:       i++;
  559:     }
  560:   }
  561:   CloseHandle(pipe_inp_w);
  562: 
  563:   // Copy stdout to output buffer until full, rest to /dev/null
  564:   // convert \r\n => \n
  565:   for (i = 0; ; ) {
  566:     char buf[256];
  567:     if (!ReadFile(pipe_out_r, buf, sizeof(buf), &num_io, NULL) || num_io == 0)
  568:       break;
  569:     for (int j = 0; i < outsize-1 && j < (int)num_io; j++) {
  570:       if (buf[j] != '\r')
  571:         outbuf[i++] = buf[j];
  572:     }
  573:   }
  574:   outbuf[i] = 0;
  575:   CloseHandle(pipe_out_r);
  576: 
  577:   // Wait for process exitcode
  578:   DWORD exitcode = 42;
  579:   WaitForSingleObject(pi.hProcess, INFINITE);
  580:   GetExitCodeProcess(pi.hProcess, &exitcode);
  581:   CloseHandle(pi.hProcess);
  582:   return exitcode;
  583: }
  584: 
  585: 
  586: /////////////////////////////////////////////////////////////////////////////
  587: // Initd Functions
  588: 
  589: static int wait_signaled(HANDLE h, int seconds)
  590: {
  591:   int i;
  592:   for (i = 0; ; ) {
  593:     if (WaitForSingleObject(h, 1000L) == WAIT_OBJECT_0)
  594:       return 0;
  595:     if (++i >= seconds)
  596:       return -1;
  597:     fputchar('.'); fflush(stdout);
  598:   }
  599: }
  600: 
  601: 
  602: static int wait_evt_running(int seconds, int exists)
  603: {
  604:   int i;
  605:   if (event_exists(EVT_RUNNING) == exists)
  606:     return 0;
  607:   for (i = 0; ; ) {
  608:     Sleep(1000);
  609:     if (event_exists(EVT_RUNNING) == exists)
  610:       return 0;
  611:     if (++i >= seconds)
  612:       return -1;
  613:     fputchar('.'); fflush(stdout);
  614:   }
  615: }
  616: 
  617: 
  618: static int is_initd_command(char * s)
  619: {
  620:   if (!strcmp(s, "status"))
  621:     return EVT_RUNNING;
  622:   if (!strcmp(s, "stop"))
  623:     return SIGTERM;
  624:   if (!strcmp(s, "reload"))
  625:     return SIGHUP;
  626:   if (!strcmp(s, "sigusr1"))
  627:     return SIGUSR1;
  628:   if (!strcmp(s, "sigusr2"))
  629:     return SIGUSR2;
  630:   if (!strcmp(s, "restart"))
  631:     return EVT_RESTART;
  632:   return -1;
  633: }
  634: 
  635: 
  636: static int initd_main(const char * ident, int argc, char **argv)
  637: {
  638:   int rc;
  639:   if (argc < 2)
  640:     return -1;
  641:   if ((rc = is_initd_command(argv[1])) < 0)
  642:     return -1;
  643:   if (argc != 2) {
  644:     printf("%s: no arguments allowed for command %s\n", ident, argv[1]);
  645:     return 1;
  646:   }
  647: 
  648:   switch (rc) {
  649:     default:
  650:     case EVT_RUNNING:
  651:       printf("Checking for %s:", ident); fflush(stdout);
  652:       rc = event_exists(EVT_RUNNING);
  653:       puts(rc ? " running" : " not running");
  654:       return (rc ? 0 : 1);
  655: 
  656:     case SIGTERM:
  657:       printf("Stopping %s:", ident); fflush(stdout);
  658:       rc = sig_event(SIGTERM);
  659:       if (rc <= 0) {
  660:         puts(rc < 0 ? " not running" : " error");
  661:         return (rc < 0 ? 0 : 1);
  662:       }
  663:       rc = wait_evt_running(10, 0);
  664:       puts(!rc ? " done" : " timeout");
  665:       return (!rc ? 0 : 1);
  666: 
  667:     case SIGHUP:
  668:       printf("Reloading %s:", ident); fflush(stdout);
  669:       rc = sig_event(SIGHUP);
  670:       puts(rc > 0 ? " done" : rc == 0 ? " error" : " not running");
  671:       return (rc > 0 ? 0 : 1);
  672: 
  673:     case SIGUSR1:
  674:     case SIGUSR2:
  675:       printf("Sending SIGUSR%d to %s:", (rc-SIGUSR1+1), ident); fflush(stdout);
  676:       rc = sig_event(rc);
  677:       puts(rc > 0 ? " done" : rc == 0 ? " error" : " not running");
  678:       return (rc > 0 ? 0 : 1);
  679: 
  680:     case EVT_RESTART:
  681:       {
  682:         HANDLE rst;
  683:         printf("Stopping %s:", ident); fflush(stdout);
  684:         if (event_exists(EVT_DETACHED)) {
  685:           puts(" not detached, cannot restart");
  686:           return 1;
  687:         }
  688:         if (!(rst = create_event(EVT_RESTART, FALSE, FALSE, NULL))) {
  689:           puts(" error");
  690:           return 1;
  691:         }
  692:         rc = sig_event(SIGTERM);
  693:         if (rc <= 0) {
  694:           puts(rc < 0 ? " not running" : " error");
  695:           CloseHandle(rst);
  696:           return 1;
  697:         }
  698:         rc = wait_signaled(rst, 10);
  699:         CloseHandle(rst);
  700:         if (rc) {
  701:           puts(" timeout");
  702:           return 1;
  703:         }
  704:         puts(" done");
  705:         Sleep(100);
  706: 
  707:         printf("Starting %s:", ident); fflush(stdout);
  708:         rc = wait_evt_running(10, 1);
  709:         puts(!rc ? " done" : " error");
  710:         return (!rc ? 0 : 1);
  711:       }
  712:   }
  713: }
  714: 
  715: 
  716: /////////////////////////////////////////////////////////////////////////////
  717: // Windows Service Functions
  718: 
  719: int daemon_winsvc_exitcode; // Set by app to exit(code)
  720: 
  721: static SERVICE_STATUS_HANDLE svc_handle;
  722: static SERVICE_STATUS svc_status;
  723: 
  724: 
  725: // Report status to SCM
  726: 
  727: static void service_report_status(int state, int seconds)
  728: {
  729:   // TODO: Avoid race
  730:   static DWORD checkpoint = 1;
  731:   svc_status.dwCurrentState = state;
  732:   svc_status.dwWaitHint = seconds*1000;
  733:   switch (state) {
  734:     default:
  735:       svc_status.dwCheckPoint = checkpoint++;
  736:       break;
  737:     case SERVICE_RUNNING:
  738:     case SERVICE_STOPPED:
  739:       svc_status.dwCheckPoint = 0;
  740:   }
  741:   switch (state) {
  742:     case SERVICE_START_PENDING:
  743:     case SERVICE_STOP_PENDING:
  744:       svc_status.dwControlsAccepted = 0;
  745:       break;
  746:     default:
  747:       svc_status.dwControlsAccepted =
  748:         SERVICE_ACCEPT_STOP|SERVICE_ACCEPT_SHUTDOWN|
  749:         SERVICE_ACCEPT_PAUSE_CONTINUE|SERVICE_ACCEPT_PARAMCHANGE;
  750:       break;
  751:   }
  752:   SetServiceStatus(svc_handle, &svc_status);
  753: }
  754: 
  755: 
  756: // Control the service, called by SCM
  757: 
  758: static void WINAPI service_control(DWORD ctrlcode)
  759: {
  760:   switch (ctrlcode) {
  761:     case SERVICE_CONTROL_STOP:
  762:     case SERVICE_CONTROL_SHUTDOWN:
  763:       service_report_status(SERVICE_STOP_PENDING, 30);
  764:       svc_paused = 0;
  765:       SetEvent(sigterm_handle);
  766:       break;
  767:     case SERVICE_CONTROL_PARAMCHANGE: // Win2000/XP
  768:       service_report_status(svc_status.dwCurrentState, 0);
  769:       svc_paused = 0;
  770:       SetEvent(sighup_handle); // reload
  771:       break;
  772:     case SERVICE_CONTROL_PAUSE:
  773:       service_report_status(SERVICE_PAUSED, 0);
  774:       svc_paused = 1;
  775:       break;
  776:     case SERVICE_CONTROL_CONTINUE:
  777:       service_report_status(SERVICE_RUNNING, 0);
  778:       {
  779:         int was_paused = svc_paused;
  780:         svc_paused = 0;
  781:         SetEvent(was_paused ? sighup_handle : sigusr1_handle); // reload:recheck
  782:       }
  783:       break;
  784:     case SERVICE_CONTROL_INTERROGATE:
  785:     default: // unknown
  786:       service_report_status(svc_status.dwCurrentState, 0);
  787:       break;
  788:   }
  789: }
  790: 
  791: 
  792: // Exit handler for service
  793: 
  794: static void service_exit(void)
  795: {
  796:   // Close signal events
  797:   int i;
  798:   for (i = 0; i < num_sig_handlers; i++)
  799:     CloseHandle(sig_events[i]);
  800:   num_sig_handlers = 0;
  801: 
  802:   // Set exitcode
  803:   if (daemon_winsvc_exitcode) {
  804:     svc_status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
  805:     svc_status.dwServiceSpecificExitCode = daemon_winsvc_exitcode;
  806:   }
  807:   // Report stopped
  808:   service_report_status(SERVICE_STOPPED, 0);
  809: }
  810: 
  811: 
  812: // Variables for passing main(argc, argv) from daemon_main to service_main()
  813: static int (*svc_main_func)(int, char **);
  814: static int svc_main_argc;
  815: static char ** svc_main_argv;
  816: 
  817: // Main function for service, called by service dispatcher
  818: 
  819: static void WINAPI service_main(DWORD /*argc*/, LPSTR * argv)
  820: {
  821:   char path[MAX_PATH], *p;
  822: 
  823:   // Register control handler
  824:   svc_handle = RegisterServiceCtrlHandler(argv[0], service_control);
  825: 
  826:   // Init service status
  827:   svc_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
  828:   service_report_status(SERVICE_START_PENDING, 10);
  829: 
  830:   // Service started in \windows\system32, change to .exe directory
  831:   if (GetModuleFileNameA(NULL, path, sizeof(path)) && (p = strrchr(path, '\\'))) {
  832:     *p = 0; SetCurrentDirectoryA(path);
  833:   }
  834: 
  835:   // Install exit handler
  836:   atexit(service_exit);
  837: 
  838:   // Do the real work, service status later updated by daemon_detach()
  839:   daemon_winsvc_exitcode = svc_main_func(svc_main_argc, svc_main_argv);
  840: 
  841:   exit(daemon_winsvc_exitcode);
  842:   // ... continued in service_exit()
  843: }
  844: 
  845: 
  846: /////////////////////////////////////////////////////////////////////////////
  847: // Windows Service Admin Functions
  848: 
  849: 
  850: // Make registry key name for event message file
  851: static bool make_evtkey(char * buf, unsigned size, const char * ident)
  852: {
  853:   static const char prefix[] = "SYSTEM\\CurrentControlSet\\Services\\Eventlog\\Application\\";
  854:   const unsigned pfxlen = sizeof(prefix)-1;
  855:   unsigned idlen = strlen(ident);
  856:   if (pfxlen + idlen >= size) {
  857:     printf(" Buffer overflow\n");
  858:     return false;
  859:   }
  860:   memcpy(buf, prefix, pfxlen);
  861:   memcpy(buf+pfxlen, ident, idlen+1);
  862:   return true;
  863: }
  864: 
  865: // Install this exe as event message file
  866: static void inst_evtmsg(const char * ident)
  867: {
  868:   printf("Installing event message file for %s:", ident); fflush(stdout);
  869: 
  870:   char mypath[MAX_PATH];
  871:   if (!GetModuleFileNameA((HMODULE)0, mypath, sizeof(mypath))) {
  872:     printf(" unknown program path, Error=%ld\n", GetLastError());
  873:     return;
  874:   }
  875: 
  876:   char subkey[MAX_PATH];
  877:   if (!make_evtkey(subkey, sizeof(subkey), ident))
  878:     return;
  879: 
  880:   HKEY hk;
  881:   LONG err = RegCreateKeyExA(HKEY_LOCAL_MACHINE, subkey, 0, (char *)0, 0, KEY_ALL_ACCESS,
  882:                              (SECURITY_ATTRIBUTES *)0, &hk, (DWORD *)0);
  883:   if (err != ERROR_SUCCESS) {
  884:     printf(" RegCreateKeyEx failed, error=%ld\n", err);
  885:     return;
  886:   }
  887: 
  888:   err = RegSetValueExA(hk, "EventMessageFile", 0, REG_SZ,
  889:                        (const BYTE *)mypath, strlen(mypath)+1);
  890:   if (err == ERROR_SUCCESS) {
  891:     DWORD val = EVENTLOG_INFORMATION_TYPE
  892:                |EVENTLOG_WARNING_TYPE
  893:                |EVENTLOG_ERROR_TYPE;
  894:     err = RegSetValueExA(hk, "TypesSupported", 0, REG_DWORD,
  895:                          (const BYTE *)&val, sizeof(val));
  896:   }
  897:   if (err != ERROR_SUCCESS)
  898:     printf(" RegSetValueEx failed, error=%ld\n", err);
  899: 
  900:   RegCloseKey(hk);
  901:   puts(" done");
  902: }
  903: 
  904: // Uninstall event message file
  905: static void uninst_evtmsg(const char * ident)
  906: {
  907:   printf("Removing event message file for %s:", ident); fflush(stdout);
  908: 
  909:   char subkey[MAX_PATH];
  910:   if (!make_evtkey(subkey, sizeof(subkey), ident))
  911:     return;
  912: 
  913:   LONG err = RegDeleteKeyA(HKEY_LOCAL_MACHINE, subkey);
  914:   if (err != ERROR_SUCCESS && err != ERROR_FILE_NOT_FOUND) {
  915:     printf(" RegDeleteKey failed, error=%ld\n", err);
  916:     return;
  917:   }
  918:   puts(" done");
  919: }
  920: 
  921: 
  922: // Service install/remove commands
  923: 
  924: static int svcadm_main(const char * ident, const daemon_winsvc_options * svc_opts,
  925:                        int argc, char **argv                                      )
  926: {
  927:   int remove; long err;
  928:   SC_HANDLE hm, hs;
  929: 
  930:   if (argc < 2)
  931:     return -1;
  932:   if (!strcmp(argv[1], "install"))
  933:     remove = 0;
  934:   else if (!strcmp(argv[1], "remove")) {
  935:     if (argc != 2) {
  936:       printf("%s: no arguments allowed for command remove\n", ident);
  937:       return 1;
  938:     }
  939:     remove = 1;
  940:   }
  941:   else
  942:     return -1;
  943: 
  944:   printf("%s service %s:", (!remove?"Installing":"Removing"), ident); fflush(stdout);
  945: 
  946:   // Open SCM
  947:   if (!(hm = OpenSCManager(NULL/*local*/, NULL/*default*/, SC_MANAGER_ALL_ACCESS))) {
  948:     if ((err = GetLastError()) == ERROR_ACCESS_DENIED)
  949:       puts(" access to SCManager denied");
  950:     else
  951:       printf(" cannot open SCManager, Error=%ld\n", err);
  952:     return 1;
  953:   }
  954: 
  955:   if (!remove) {
  956:     char path[MAX_PATH+100];
  957:     int i;
  958:     // Get program path
  959:     if (!GetModuleFileNameA(NULL, path, MAX_PATH)) {
  960:       printf(" unknown program path, Error=%ld\n", GetLastError());
  961:       CloseServiceHandle(hm);
  962:       return 1;
  963:     }
  964:     // Add quotes if necessary
  965:     if (strchr(path, ' ')) {
  966:       i = strlen(path);
  967:       path[i+1] = '"'; path[i+2] = 0;
  968:       while (--i >= 0)
  969:         path[i+1] = path[i];
  970:       path[0] = '"';
  971:     }
  972:     // Append options
  973:     strcat(path, " "); strcat(path, svc_opts->cmd_opt);
  974:     for (i = 2; i < argc; i++) {
  975:       const char * s = argv[i];
  976:       if (strlen(path)+1+1+strlen(s)+1 >= sizeof(path))
  977:         break;
  978:       // Add quotes if necessary
  979:       if (strchr(s, ' ') && !strchr(s, '"')) {
  980:         strcat(path, " \""); strcat(path, s); strcat(path, "\"");
  981:       }
  982:       else {
  983:         strcat(path, " "); strcat(path, s);
  984:       }
  985:     }
  986:     // Create
  987:     if (!(hs = CreateService(hm,
  988:       svc_opts->svcname, svc_opts->dispname,
  989:       SERVICE_ALL_ACCESS,
  990:       SERVICE_WIN32_OWN_PROCESS,
  991:       SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, path,
  992:       NULL/*no load ordering*/, NULL/*no tag id*/,
  993:       ""/*no depedencies*/, NULL/*local system account*/, NULL/*no pw*/))) {
  994:       if ((err = GetLastError()) == ERROR_SERVICE_EXISTS)
  995:         puts(" the service is already installed");
  996:       else if (err == ERROR_SERVICE_MARKED_FOR_DELETE)
  997:         puts(" service is still running and marked for deletion\n"
  998:              "Stop the service and retry install");
  999:       else
 1000:         printf(" failed, Error=%ld\n", err);
 1001:       CloseServiceHandle(hm);
 1002:       return 1;
 1003:     }
 1004:     // Set optional description
 1005:     if (svc_opts->descript) {
 1006:       SERVICE_DESCRIPTIONA sd = { const_cast<char *>(svc_opts->descript) };
 1007:       ChangeServiceConfig2A(hs, SERVICE_CONFIG_DESCRIPTION, &sd);
 1008:     }
 1009:     // Enable delayed auto start if supported
 1010:     OSVERSIONINFOA ver; ver.dwOSVersionInfoSize = sizeof(ver);
 1011:     if (   GetVersionExA(&ver)
 1012:         && ver.dwPlatformId == VER_PLATFORM_WIN32_NT
 1013:         && ver.dwMajorVersion >= 6 /* Vista */      ) {
 1014:       SERVICE_DELAYED_AUTO_START_INFO sdasi = { TRUE };
 1015:       ChangeServiceConfig2A(hs, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, &sdasi);
 1016:     }
 1017:   }
 1018:   else {
 1019:     // Open
 1020:     if (!(hs = OpenService(hm, svc_opts->svcname, SERVICE_ALL_ACCESS))) {
 1021:       puts(" not found");
 1022:       CloseServiceHandle(hm);
 1023:       return 1;
 1024:     }
 1025:     // TODO: Stop service if running
 1026:     // Remove
 1027:     if (!DeleteService(hs)) {
 1028:       if ((err = GetLastError()) == ERROR_SERVICE_MARKED_FOR_DELETE)
 1029:         puts(" service is still running and marked for deletion\n"
 1030:              "Stop the service to remove it");
 1031:       else
 1032:         printf(" failed, Error=%ld\n", err);
 1033:       CloseServiceHandle(hs); CloseServiceHandle(hm);
 1034:       return 1;
 1035:     }
 1036:   }
 1037:   puts(" done");
 1038:   CloseServiceHandle(hs); CloseServiceHandle(hm);
 1039: 
 1040:   // Install/Remove event message file registry entry
 1041:   if (!remove) {
 1042:     inst_evtmsg(ident);
 1043:   }
 1044:   else {
 1045:     uninst_evtmsg(ident);
 1046:   }
 1047: 
 1048:   return 0;
 1049: }
 1050: 
 1051: 
 1052: /////////////////////////////////////////////////////////////////////////////
 1053: // Main Function
 1054: 
 1055: // This function must be called from main()
 1056: // main_func is the function doing the real work
 1057: 
 1058: int daemon_main(const char * ident, const daemon_winsvc_options * svc_opts,
 1059:                 int (*main_func)(int, char **), int argc, char **argv      )
 1060: {
 1061:   int rc;
 1062: #ifdef _DEBUG
 1063:   // Enable Debug heap checks
 1064:   _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG)
 1065:     |_CRTDBG_ALLOC_MEM_DF|_CRTDBG_CHECK_ALWAYS_DF|_CRTDBG_LEAK_CHECK_DF);
 1066: #endif
 1067: 
 1068:   // Check for [status|stop|reload|restart|sigusr1|sigusr2] parameters
 1069:   if ((rc = initd_main(ident, argc, argv)) >= 0)
 1070:     return rc;
 1071:   // Check for [install|remove] parameters
 1072:   if (svc_opts && (rc = svcadm_main(ident, svc_opts, argc, argv)) >= 0)
 1073:     return rc;
 1074: 
 1075:   // Run as service if svc_opts.cmd_opt is given as first(!) argument
 1076:   svc_mode = (svc_opts && argc >= 2 && !strcmp(argv[1], svc_opts->cmd_opt));
 1077: 
 1078:   if (!svc_mode) {
 1079:     // Daemon: Try to simulate a Unix-like daemon
 1080:     HANDLE rev;
 1081:     BOOL exists;
 1082: 
 1083:     // Create main event to detect process type:
 1084:     // 1. new: parent process => start child and wait for detach() or exit() of child.
 1085:     // 2. exists && signaled: child process => do the real work, signal detach() to parent
 1086:     // 3. exists && !signaled: already running => exit()
 1087:     if (!(rev = create_event(EVT_RUNNING, TRUE/*signaled*/, TRUE, &exists)))
 1088:       return 100;
 1089: 
 1090:     if (!exists && !debugging()) {
 1091:       // Event new => parent process
 1092:       return parent_main(rev);
 1093:     }
 1094: 
 1095:     if (WaitForSingleObject(rev, 0) == WAIT_OBJECT_0) {
 1096:       // Event was signaled => In child process
 1097:       return child_main(rev, main_func, argc, argv);
 1098:     }
 1099: 
 1100:     // Event no longer signaled => Already running!
 1101:     daemon_help(stdout, ident, "already running");
 1102:     CloseHandle(rev);
 1103:     return 1;
 1104:   }
 1105:   else {
 1106:     // Service: Start service_main() via SCM
 1107:     SERVICE_TABLE_ENTRY service_table[] = {
 1108:       { (char*)svc_opts->svcname, service_main }, { NULL, NULL }
 1109:     };
 1110: 
 1111:     svc_main_func = main_func;
 1112:     svc_main_argc = argc;
 1113:     svc_main_argv = argv;
 1114:     if (!StartServiceCtrlDispatcher(service_table)) {
 1115:       printf("%s: cannot dispatch service, Error=%ld\n"
 1116:         "Option \"%s\" cannot be used to start %s as a service from console.\n"
 1117:         "Use \"%s install ...\" to install the service\n"
 1118:         "and \"net start %s\" to start it.\n",
 1119:         ident, GetLastError(), svc_opts->cmd_opt, ident, ident, ident);
 1120: 
 1121: #ifdef _DEBUG
 1122:       if (debugging())
 1123:         service_main(argc, argv);
 1124: #endif
 1125:       return 100;
 1126:     }
 1127:     Sleep(1000);
 1128:     ExitThread(0); // Do not redo exit() processing
 1129:     /*NOTREACHED*/
 1130:     return 0;
 1131:   }
 1132: }
 1133: 
 1134: 
 1135: /////////////////////////////////////////////////////////////////////////////
 1136: // Test Program
 1137: 
 1138: #ifdef TEST
 1139: 
 1140: static volatile sig_atomic_t caughtsig = 0;
 1141: 
 1142: static void sig_handler(int sig)
 1143: {
 1144:   caughtsig = sig;
 1145: }
 1146: 
 1147: static void test_exit(void)
 1148: {
 1149:   printf("Main exit\n");
 1150: }
 1151: 
 1152: int test_main(int argc, char **argv)
 1153: {
 1154:   int i;
 1155:   int debug = 0;
 1156:   char * cmd = 0;
 1157: 
 1158:   printf("PID=%ld\n", GetCurrentProcessId());
 1159:   for (i = 0; i < argc; i++) {
 1160:     printf("%d: \"%s\"\n", i, argv[i]);
 1161:     if (!strcmp(argv[i],"-d"))
 1162:       debug = 1;
 1163:   }
 1164:   if (argc > 1 && argv[argc-1][0] != '-')
 1165:     cmd = argv[argc-1];
 1166: 
 1167:   daemon_signal(SIGINT, sig_handler);
 1168:   daemon_signal(SIGBREAK, sig_handler);
 1169:   daemon_signal(SIGTERM, sig_handler);
 1170:   daemon_signal(SIGHUP, sig_handler);
 1171:   daemon_signal(SIGUSR1, sig_handler);
 1172:   daemon_signal(SIGUSR2, sig_handler);
 1173: 
 1174:   atexit(test_exit);
 1175: 
 1176:   if (!debug) {
 1177:     printf("Preparing to detach...\n");
 1178:     Sleep(2000);
 1179:     daemon_detach("test");
 1180:     printf("Detached!\n");
 1181:   }
 1182: 
 1183:   for (;;) {
 1184:     daemon_sleep(1);
 1185:     printf("."); fflush(stdout);
 1186:     if (caughtsig) {
 1187:       if (caughtsig == SIGUSR2) {
 1188:         debug ^= 1;
 1189:         if (debug)
 1190:           daemon_enable_console("Daemon[Debug]");
 1191:         else
 1192:           daemon_disable_console();
 1193:       }
 1194:       else if (caughtsig == SIGUSR1 && cmd) {
 1195:         char inpbuf[200], outbuf[1000]; int rc;
 1196:         strcpy(inpbuf, "Hello\nWorld!\n");
 1197:         rc = daemon_spawn(cmd, inpbuf, strlen(inpbuf), outbuf, sizeof(outbuf));
 1198:         if (!debug)
 1199:           daemon_enable_console("Command output");
 1200:         printf("\"%s\" returns %d\n", cmd, rc);
 1201:         if (rc >= 0)
 1202:           printf("output:\n%s.\n", outbuf);
 1203:         fflush(stdout);
 1204:         if (!debug) {
 1205:           Sleep(10000); daemon_disable_console();
 1206:         }
 1207:       }
 1208:       printf("[PID=%ld: Signal=%d]", GetCurrentProcessId(), caughtsig); fflush(stdout);
 1209:       if (caughtsig == SIGTERM || caughtsig == SIGBREAK)
 1210:         break;
 1211:       caughtsig = 0;
 1212:     }
 1213:   }
 1214:   printf("\nExiting on signal %d\n", caughtsig);
 1215:   return 0;
 1216: }
 1217: 
 1218: 
 1219: int main(int argc, char **argv)
 1220: {
 1221:   static const daemon_winsvc_options svc_opts = {
 1222:   "-s", "test", "Test Service", "Service to test daemon_win32.c Module"
 1223:   };
 1224: 
 1225:   return daemon_main("testd", &svc_opts, test_main, argc, argv);
 1226: }
 1227: 
 1228: #endif

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>