github.com/schwarzm/garden-linux@v0.0.0-20150507151835-33bca2147c47/old/linux_backend/src/wsh/wsh.c (about)

     1  #define _GNU_SOURCE
     2  
     3  #include <assert.h>
     4  #include <errno.h>
     5  #include <fcntl.h>
     6  #include <signal.h>
     7  #include <stdio.h>
     8  #include <stdlib.h>
     9  #include <string.h>
    10  #include <sys/ioctl.h>
    11  #include <termios.h>
    12  #include <unistd.h>
    13  
    14  #include "msg.h"
    15  #include "pump.h"
    16  #include "un.h"
    17  
    18  typedef struct wsh_s wsh_t;
    19  
    20  struct wsh_s {
    21    /* Path and args to execute */
    22    int argc;
    23    char **argv;
    24  
    25    /* Environment variables for running process */
    26    char **environment_variables;
    27    size_t environment_variable_count;
    28  
    29    /* Path to socket */
    30    const char *socket_path;
    31  
    32    /* User to change to */
    33    const char *user;
    34  
    35    /* Working directory of process */
    36    const char *dir;
    37  
    38    /* File to save container-namespaced pid of spawned process in to */
    39    const char *pid_file;
    40  };
    41  
    42  int wsh__usage(wsh_t *w) {
    43    fprintf(stderr, "Usage: %s OPTION...\n", w->argv[0]);
    44    fprintf(stderr, "\n");
    45  
    46    fprintf(stderr, "  --socket PATH   "
    47      "Path to socket"
    48      "\n");
    49  
    50    fprintf(stderr, "  --user USER     "
    51      "User to change to"
    52      "\n");
    53  
    54    fprintf(stderr, "  --env KEY=VALUE "
    55      "Environment variables to set for the command. "
    56      "You can specify multiple --env arguments"
    57      "\n");
    58  
    59    fprintf(stderr, "  --dir PATH      "
    60      "Working directory for the running process"
    61      "\n");
    62  
    63    fprintf(stderr, "  --pidfile PIDFILE      "
    64      "File to save container-namespaced pid of spawned process to"
    65      "\n");
    66  
    67    fprintf(stderr, "  --rsh           "
    68      "RSH compatibility mode"
    69      "\n");
    70    return 0;
    71  }
    72  
    73  int wsh__getopt(wsh_t *w) {
    74    int i = 1;
    75    int j = w->argc - i;
    76  
    77    w->pid_file = 0;
    78  
    79    while (i < w->argc) {
    80      if (w->argv[i][0] != '-') {
    81        break;
    82      }
    83  
    84      if (j >= 1 && ((strcmp(w->argv[i], "-h") == 0) || (strcmp(w->argv[i], "--help") == 0))) {
    85        wsh__usage(w);
    86        return -1;
    87      } else if (j >= 2 && strcmp(w->argv[i], "--socket") == 0) {
    88        w->socket_path = strdup(w->argv[i+1]);
    89        i += 2;
    90        j -= 2;
    91      } else if (j >= 2 && strcmp(w->argv[i], "--user") == 0) {
    92        w->user = strdup(w->argv[i+1]);
    93        i += 2;
    94        j -= 2;
    95      } else if (j >= 2 && strcmp(w->argv[i], "--dir") == 0) {
    96        w->dir = strdup(w->argv[i+1]);
    97        i += 2;
    98        j -= 2;
    99      } else if (j >= 2 && strcmp(w->argv[i], "--pidfile") == 0) {
   100        w->pid_file = strdup(w->argv[i+1]);
   101        i += 2;
   102        j -= 2;
   103      } else if (j >= 2 && strcmp(w->argv[i], "--env") == 0) {
   104        w->environment_variable_count++;
   105        w->environment_variables = realloc(w->environment_variables, w->environment_variable_count * sizeof(char *));
   106        w->environment_variables[w->environment_variable_count - 1] = strdup(w->argv[i+1]);
   107        i += 2;
   108        j -= 2;
   109      } else if (j >= 1 && strcmp(w->argv[i], "--rsh") == 0) {
   110        i += 1;
   111        j -= 1;
   112  
   113        /* rsh [-46dn] [-l username] [-t timeout] host [command] */
   114        while (i < w->argc) {
   115          if (w->argv[i][0] != '-') {
   116            break;
   117          }
   118  
   119          if (j >= 1 && strlen(w->argv[i]) == 2 && strchr("46dn", w->argv[i][1])) {
   120            /* Ignore */
   121            i += 1;
   122            j -= 1;
   123          } else if (j >= 2 && strlen(w->argv[i]) == 2 && w->argv[i][1] == 'l') {
   124            w->user = strdup(w->argv[i+1]);
   125            i += 2;
   126            j -= 2;
   127          } else if (j >= 2 && strlen(w->argv[i]) == 2 && w->argv[i][1] == 't') {
   128            /* Ignore */
   129            i += 2;
   130            j -= 2;
   131          } else {
   132            goto invalid;
   133          }
   134        }
   135  
   136        /* Skip over host */
   137        assert(i < w->argc);
   138        i += 1;
   139        j -= 1;
   140      } else {
   141        goto invalid;
   142      }
   143    }
   144  
   145    w->argc = w->argc - i;
   146    if (w->argc) {
   147      w->argv = &w->argv[i];
   148    } else {
   149      w->argv = NULL;
   150    }
   151  
   152    return 0;
   153  
   154  invalid:
   155    fprintf(stderr, "%s: invalid option -- %s\n", w->argv[0], w->argv[i]);
   156    fprintf(stderr, "Try `%s --help' for more information.\n", w->argv[0]);
   157    return -1;
   158  }
   159  
   160  static pid_t pid;
   161  
   162  void cleanup_pidfile(const char *pidfile, int pid_fd) {
   163    int rv;
   164  
   165    if (pidfile) {
   166      rv = unlink(pidfile);
   167      if (rv != 0) {
   168        perror("unlink pidfile");
   169        exit(255);
   170      }
   171    }
   172  }
   173  
   174  void pump_loop(const char *pid_file, pump_t *p, int pid_fd, int exit_status_fd, pump_pair_t *pp, int pplen) {
   175    int i, rv, pidfd;
   176    char pidstr[10];
   177  
   178    rv = read(pid_fd, &pid, sizeof(pid));
   179    assert(rv >= 0);
   180  
   181    if (pid_file) {
   182      pidfd = open(pid_file, O_RDWR|O_CREAT, 0600);
   183      if (pidfd == -1 ) {
   184        perror("open pidfile");
   185        exit(1);
   186      }
   187  
   188      sprintf(pidstr, "%d\n", pid);
   189      write(pidfd, pidstr, strlen(pidstr));
   190  
   191      rv = close(pid_fd);
   192      if (rv != 0) {
   193        perror("close pidfile");
   194      }
   195    }
   196  
   197    for (;;) {
   198      pump_init(p);
   199  
   200      for (i = 0; i < pplen; i++) {
   201        pump_add_pair(p, &pp[i]);
   202      }
   203  
   204      if (exit_status_fd >= 0) {
   205        pump_add_fd(p, exit_status_fd, PUMP_READ | PUMP_EXCEPT);
   206      }
   207  
   208      do {
   209        rv = pump_select(p);
   210      } while (rv == -1 && errno == EINTR);
   211  
   212      if (rv == -1) {
   213        perror("select");
   214        cleanup_pidfile(pid_file, pidfd);
   215        abort();
   216      }
   217  
   218      for (i = 0; i < pplen; i++) {
   219        pump_pair_copy(&pp[i]);
   220      }
   221  
   222      if (pump_ready(p, exit_status_fd, PUMP_READ | PUMP_EXCEPT)) {
   223        int status;
   224  
   225        rv = read(exit_status_fd, &status, sizeof(status));
   226        assert(rv >= 0);
   227  
   228        /* One more splice to make sure kernel buffers are emptied */
   229        for (i = 0; i < pplen; i++) {
   230          pump_pair_copy(&pp[i]);
   231        }
   232  
   233        if (rv == 0) {
   234          /* EOF: process terminated by signal */
   235          cleanup_pidfile(pid_file, pidfd);
   236          exit(255);
   237        }
   238  
   239        assert(rv == sizeof(status));
   240        cleanup_pidfile(pid_file, pidfd);
   241        exit(status);
   242      }
   243    }
   244  }
   245  
   246  static int pty_local_fd, pty_remote_fd;
   247  static struct termios told, tnew;
   248  static struct winsize wsz;
   249  
   250  void tty_reset(void) {
   251    int rv;
   252  
   253    rv = tcsetattr(pty_local_fd, TCSANOW, &told);
   254    assert(rv != -1);
   255  }
   256  
   257  void tty__atexit(void) {
   258    tty_reset();
   259  }
   260  
   261  void tty_raw(void) {
   262    int rv;
   263  
   264    rv = tcgetattr(pty_local_fd, &told);
   265    assert(rv != -1);
   266  
   267    rv = atexit(tty__atexit);
   268    assert(rv != -1);
   269  
   270    tnew = told;
   271    cfmakeraw(&tnew);
   272  
   273    rv = tcsetattr(pty_local_fd, TCSANOW, &tnew);
   274    assert(rv != -1);
   275  }
   276  
   277  void tty_gwinsz(void) {
   278    int rv;
   279  
   280    rv = ioctl(pty_local_fd, TIOCGWINSZ, &wsz);
   281    assert(rv != -1);
   282  }
   283  
   284  void tty_swinsz(void) {
   285    int rv;
   286  
   287    rv = ioctl(pty_remote_fd, TIOCSWINSZ, &wsz);
   288    assert(rv != -1);
   289  }
   290  
   291  void tty__sigwinch(int sig) {
   292    tty_gwinsz();
   293    tty_swinsz();
   294  }
   295  
   296  void tty_winsz(void) {
   297    sighandler_t s;
   298  
   299    /* Setup handler for window size */
   300    s = signal(SIGWINCH, tty__sigwinch);
   301    assert(s != SIG_ERR);
   302  
   303    /* Figure out window size and forward it to the remote pty */
   304    tty_gwinsz();
   305    tty_swinsz();
   306  }
   307  
   308  void loop_interactive(const char *pidfile, int fd) {
   309    msg_response_t res;
   310    int fds[3];
   311    size_t fdslen = sizeof(fds)/sizeof(fds[0]);
   312    int rv;
   313  
   314    rv = un_recv_fds(fd, (char *)&res, sizeof(res), fds, fdslen);
   315    if (rv <= 0) {
   316      perror("recvmsg");
   317      exit(255);
   318    }
   319  
   320    assert(rv == sizeof(res));
   321  
   322    pty_remote_fd = fds[0];
   323    pty_local_fd = STDIN_FILENO;
   324  
   325    tty_raw();
   326    tty_winsz();
   327  
   328    pump_t p;
   329    pump_pair_t pp[2];
   330  
   331    /* Use duplicates to decouple input/output */
   332    pump_pair_init(&pp[0], &p, STDIN_FILENO, dup(fds[0]));
   333    pump_pair_init(&pp[1], &p, dup(fds[0]), STDOUT_FILENO);
   334  
   335    pump_loop(pidfile, &p, fds[2], fds[1], pp, 2);
   336  }
   337  
   338  void loop_noninteractive(const char* pidfile, int fd) {
   339    msg_response_t res;
   340    int fds[5];
   341    size_t fdslen = sizeof(fds)/sizeof(fds[0]);
   342    int rv;
   343  
   344    rv = un_recv_fds(fd, (char *)&res, sizeof(res), fds, fdslen);
   345    if (rv <= 0) {
   346      perror("recvmsg");
   347      exit(255);
   348    }
   349  
   350    assert(rv == sizeof(res));
   351  
   352    pump_t p;
   353    pump_pair_t pp[3];
   354  
   355    pump_pair_init(&pp[0], &p, STDIN_FILENO, fds[0]);
   356    pump_pair_init(&pp[1], &p, fds[1], STDOUT_FILENO);
   357    pump_pair_init(&pp[2], &p, fds[2], STDERR_FILENO);
   358  
   359    pump_loop(pidfile, &p, fds[4], fds[3], pp, 3);
   360  }
   361  
   362  int main(int argc, char **argv) {
   363    wsh_t *w;
   364    int rv;
   365    int fd;
   366    msg_request_t req;
   367  
   368    signal(SIGPIPE, SIG_IGN);
   369  
   370    w = calloc(1, sizeof(*w));
   371    assert(w != NULL);
   372  
   373    w->argc = argc;
   374    w->argv = argv;
   375  
   376    rv = wsh__getopt(w);
   377    if (rv == -1) {
   378      exit(1);
   379    }
   380  
   381    if (w->socket_path == NULL) {
   382      w->socket_path = "run/wshd.sock";
   383    }
   384  
   385    rv = un_connect(w->socket_path);
   386    if (rv < 0) {
   387      perror("connect");
   388      exit(255);
   389    }
   390  
   391    fd = rv;
   392  
   393    msg_request_init(&req);
   394  
   395    msg_dir_import(&req.dir, w->dir);
   396  
   397    if (isatty(STDIN_FILENO)) {
   398      req.tty = 1;
   399    } else {
   400      req.tty = 0;
   401    }
   402  
   403    rv = msg_array_import(&req.arg, w->argc, (const char **)w->argv);
   404    if (rv == -1) {
   405      fprintf(stderr, "msg_import_array: Too much data in args\n");
   406      exit(255);
   407    }
   408  
   409    rv = msg_array_import(&req.env, w->environment_variable_count, (const char **)w->environment_variables);
   410    if (rv == -1) {
   411      fprintf(stderr, "msg_import_array: Too much data in environment variables\n");
   412      exit(255);
   413    }
   414  
   415    rv = msg_rlimit_import(&req.rlim);
   416    if (rv == -1) {
   417      fprintf(stderr, "msg_rlimit_import: %s\n", strerror(errno));
   418      exit(255);
   419    }
   420  
   421    rv = msg_user_import(&req.user, w->user);
   422    if (rv == -1) {
   423      fprintf(stderr, "msg_user_import: %s\n", strerror(errno));
   424      exit(255);
   425    }
   426  
   427    rv = un_send_fds(fd, (char *)&req, sizeof(req), NULL, 0);
   428    if (rv <= 0) {
   429      perror("sendmsg");
   430      exit(255);
   431    }
   432  
   433    if (req.tty) {
   434      loop_interactive(w->pid_file, fd);
   435    } else {
   436      loop_noninteractive(w->pid_file, fd);
   437    }
   438  
   439    perror("unreachable");
   440    exit(255);
   441  }