## root/source/timesync.c

/* [<][>][^][v][top][bottom][index][help] */

## DEFINITIONS

This source file includes following definitions.

1. main
2. parse_comline
3. srv_initialize
4. s_log_off
5. s_log_on
6. s_log_status
7. process_request
8. do_comm
9. s_terminate
10. s_get_datime
11. s_trans_num
12. s_connect_num
13. do_reply_code
14. do_reply_char
15. do_reply_int
16. do_reply_long
17. term_app
18. usage

    /* timesync - A date/time synchronization server for TCP sockets
       and QNX message passing IPC methods. Rick Smereka, Copyright (C) 2001-2006.

       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.

       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.

       You should have received a copy of the GNU General Public License
       along with this program; if not, get a copy via the Internet at
       http://gnu.org/copyleft/gpl.html or write to the Free Software
       Foundation, Inc., 59 Temple Place, Suite 330, Boston,
       MA 02111-1307 USA

       You can contact the author via email at rsmereka@future-lab.com

       This program does not attempt to implement any of the Internet
       date/time protocols. If absolute atomic time is required, this
       server should be run on a machine that runs a atomic time client.
       TCP program syntax:

          timesync [-q] [-l log_file] port

       QNX message passing syntax:

          timesync [-q] [-l log_file]

       Where the 'q' option will supress console output (quiet) and the 'l'
       (el) option specifies the server log file.

       The TCP IPC method version of this server requires at least one running
       'socloc' server.

       Original QNX version, Nov/2001, Rick Smereka

       Ported to Red Hat Linux and Windows 32bit.
       Dec/2001, Rick Smereka

       Coded to use the 'appinit' API. May/2002, Rick Smereka

       Ported to Debian Linux. Jan/2003, Rick Smereka

       Modified to use the updated 'socloc' API ('sloc.c').
       Modified to use the updated server IPC API ('ipcsrv.c').
       Added function 'term_app' and modified exit points to use
       this function. Added support for the send codes
       'TS_SEND_TRANS_NUM' and 'TS_SEND_CONNECT_NUM'.
       Jun/2003, Rick Smereka

       Changed all logging calls to use the 'logman' API.
       Added the command line parameter '-s' to have this
       server wait for the system logging server to become
       available if it is also initializing. TCP program syntax:

          timesync [-q] [-s] [-l log_file] port

       QNX message passing syntax:

          timesync [-q] [-s] [-l log_file]

       Dec/2004, Rick Smereka

       Re-compile after changing the 'socloc' API.
       Jan/2005, Rick Smereka

       Re-compile after modifications to low level TCP socket communication
       module (ipcomm.c). Feb/2006, Rick Smereka */

    #include "stdhead.h"
    #include "flsocket.h"                 /* IPC generic definitions */
    #include "ipcsrv.h"                   /* IPC server routines */
    #include "timesync.h"                 /* timesync defines */
    #include "appinit.h"                  /* application initialization API */
    #ifdef IPC_TCP
    #include "socloc.h"                   /* socket locate defines */
    #include "ip.h"                       /* IP library routines */
    #include "sloc.h"                     /* 'socloc' client API */
    #endif

    #define VERSION "1.07.01-2006.02.23"  /* version ID */
    #define TS_TERM_PASSWD "rdsWin32"     /* terminate server password */
    #define APNAME "timesync"

    /* default private log file */

    #define TS_LOG_FILE "timesync.log"

    /* WinSock specific global data structure */

    #ifdef OS_WIN32
    WSADATA wsaData;                      /* struct used by 'WSAStartup()' */
    #endif

    /* global data */

    int console_output = TRUE;            /* log messages to screen? */
    int waitfor_syslog = FALSE;           /* wait for system logger upon init? */
    char d_log_file[256];                 /* path/name of log file */
    #ifdef IPC_TCP
    int server_port = 0;                  /* TCP port that server is using */
    char localHost[256];                  /* host name of this machine */
    #else
    // QNX transaction counter
    long server_trans_num = 0L;
    #endif

    /* function prototypes */

    int main(int, char **);
    int parse_comline(int, char **);
    int srv_initialize(void);
    int process_request(void);
    int do_comm(int, char *);
    int s_terminate(char *);
    void s_log_off(void);
    void s_log_on(char *);
    void s_log_status(void);
    void s_get_datime(void);
    void s_trans_num(void);
    void s_connect_num(void);
    int do_reply_code(int);
    int do_reply_char(int, char *);
    int do_reply_int(int, int);
    int do_reply_long(int, long);
    void term_app(void);
    #ifdef IPC_TCP
    void usage();
    #endif

    int main(int argc, char **argv)
    {
       int isterm;

       /* indicate that we are initializing */

       if (!appinit_start(TS_SERVICE_NAME))
          {
          printf("%s:error creating 'appinit' lock file. Program abort\n", APNAME);
          return(0);
          }

       /* check for 'socloc' server initializing */

    #ifdef IPC_TCP
       if (!appinit_waitfor(SL_SERVICE_NAME, 5))
          {
          printf("%s:error waiting for 'socloc' to initialize. "
                 "Program abort\n", APNAME);
          return(0);
          }
    #endif

    #ifdef IPC_TCP
       if (argc == 1)
          {
          (void)appinit_stop(TS_SERVICE_NAME);
          usage();
          return(0);
          }
    #endif

       if (!parse_comline(argc, argv))
          {
          (void)appinit_stop(TS_SERVICE_NAME);
          return(0);
          }

       // wait for system logger to init if requested

       if (waitfor_syslog)
          if (!appinit_waitfor(SYS_LOG_SERVICE_NAME, 1))
             {
             printf("%s:error waiting for 'sys_log' to initialize. "
                    "Program abort\n", APNAME);
             (void)appinit_stop(TS_SERVICE_NAME);
             return(0);
             }

       /* set logger console output flag */

       logman_console(console_output);

       /* start server on IPC level, connect to a 'socloc' server and
          register this server */

       if (!srv_initialize())
          {
          (void)appinit_stop(TS_SERVICE_NAME);
          term_app();
          return(0);
          }

       /* initialize logging */

       if (logman_start(d_log_file, APNAME))
          {
          printf("%s:error initializing logging. Program abort\n", APNAME);
          (void)appinit_stop(TS_SERVICE_NAME);
          return(0);
          }

       /* build logo string based on platform */

    #ifndef OS_UNIX
       /* non-Unix */

       logman("%s Server for %s Version %s", APNAME, PLATFORM_STRING, VERSION);
    #else
       /* Unix */

       logman("%s Server for %s Version %s", APNAME, SUB_PLATFORM_STRING, VERSION);
    #endif

       logman("By Rick Smereka, Copyright (c) 2001-2006");

    #ifdef IPC_TCP
       logman("%s:server on \"%s\" servicing TCP port %d (decimal)",
                     APNAME, localHost, server_port);
    #endif
       logman("%s:server started", APNAME);
       logman("%s comes with ABSOLUTELY NO WARRANTY", APNAME);
       logman("This is free software, and you are welcome to redistribute it");
       logman("under certain conditions; see \"gpl.txt\" for information.");

       /* turn off log as this is used only for debugging */

       logman_end();

       /* indicate that the server has completed initialization */

       (void)appinit_stop(TS_SERVICE_NAME);

    #ifdef IPC_TCP
       ipc_server_wait();
    #else
       // QNX, process each connection (infinite loop)

       while(1)
          {
          isterm = process_request();
          server_trans_num++;

          if (isterm)
             break;
          }
    #endif

       term_app();
       return(0);
    }

    int parse_comline(int c_count, char **c_parm)
    {
       /* Parse the command line for parameters. Function
          returns 'TRUE' if no error was detected, 'FALSE'
          otherwise. */

       int parms = 1, done = FALSE;

       /* set default log file */

       strcpy(d_log_file, TS_LOG_FILE);

    #ifdef IPC_QNX
       if (c_count == 1)
          return(TRUE);
    #endif

       while(!done)
          {
          if (c_parm[parms][0] == SWITCH_CHAR)
             {
             switch(toupper(c_parm[parms][1]))
                {
                case 'L':
                   if (strlen(c_parm[parms]) > 2)
                      printf("%s:extraneous input with log switch, ignoring\n",
                             APNAME);

                   parms++;

                   if (parms >= c_count)
                      {
                      printf("%s:log switch with no file name, "
                             "program abort\n", APNAME);
                      return(FALSE);
                      }

                   strcpy(d_log_file, c_parm[parms]);
                   parms++;
                   break;

                case 'Q':
                   console_output = FALSE;
                   parms++;
                   break;

                case 'S':
                   waitfor_syslog = TRUE;
                   parms++;
                   break;

                default:
                   printf("%s:unknown switch[%s], program abort\n",
                          APNAME, c_parm[parms]);
                   return(FALSE);
                };
             }
    #ifdef IPC_TCP
          else
             {
             if (!qatoi(c_parm[parms], &server_port))
                {
                printf("%s:server port is non-numeric, "
                       "program abort\n", APNAME);
                return(FALSE);
                }

             if (server_port <= 0)
                {
                printf("%s:server port is out of range, "
                       "program abort\n", APNAME);
                return(FALSE);
                }

             parms++;
             }
    #endif

          if (parms >= c_count)
             done = TRUE;
          }

    #ifdef IPC_TCP
       if (server_port == 0)
          {
          printf("%s:server port missing, program abort\n", APNAME);
          return(FALSE);
          }
    #endif

       return(TRUE);
    }

    int srv_initialize(void)
    {
       /* Initialize the server. Function returns 'TRUE' upon
          success, 'FALSE' otherwise. */

       char mname[] = "srv_initialize";
       char mes[128];
       int ret;

       /* startup WinSock (if Windoze) */

    #ifdef OS_WIN32
       if (WSAStartup(WINSOCK_VERSION, &wsaData))
          {
          printf("%s:unable to start WinSock, program abort\n", mname);
          return(FALSE);
          }
    #endif

       /* initialize the server on the IPC level, note that
          the hostname 'localhost' is returned to us (TCP only) */

    #ifdef IPC_TCP
       if (!ipc_init(localHost, server_port))
    #else
       if (!ipc_init(TS_SERVICE_NAME))
    #endif
          {
          printf("%s:'ipc_init initialization failure, program abort\n", mname);
          return(FALSE);
          }

       /* initialize 'socloc' client API */

    #ifdef IPC_TCP
       if ((ret = sloc_initialize()) != SL_OK)
          {
          sl_code_string(ret, mes);
          printf("%s:bad rc[%s] from 'sloc_initialize'\n", mname, mes);
          return(FALSE);
          }

        /* get IP address of this machine */

       if (!ip_host2ip(localHost, mes))
          {
          printf("%s:unable to obtain IP address of this machine\n", mname);
          printf("%s:program abort\n", mname);
          return(FALSE);
          }

       /* register with 'socloc' */

       if ((ret = sloc_add(TS_SERVICE_NAME, localHost, server_port,
           mes)) != SL_OK)
          {
          sl_code_string(ret, mes);
          printf("%s:bad rc[%s] from 'sloc_add'\n", mname, mes);
          return(FALSE);
          }
    #endif

       return(TRUE);
    }

    void s_log_off(void)
    {
       /* Turn all loggging off. Of course, check for logging
          already off. */

       char mname[] = "s_log_off";
       int ret;

       logman("%s:enter", mname);

       if (!logman_is_active())
          ret = TS_LOG_ALREADY_OFF;
       else
          {
          logman_end();
          ret = TS_OK;
          }

       (void)do_reply_code(ret);
       logman("%s:normal exit rc[%d]", mname, ret);
    }

    void s_log_on(char *comm)
    {
       /* Turn server logging on using either the supplied log file name
          or the previously loaded log file name. Of course, check for logging
          already on. */

       char mname[] = "s_log_on";
       int ret;

       logman("%s:enter", mname);

       if (logman_is_active())
          ret = TS_LOG_ALREADY_ON;
       else
          {
          if (command_words(comm) > 1)
             {
             if (!command_word(comm, d_log_file, 2))
                {
                logman("%s:error getting log file name", mname);
                (void)do_reply_code(TS_INTERNAL_ERROR);
                return;
                }
             }

          if (logman_start(d_log_file, APNAME))
             {
             (void)do_reply_code(TS_LOG_ERROR);
             return;
             }

          ret = TS_OK;
          }

       (void)do_reply_code(ret);
       logman("%s:normal exit rc[%d]", mname, ret);
    }

    void s_log_status(void)
    {
       /* Get the current log status (on or off). */

       char *reply, *wrd, mname[] = "s_log_status";
       int full_len;

       logman("%s:enter", mname);
       full_len = strlen(d_log_file) + 5;

       if ((reply = (char *)malloc(full_len)) == (char *)NULL)
          {
          (void)do_reply_char(TS_MEMORY_FAIL, (char *)NULL);
          logman("%s:alloc fail[reply]", mname);
          return;
          }

       if ((wrd = initqstring(d_log_file)) == (char *)NULL)
          {
          free(reply);
          (void)do_reply_char(TS_MEMORY_FAIL, (char *)NULL);
          logman("%s:alloc fail[wrd]", mname);
          return;
          }

       if (logman_is_active())
          sprintf(reply, "1 %s", wrd);
       else
          sprintf(reply, "0 %s", wrd);

       free(wrd);
       (void)do_reply_char(TS_OK, reply);
       free(reply);
       logman("%s:normal exit rc[0]", mname);
    }

    int process_request(void)
    {
       /* Process a single request. Function returns 'FALSE' if
          termination signal was not encountered, 'TRUE'
          otherwise. */

       char mname[] = "process_request";
       char *messbuf, tmp[128];
       int ret, nbytes, nwords, mestype;

       logman("%s:enter", mname);

       if ((messbuf = (char *)malloc(TS_MAXCOMMAND)) == (char *)NULL)
          {
          (void)do_reply_code(TS_MEMORY_FAIL);
          logman("%s:alloc fail[messbuf]", mname);
          return(FALSE);
          }

       memset(messbuf, 0, TS_MAXCOMMAND);

       // get the message from the client

    #ifdef IPC_TCP
       if ((nbytes = ipc_recv_data(messbuf)) == 0)
    #else
       if ((nbytes = ipc_recv_data(messbuf, TS_MAXCOMMAND)) == 0)
    #endif
          {
          free(messbuf);
          logman("%s:connection closed by client", mname);
    #ifdef IPC_TCP
          return(-1);
    #else
          return(FALSE);
    #endif
          }

       messbuf[nbytes] = EOS;
       logman("%s:recv %s,l=%d", mname, messbuf, strlen(messbuf));
       nwords = command_words(messbuf);

       // no command words, empty string

       if (!nwords)
          {
          (void)do_reply_code(TS_INVALID_FUNCTION);
          free(messbuf);
          logman("%s:empty command", mname);
          return(FALSE);
          }

       // first command word must be a number

       if (!command_word(messbuf, tmp, 1))
          {
          free(messbuf);
          (void)do_reply_code(TS_INTERNAL_ERROR);
          logman("%s:error getting first command word", mname);
          return(FALSE);
          }

       if (!qatoi(tmp, &mestype))
          {
          (void)do_reply_code(TS_INVALID_FUNCTION);
          logman("%s:command signature not a number", mname);
          free(messbuf);
          return(FALSE);
          }

       /* pass entire string including command number */

       ret = do_comm(mestype, messbuf);
       free(messbuf);
       logman("%s:normal exit,rc=%d", mname, ret);
       return(ret);
    }

    int do_comm(int mestype, char *slm)
    {
       /* High level server command dispatcher.
          Function returns 'TRUE' if a terminate
          request was encountered, 'FALSE'
          otherwise. */

       char mname[] = "do_comm";
       int ret;

       logman("%s:enter", mname);
       ret = FALSE;

       switch(mestype)
          {
          case TS_SEND_STATUS:
             (void)do_reply_code(TS_OK);
             break;

          case TS_SEND_GET_DATIME:
             s_get_datime();
             break;

          case TS_SEND_TERM:
             if (s_terminate(slm))
                ret = TRUE;

             break;

          case TS_SEND_LOG_OFF:
             s_log_off();
             break;

          case TS_SEND_LOG_ON:
             s_log_on(slm);
             break;

          case TS_SEND_LOG_STATUS:
             s_log_status();
             break;

          case TS_SEND_SERVICE_NAME:
             (void)do_reply_char(TS_OK, TS_SERVICE_NAME);
             break;

          case TS_SEND_VERSION:
             (void)do_reply_char(TS_OK, VERSION);
             break;

          case TS_SEND_TRANS_NUM:
             s_trans_num();
             break;

          case TS_SEND_CONNECT_NUM:
             s_connect_num();
             break;

          default:
             logman("%s:received unknown code=%d", mname, mestype);
             (void)do_reply_code(TS_INVALID_FUNCTION);
             break;
          };

       logman("%s:exit[%d]", mname, ret);
       return(ret);
    }

    int s_terminate(char *comm)
    {
       /* Terminate the server. Check the supplied password first.
          Syntax:

             TS_SEND_TERM password

          Function returns 'TRUE' if correct password was supplied,
          'FALSE' otherwise. */

       char mname[] = "s_terminate";
       char wrd[256];
       int ret;

       logman("%s:enter", mname);

       if (command_words(comm) < 2)
          {
          (void)do_reply_code(TS_INVALID_FUNCTION);
          logman("%s:no password", mname);
          return(FALSE);
          }

       if (!command_word(comm, wrd, 2))
          {
          (void)do_reply_code(TS_INTERNAL_ERROR);
          logman("%s:error getting password", mname);
          return(FALSE);
          }

       if (!strcmp(wrd, TS_TERM_PASSWD))
          {
          (void)do_reply_code(TS_OK);
          return(TRUE);
          }

       logman("%s:illegal terminate request", mname);
       (void)do_reply_code(TS_ACCESS_DENIED);
       return(FALSE);
    }

    void s_get_datime(void)
    {
       /* Obtain, format the current date and time and send it back
          to the client. Note that the year and month are properly
          adjusted. */

       time_t tod;
       struct tm *tmbuf;
       char mname[] = "s_get_datime", buf[15];

       logman("%s:enter", mname);
       tod = time(NULL);
       tmbuf = localtime(&tod);
       sprintf(buf, "%d%02d%02d%02d%02d%02d", tmbuf->tm_year + 1900,
               tmbuf->tm_mon + 1, tmbuf->tm_mday, tmbuf->tm_hour,
               tmbuf->tm_min, tmbuf->tm_sec);
       (void)do_reply_char(TS_OK, buf);
       logman("%s:normal exit", mname);
    }

    void s_trans_num(void)
    {
       /* Get the current transaction count. Syntax:

             TS_SEND_TRANS_NUM */

       long count;

    #ifdef IPC_TCP
       count = ipc_trans_num();
    #else
       count = server_trans_num;
    #endif

       (void)do_reply_long(TS_OK, count);
    }

    void s_connect_num(void)
    {
       /* Get the current connection count. Syntax:

             TS_SEND_CONNECT_NUM */

    #ifdef IPC_TCP
       int count;
       count = ipc_connect_num();
       (void)do_reply_int(TS_OK, count);
    #else
       (void)do_reply_int(TS_NOT_IMPLEMENTED, 0);
    #endif
    }

    int do_reply_code(int reply_code)
    {
       /* Send a reply to a request which returns the reply code. Function
          returns 'TS_OK' upon success, an error code otherwise. */

       char reply_char[50], mname[] = "do_reply_code";

       memset(reply_char, 0, 50);
       sprintf(reply_char, "%d", reply_code);
       logman("%s:reply=%s,l=%d", mname, reply_char, strlen(reply_char));

       if (ipc_send_data(reply_char) == 0)
          {
          logman("%s:bad rc[0] from ipc_send_data", mname);
          return(TS_VC_ERROR);
          }

       return(TS_OK);
    }

    int do_reply_char(int reply_code, char *parm)
    {
       /* Send a reply to a request which returns the reply code and
          possibly a string. Function returns 'TS_OK' upon success,
          an error code otherwise. */

       char *reply, mname[] = "do_reply_char";

       if ((reply = (char *)malloc(TS_MAXCOMMAND)) == (char *)NULL)
          return(TS_MEMORY_FAIL);

       memset(reply, 0, TS_MAXCOMMAND);

       if (parm != (char *)NULL && strlen(parm))
          sprintf(reply, "%d %s", reply_code, parm);
       else
          sprintf(reply, "%d", reply_code);

       logman("%s:reply=%s,l=%d", mname, reply, strlen(reply));

       if (ipc_send_data(reply) == 0)
          {
          free(reply);
          logman("%s:bad rc[0] from ipc_send_data", mname);
          return(TS_VC_ERROR);
          }

       free(reply);
       return(TS_OK);
    }

    int do_reply_int(int reply_code, int parm)
    {
       /* Send a reply to a request which returns the reply code and
          an integer. Function returns 'TS_OK' upon success,
          an error code otherwise. */

       char reply[50], mname[] = "do_reply_int";

       memset(reply, 0, 50);
       sprintf(reply, "%d %d", reply_code, parm);
       logman("%s:reply=%s,l=%d", mname, reply, strlen(reply));

       if (ipc_send_data(reply) == 0)
          {
          logman("%s:bad rc[0] from ipc_send_data", mname);
          return(TS_VC_ERROR);
          }

       return(TS_OK);
    }

    int do_reply_long(int reply_code, long parm)
    {
       /* Send a reply to a request which returns the reply code and
          a long integer. Function returns 'TS_OK' upon success,
          an error code otherwise. */

       char reply[50], mname[] = "do_reply_long";

       memset(reply, 0, 50);
       sprintf(reply, "%d %ld", reply_code, parm);
       logman("%s:reply=%s,l=%d", mname, reply, strlen(reply));

       if (ipc_send_data(reply) == 0)
          {
          logman("%s:bad rc[0] from ipc_send_data", mname);
          return(TS_VC_ERROR);
          }

       return(TS_OK);
    }

    void term_app(void)
    {
       // Shutdown anything left open.

    #ifdef IPC_TCP
       (void)sloc_delete(server_port);
       sloc_term_api();
    #ifdef OS_WIN32
       WSACleanup();
    #endif
    #else
       (void)ipc_close();
    #endif

       logman("term_app:server down");

       if (logman_is_active())
          logman_end();
    }

    #ifdef IPC_TCP
    void usage(void)
    {
       /* Display program usage. */

       printf("usage:%s [%cq] [%cs] [%cl log_file] port\n", APNAME, SWITCH_CHAR,
              SWITCH_CHAR, SWITCH_CHAR);
    }
    #endif

/* [<][>][^][v][top][bottom][index][help] */