priv/execdaemon/execdaemon.c
author Tom Parker <palfrey@lshift.net>
Fri Dec 03 12:19:42 2010 +0000 (17 months ago)
changeset 278 18023c8db394
parent 58 5dc840385303
permissions -rw-r--r--
Handle authentication errors in lastfm:scrobble
     1 #include <stdlib.h>
     2 #include <string.h>
     3 #include <stdio.h>
     4 #include <errno.h>
     5 #include <signal.h>
     6 #include <fcntl.h>
     7 #include <ctype.h>
     8 
     9 #include <sys/types.h>
    10 #include <sys/wait.h>
    11 #include <sys/time.h>
    12 #include <unistd.h>
    13 
    14 static pid_t kid = 0;
    15 
    16 static int want_debug = 0;
    17 
    18 static char *command = NULL;
    19 static int command_buflen = 0;
    20 static int command_length = 0;
    21 static int current_request = 0;
    22 
    23 static char *program_name = NULL;
    24 static int arg_count = 0;
    25 static char **arg_list = NULL;
    26 
    27 #define COMMAND_TERMINATOR '\0'
    28 
    29 static struct SignalTable {
    30   char *sig_name;
    31   int sig_num;
    32 } signal_table[] = {
    33   { "HUP", SIGHUP },
    34   { "INT", SIGINT },
    35   { "QUIT", SIGQUIT },
    36   { "ILL", SIGILL },
    37   { "TRAP", SIGTRAP },
    38   { "ABRT", SIGABRT },
    39   { "IOT", SIGIOT },
    40   { "BUS", SIGBUS },
    41   { "FPE", SIGFPE },
    42   { "KILL", SIGKILL },
    43   { "USR1", SIGUSR1 },
    44   { "SEGV", SIGSEGV },
    45   { "USR2", SIGUSR2 },
    46   { "PIPE", SIGPIPE },
    47   { "ALRM", SIGALRM },
    48   { "TERM", SIGTERM },
    49   { "CHLD", SIGCHLD },
    50   { "CONT", SIGCONT },
    51   { "STOP", SIGSTOP },
    52   { "TSTP", SIGTSTP },
    53   { "TTIN", SIGTTIN },
    54   { "TTOU", SIGTTOU },
    55   { "URG", SIGURG },
    56   { "XCPU", SIGXCPU },
    57   { "XFSZ", SIGXFSZ },
    58   { "VTALRM", SIGVTALRM },
    59   { "PROF", SIGPROF },
    60   { "WINCH", SIGWINCH },
    61   { "IO", SIGIO },
    62   { NULL, 0 }
    63 };
    64 
    65 void append_command_char(int ch) {
    66   if (command_length >= command_buflen) {
    67     int delta = 1024;
    68     command = realloc(command, command_buflen + delta);
    69     command_buflen += delta;
    70   }
    71 
    72   command[command_length++] = ch;
    73 }
    74 
    75 int read_command(void) {
    76   int ch;
    77   command_length = 0;
    78   while (1) {
    79     ch = fgetc(stdin);
    80     if ((ch == EOF) || (ch == COMMAND_TERMINATOR)) {
    81       break;
    82     }
    83     append_command_char(ch);
    84   }
    85   append_command_char(0);
    86   command_length--; /* don't count the trailing nul */
    87   return command_length;
    88 }
    89 
    90 void write_response(char *code, char *arg) {
    91   if (want_debug) fprintf(stderr, "response: %d:%s,%s\n", current_request, code, arg);
    92   fprintf(stdout, "%d:%s,%s", current_request, code, arg);
    93   fputc(COMMAND_TERMINATOR, stdout);
    94   fflush(stdout);
    95 }
    96 
    97 char *num_buf(int n) {
    98   static char buf[32];
    99   sprintf(buf, "%d", n);
   100   return buf;
   101 }
   102 
   103 void errno_response() {
   104   int e = errno;
   105   write_response("errno", num_buf(e));
   106 }
   107 
   108 void handle_program(char *cmd, char *arg) {
   109   if (program_name != NULL) {
   110     free(program_name);
   111   }
   112   program_name = strdup(arg);
   113   write_response("ok", arg);
   114 }
   115 
   116 void handle_argc(char *cmd, char *arg) {
   117   int i;
   118   if (arg_count > 0) {
   119     for (i = 0; i < arg_count; i++) {
   120       if (arg_list[i] != NULL) {
   121 	free(arg_list[i]);
   122       }
   123     }
   124     free(arg_list);
   125   }
   126 
   127   arg_count = (int) strtoul(arg, NULL, 10);
   128 
   129   /* Allocate arg_count+2 slots because execv wants a
   130      NULL-terminated array and we copy the program name into the 0th
   131      arg slot before execing */
   132   arg_list = calloc(arg_count + 2, sizeof(char *));
   133   write_response("ok", arg);
   134 }
   135 
   136 void handle_arg(char *cmd, char *arg) {
   137   char *val = strchr(arg, ',');
   138   int index;
   139   if (arg == NULL) {
   140     val = "";
   141   } else {
   142     *val = '\0';
   143     val++;
   144   }
   145   index = (int) strtoul(arg, NULL, 10);
   146   if (index < 0 || index >= arg_count) {
   147     write_response("bad", arg);
   148   } else {
   149     if (arg_list[index + 1] != NULL) {
   150       free(arg_list[index + 1]);
   151     }
   152     arg_list[index + 1] = strdup(val);
   153     write_response("ok", arg);
   154   }
   155 }
   156 
   157 void handle_execv(char *cmd, char *arg) {
   158   if (kid != 0) {
   159     write_response("bad", "already");
   160     return;
   161   }
   162 
   163   if (command_length == 0) {
   164     write_response("bad", "command");
   165     return;
   166   }
   167 
   168   if (arg_list == NULL) {
   169     write_response("bad", "args");
   170     return;
   171   }
   172 
   173   arg_list[0] = strdup(program_name);
   174 
   175   kid = fork();
   176   if (kid == 0) {
   177     int nullfd = open("/dev/null", O_RDWR, 0777);
   178     close(0); dup2(nullfd, 0);
   179     close(1); dup2(nullfd, 1);
   180     close(2); dup2(nullfd, 2);
   181     close(nullfd);
   182     if (execv(program_name, arg_list) == -1) {
   183       exit(1);
   184     }
   185   } else {
   186     write_response("pid", num_buf((int) kid));
   187   }
   188 }
   189 
   190 int lookup_signal(char *name) {
   191   struct SignalTable *entry;
   192   for (entry = &signal_table[0]; entry->sig_name != NULL; entry++) {
   193     if (!strcasecmp(name, entry->sig_name)) {
   194       return entry->sig_num;
   195     }
   196   }
   197   return 0;
   198 }
   199 
   200 void handle_sendsig(char *cmd, char *arg) {
   201   int signum;
   202   if (isdigit(arg[0])) {
   203     signum = (int) strtoul(arg, NULL, 10);
   204   } else {
   205     signum = lookup_signal(arg);
   206   }
   207 
   208   if (signum == 0) {
   209     write_response("bad", "invalid");
   210     return;
   211   }
   212 
   213   if (kid == 0) {
   214     write_response("bad", "not_running");
   215     return;
   216   }
   217 
   218   if (kill(kid, signum) == 0) {
   219     write_response("ok","");
   220   } else {
   221     errno_response();
   222   }
   223 }
   224 
   225 struct CommandTableEntry {
   226   char *name;
   227   void (*handler)(char *cmd, char *arg);
   228 } cmd_table[] = {
   229   {"program", handle_program},
   230   {"argc", handle_argc},
   231   {"arg", handle_arg},
   232   {"execv", handle_execv},
   233   {"sendsig", handle_sendsig},
   234   {NULL, NULL}
   235 };
   236 
   237 void eval_command(void) {
   238   char *reqnumstr, *cmd, *arg;
   239   struct CommandTableEntry *cte;
   240 
   241   reqnumstr = command;
   242   cmd = strchr(command, ':');
   243   if (cmd == NULL) {
   244     exit(1);
   245   } else {
   246     *cmd = '\0';
   247     cmd++;
   248   }
   249 
   250   current_request = (int) strtoul(reqnumstr, NULL, 10);
   251   if (current_request == 0) {
   252     exit(1);
   253   }
   254 
   255   arg = strchr(cmd, ',');
   256   if (arg == NULL) {
   257     arg = "";
   258   } else {
   259     *arg = '\0';
   260     arg++;
   261   }
   262 
   263   if (want_debug) fprintf(stderr, "command: %d:%s,%s\n", current_request, cmd, arg);
   264 
   265   for (cte = &cmd_table[0]; cte->name != NULL; cte++) {
   266     if (!strcmp(cte->name, cmd)) {
   267       cte->handler(cmd, arg);
   268       current_request = 0;
   269       return;
   270     }
   271   }
   272 
   273   write_response("bad_command", cmd);
   274   current_request = 0;
   275 }
   276 
   277 int main(int argc, char *argv[]) {
   278   fd_set fds;
   279   int keep_going = 1;
   280 
   281   want_debug = (argc > 1) && (!strcmp(argv[1], "-debug"));
   282 
   283   if (want_debug) fprintf(stderr, "execdaemon starting.\n");
   284 
   285   while (keep_going) {
   286     struct timeval timeout;
   287     timeout.tv_sec = 0;
   288     timeout.tv_usec = 100000; /* 100 ms */
   289 
   290     FD_ZERO(&fds);
   291     FD_SET(0, &fds);
   292 
   293     switch (select(1, &fds, NULL, NULL, &timeout)) {
   294       case 1: {
   295 	if (!read_command()) {
   296 	  keep_going = 0;
   297 	} else {
   298 	  eval_command();
   299 	}
   300 	break;
   301       }
   302 
   303       case 0:
   304 	break;
   305 
   306       case -1:
   307 	perror("select");
   308 	exit(1);
   309     }
   310 
   311     if (kid != 0) {
   312       int pidstatus;
   313       pid_t result = waitpid(kid, &pidstatus, WNOHANG);
   314       if (result != 0) {
   315 	if (WIFEXITED(pidstatus)) {
   316 	  write_response("exit", num_buf(WEXITSTATUS(pidstatus)));
   317 	} else if (WIFSIGNALED(pidstatus)) {
   318 	  write_response("signal", num_buf(WTERMSIG(pidstatus)));
   319 	} else {
   320 	  write_response("died", "");
   321 	}
   322 	kid = 0;
   323       }
   324     }
   325   }
   326 
   327   if (want_debug) fprintf(stderr, "execdaemon exiting.\n");
   328   return 0;
   329 }