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