/* A simple download server to demonstrate dpid.
 * It uses wget to download a link.  This has been tested with wget 1.8.1 
 * The server accepts multiple connections once it has been started.
 * If there are no requests within 5 minutes it waits for all child processes
 * to finish and then it exits.
 */
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#include <errno.h>
#include <sys/time.h>
#include <glib.h>

#define BUFLEN 256
#define TOUT 300

extern int errno;
pid_t origpid, fpid;

/*---------------------------------------------------------------------------*/

/*
 * Task: given a tag and an attribute name, return its value.
 *       (character stuffing is removed here)
 * Return value: the attribute value, or NULL if not present or malformed.
 * (copied from bookmarks.c)
 */
char *Get_attr_value(char *tag, int tagsize, char *attrname)
{
   char *p, *q, *ltag, quote, *start, *val = NULL;

   ltag = g_strndup(tag, tagsize);
   if ((p = strstr(ltag, attrname)) &&
       (p = strchr(p, '=')) && (p = strpbrk(p, "'\""))) {
      quote = *p;
      start = ++p;
      while ((q = strchr(p, quote)) && q[1] == quote)
         p = q + 2;
      if (q) {
         val = g_strndup(start, q - start);
         for (p = q = val; (*q = *p); ++p, ++q)
            if ((*p == '"' || *p == '\'') && p[1] == p[0])
               ++p;
      }
   }
   g_free(ltag);

   return val;
}

/*---------------------------------------------------------------------------*/

/*
 * SIGCHLD handler
 */
void sigchld(int sig)
{
   printf("downloads server %d: sigchld\n", origpid);
   fflush(stdout);
   while (waitpid(0, NULL, WNOHANG) > 0) {
   }
}

/*
 * Establish SIGCHLD handler
 */
void est_sigchld(void)
{
   struct sigaction act;
   sigset_t block;

   sigemptyset(&block);
   sigaddset(&block, SIGCHLD);
   act.sa_handler = sigchld;
   act.sa_mask = block;
   act.sa_flags = SA_NOCLDSTOP;
   sigaction(SIGCHLD, &act, NULL);
}

/*
 * Read a single line from a socket and store it in a GString.
 */
ssize_t readline(int socket, GString ** msg)
{
   ssize_t rdlen;
   char buf[BUFLEN], c = 0;
   ssize_t i = 0;

   buf[0] = '\0';

   for (i = 0;
        (rdlen = read(socket, &c, 1)) != 0 && c != '\n' && rdlen != -1; i++)
      g_string_append_c(*msg, c);

   if (rdlen == -1) {
      perror("downloads server:readline: read");
      exit(EXIT_FAILURE);
   }

   return i;
}


/*!
 * \bug Does not check cmd field for a valid command in dpi tag.
 * \bug Links are not updated when a download page is renamed.
 */
int main(void)
{
   int new_socket, ns;
   socklen_t csz;
   size_t dirlen = 0, srcf_len = 0, url_len = 0;
   ssize_t rdlen;
   struct sockaddr_un clnt_addr;

   //char *wget_cmd = "wget -r -p --no-parent -nc -k -nH --cut-dirs=30 -P";
   char *wget_cmd = "wget --no-parent -nc -k -nH --cut-dirs=30 -P";
   char *url = NULL, *dl_dest = NULL, *urlp = NULL, *cmd = NULL;
   GString *gs_wg_dest, *gs_dir, *gs_dl_cmd, *tag;
   fd_set active_set, selected_set;
   struct timeval tout;
   sigset_t blockSC;

   origpid = getpid();
   fpid = origpid;
   printf("\n\nDL_SRV PARENT %u: DOWNLOAD SERVER STARTED\n", origpid);
   fflush(stdout);
   sigemptyset(&blockSC);
   sigaddset(&blockSC, SIGCHLD);
   est_sigchld();

   csz = (socklen_t) sizeof(clnt_addr);
   FD_ZERO(&active_set);
   FD_SET(STDIN_FILENO, &active_set);
   while (1) {
      do {
         /* exit if there are no download requests after this time */
         tout.tv_sec = TOUT;
         tout.tv_usec = 0;
         selected_set = active_set;
         ns = select(STDIN_FILENO + 1, &selected_set, NULL, NULL, &tout);
      } while (ns == -1 && errno == EINTR);

      if (ns == -1) {
         perror("downloads server: select");
         exit(1);

      } else if (ns == 0) {     /* exit if no download requests */
         close(STDIN_FILENO);
         printf("downloads server %d:Terminating.\n"
                "Waiting for children to finish\n", origpid);
         fflush(stdout);
         /* BUG? Any further calls to downloads server will be queued by dpid
          * until all the children have finished.  This could be a long time */
         while (waitpid(WAIT_ANY, NULL, 0) >= 0) {
         }
         printf("\n\nDL_SRV %d: EXITING\n", origpid);
         fflush(stdout);
         exit(0);

      } else {
         /* accept the request */
         do {
            new_socket = accept(STDIN_FILENO, (struct sockaddr *) &clnt_addr,
                                &csz);
         } while (new_socket == -1 && errno == EINTR);

         if (new_socket == -1) {
            perror("downloads server: accept");
            exit(1);
         }
      }

      sigprocmask(SIG_BLOCK, &blockSC, NULL);
      tag = g_string_new(NULL);
      rdlen = readline(new_socket, &tag);
      if ((cmd = Get_attr_value(tag->str, tag->len, "cmd")) == NULL) {
         fprintf(stderr, "Failed to parse 'cmd' in %s\n", tag->str);
         exit(1);
      }
      if (strcmp(cmd, "DpiBye") == 0) {
         printf("downloads dpi: Got DpiBye, terminating.\n");
         exit(0);
      }
      fpid = fork();
      if (fpid == 0) {
         pid_t ppid, cpid;

         origpid = cpid = getpid();
         ppid = getppid();
         printf("\n\ndownloads server child %u: Started- Parent=%d\n", cpid,
                ppid);
         fflush(stdout);
         if ((url = Get_attr_value(tag->str, tag->len, "url")) == NULL) {
            fprintf(stderr, "Failed to parse 'url' in %s\n", tag->str);
            exit(1);
         }
         if ((dl_dest =
              Get_attr_value(tag->str, tag->len, "destination")) == NULL) {
            fprintf(stderr, "Failed to parse 'destination' in %s\n", tag->str);
            exit(1);
         }

         printf("downloads server child %u:parent=%u, url = %s\n", cpid, ppid,
                url);
         fflush(stdout);
         printf("downloads server child %u:parent=%u, dl_dest = %s\n", cpid,
                ppid, dl_dest);
         fflush(stdout);

         url_len = strlen(url);
         for (urlp = &url[url_len], srcf_len = 0;
              srcf_len < url_len && *urlp != '/'; srcf_len++, urlp--);

         for (dirlen = strlen(dl_dest);
              dirlen != 0 && dl_dest[dirlen] != '/'; dirlen--);
         gs_dir = g_string_new(dl_dest);
         g_string_truncate(gs_dir, dirlen);
         gs_wg_dest = g_string_new(NULL);
         g_string_sprintfa(gs_wg_dest, "%s%s", gs_dir->str, urlp);
         gs_dl_cmd = g_string_new(NULL);
         g_string_sprintfa(gs_dl_cmd, "%s%s %s", wget_cmd, gs_dir->str, url);

         g_free(url);
         url = NULL;
         g_string_free(gs_dir, TRUE);
         gs_dir = NULL;

         printf("downloads server child %u:parent=%u, Running: %s\n", cpid,
                ppid, gs_dl_cmd->str);
         fflush(stdout);
         system(gs_dl_cmd->str);

         if (strcmp(gs_wg_dest->str, dl_dest)) {
            printf
                ("downloads server child %u:parent=%u, Renaming\n%s\nto\n%s\n",
                 cpid, ppid, gs_wg_dest->str, dl_dest);
            fflush(stdout);
            rename(gs_wg_dest->str, dl_dest);
         }

         g_string_free(gs_dl_cmd, TRUE);
         gs_dl_cmd = NULL;
         g_free(dl_dest);
         dl_dest = NULL;
         g_string_free(gs_wg_dest, TRUE);
         gs_wg_dest = NULL;

         if (close(new_socket) == -1) {
            perror("downloads server: close");
            exit(EXIT_FAILURE);
         }

         printf("downloads server child %u:parent=%u, Child finished\n", cpid,
                ppid);
         fflush(stdout);
         exit(0);
      }
      g_string_free(tag, TRUE);
      sigprocmask(SIG_UNBLOCK, &blockSC, NULL);
      close(new_socket);
   }
}
