Fork me on GitHub

plash run


Usage

plash run IMAGE_ID [ CMD1 [ CMD2 ... ] ]

Description

Run a container. If no command is specified, the containers default root
shell is executed.

The following host file systems are mapped to the container:
- /tmp
- /home
- /root
- /etc/resolv.conf
- /sys
- /dev
- /proc
- /host (contains entire host filesystem)

Tested Behaviour

#!/bin/sh
set -xeu

• run a program and capture stdout

out=$(plash run 1 echo hellow)
test "$out" = hellow

• container is writeable

plash run 1 touch /a

• can be used with the `b` command

plash b run -f 1 --invalidate-layer -- ls

• no command arguments spawn a shell

test "$(echo 'echo iamshell' | plash run 1)" = iamshell

Source Code


#define USAGE "usage: plash run IMAGE_ID [ CMD1 [ CMD2 ... ] ]\n"

#define _GNU_SOURCE
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include <plash.h>

void read_envs_from_plashenv() {
  char *line = NULL;
  char *lineCopy = NULL;
  size_t len = 0;
  FILE *fp = fopen(".plashenvs", "r");
  if (fp == NULL)
    return;
  while ((getline(&line, &len, fp)) != -1) {
    line[strcspn(line, "\n")] = 0; // chop newline char
    lineCopy = strdup(line);
    if (!lineCopy) {
      pl_fatal("strdup");
    }
    pl_whitelist_env(lineCopy);
  }
  fclose(fp);
}

void read_envs_from_plashenvprefix() {
  char *line = NULL;
  char *lineCopy = NULL;
  size_t len = 0;
  FILE *fp = fopen(".plashenvsprefix", "r");
  if (fp == NULL)
    return;
  while ((getline(&line, &len, fp)) != -1) {
    line[strcspn(line, "\n")] = 0; // chop newline char
    lineCopy = strdup(line);
    if (!lineCopy) {
      pl_fatal("strdup");
    }
    pl_whitelist_env_prefix(lineCopy);
  }
  fclose(fp);
}

void read_mounts_from_plashmounts() {
  char *line = NULL;
  char *lineCopy = NULL;
  size_t len = 0;
  char *mount;
  FILE *fp = fopen(".plashmounts", "r");
  if (fp == NULL)
    return;
  while ((getline(&line, &len, fp)) != -1) {
    line[strcspn(line, "\n")] = 0; // chop newline char
    lineCopy = strdup(line);
    if (!lineCopy) {
      pl_fatal("strdup");
    }
    mount = strsep(&lineCopy, ":");
    errno = 0;
    if (mount[0] != '/')
      pl_fatal("src mount in /.plashmounts must start with a slash");
    if (lineCopy == NULL) {
      pl_bind_mount(mount, mount + 1);
    } else {
      pl_bind_mount(mount, lineCopy + 1);
      errno = 0;
      if (lineCopy[0] != '/')
        pl_fatal("dst mount in /.plashmounts must start with a slash");
    }
  }
  fclose(fp);
}

int run_main(int argc, char *argv[]) {

  if (argc < 2) {
    fputs(USAGE, stderr);
    return EXIT_FAILURE;
  }
  char *container_id = argv[1];
  char *origpwd = get_current_dir_name();
  char *plash_data = pl_call("data");
  char *changesdir = pl_call("mkdtemp");

  //
  // get "userspace root"
  //
  pl_unshare_user();
  pl_unshare_mount();

  //
  // prepare an empty mountpoint
  //
  if (chdir(plash_data) == -1)
    pl_fatal("chdir");
  if (mount("tmpfs", "mnt", "tmpfs", MS_MGC_VAL, NULL) == -1)
    pl_fatal("mount");

  //
  // mount root filesystem at the empty mountpoint
  //
  pl_call("mount", container_id, "mnt", changesdir);

  //
  // mount
  //
  if (chdir("mnt") == -1)
    pl_fatal("chdir");
  pl_bind_mount("/tmp", "tmp");
  pl_bind_mount("/home", "home");
  pl_bind_mount("/root", "root");
  pl_bind_mount("/sys", "sys");
  pl_bind_mount("/dev", "dev");
  pl_bind_mount("/proc", "proc");

  // ensure /etc/resolv.conf is a normal file. Because if it where a symlink,
  // mounting over it would not work as expected
  unlink("etc/resolv.conf");
  int fd;
  if ((fd = open("etc/resolv.conf", O_CREAT | O_WRONLY, 0775)) < 0)
    pl_fatal("open");
  close(fd);
  pl_bind_mount("/etc/resolv.conf", "etc/resolv.conf");

  read_mounts_from_plashmounts();

  //
  // Import envs
  //
  pl_whitelist_env("TERM");
  pl_whitelist_env("DISPLAY");
  pl_whitelist_env("PLASH_DATA");
  pl_whitelist_envs_from_env("PLASH_EXPORT");
  read_envs_from_plashenv();
  read_envs_from_plashenvprefix();
  pl_whitelist_env(NULL);

  //
  // chroot, then reconstruct working directory
  //
  chroot(".") != -1 || pl_fatal("chroot");
  pl_chdir(origpwd);

  //
  // build up the arguments to run
  //
  char **run_args = argv + 2;
  if (*run_args == NULL) {
    run_args = (char *[]){pl_get_default_root_shell(), "-l", NULL};
  }

  //
  // exec!
  //
  execvp(*run_args, run_args);
  if (errno == ENOENT) {
    fprintf(stderr, "%s: command not found\n", *run_args);
    return 127;
  }
  pl_fatal("execvp");
  return EXIT_FAILURE;
}