Fork me on GitHub
  ▖     
▃▀▄▄▖▄▄▖
 ▀▖     
        


#!/usr/bin/env python3
#
# usage: plash runopts -c CONTAINER [-d CHANGESDIR] [ -m MOUNT ] [CMD1 [ CMD2 ... ]]
#
# Run a container specifying lower level options.  Usually you'll want to use
# `plash run` instead. If no command is specified the containers default root
# shell will be executed.
#
# Supported options:
#
# -c CONTAINER
#        the container id to run
#
# -d CHANGESIDR
#        if specified changes to the root file system will be written there
#
# -m MOUNT
#        mount a path to the same location inside the container, can be
#        specified multiple times


ALWAYS_EXPORT = ['TERM', 'DISPLAY', 'HOME']


import getopt
import os
import re
import sys
import tempfile
from subprocess import CalledProcessError, check_call

from plash import utils
from plash.unshare import unshare_if_root, unshare_if_user
from plash.utils import (assert_initialized, catch_and_die, die,
                         handle_help_flag, mkdtemp)

handle_help_flag()
assert_initialized()

with utils.catch_and_die([getopt.GetoptError], debug='runopts'):
    user_opts, user_args = getopt.getopt(sys.argv[1:], 'c:d:m:')

#
# find out the mountpoint
#
if not os.getuid() and os.environ.get('PLASH_NO_UNSHARE'):
    mountpoint = tempfile.mkdtemp(dir='/var/tmp', prefix='plash-mountpoint-{}-'.format(os.getpid()))
else:
    mountpoint = os.path.join(utils.get_plash_data(), 'mnt')

#
# parse user options
#
container = None
changesdir = None
mounts = []
for opt_key, opt_value in user_opts:

    if opt_key == '-c':
        container = opt_value

    elif opt_key == '-d':
        changesdir = opt_value

    if opt_key == '-m':
        mounts.append(opt_value)

if not container:
    die('runopts: missing -c option')

#
# enter different namespace
#
unshare_if_root()
unshare_if_user()


#
# mount root filesystem
#
with catch_and_die([CalledProcessError], silent=True):
    check_call(['plash-mount', container, mountpoint] + (
        [changesdir] if changesdir else []))


#
# mount requested mounts
#
for mount in mounts:
    check_call([
        'mount', '--rbind', mount,
        os.path.join(mountpoint, mount.lstrip('/'))
    ])

#
# setup chroot and exec inside it
#

pwd_at_start = os.getcwd()

# I had problems opening the files after the chroot (LookupError: unknown encoding: ascii)

# read PATH from /etc/login.defs if available
envs = []
for env in os.environ.get('PLASH_EXPORT', '').split(':') + ALWAYS_EXPORT:
    if env:
        try:
            envs.append('{}={}'.format(env, os.environ[env]))
        except KeyError:
            pass

if not user_args:
    cmd = [utils.get_default_shell(os.path.join(mountpoint, 'etc/passwd'))]
else:
    cmd = user_args

os.chroot(mountpoint)
try:
    os.chdir(pwd_at_start)
except (ValueError, OSError):
    os.chdir('/')

with catch_and_die([OSError]):
    try:
        os.execve('/bin/sh', ['sh', '-lc', 'exec env "$@"', '--'] + envs + cmd, {})
    except FileNotFoundError:
        sys.stderr.write('{}: command not found\n'.format(cmd[0]))
        sys.exit(127)