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


#!/usr/bin/env python3
#
# usage: plash mount CONTAINER MOUNTPOINT [ CHANGESDIR ]
#
# Mount a container-filsystem. To cleanup you need to unmount it manually.
# Changes to the filesystem will be written to CHANGESDIR, if ommited a
# temporary directory will be used.
#
# Parameters may be interpreted as build instruction.

import os
import shutil
import sys
from os.path import join
from subprocess import CalledProcessError, check_call

from plash.utils import (assert_initialized, catch_and_die, die,
                         die_with_usage, get_plash_data, handle_build_args,
                         handle_help_flag, nodepath_or_die)

handle_help_flag()
handle_build_args()
assert_initialized()

try:
    container, mountpoint = sys.argv[1:3]
    if len(sys.argv) >= 4:
        changedir = sys.argv[3]
    else:
        changedir = None
except ValueError:
    die_with_usage()

nodepath = nodepath_or_die(container)

container_ids_path = []
parts = nodepath.split('/')
while True:
    pop = parts.pop()
    container_ids_path.append(pop)
    if pop == '0':
        break

# use the symlinks and not the full paths because the arg size is limited
# On my setup i get 58 (EDIT: should be more now) layers before an error,
# we could have multiple mount calls to overcome this
plash_data = get_plash_data()
lowerdir_list = [
    join(plash_data, 'index', i, '_data', 'root') for i in container_ids_path
]


def mount_unionfs(lowerdir_list, mountpoint, changedir):
    lowerdirs_str = ':'.join('{}=RO'.format(i) for i in lowerdir_list)
    if changedir:
        upperdir = os.path.join(changedir, 'data')
        os.makedirs(upperdir, exist_ok=True)
        upperdir_str = '{}=RW:'.format(upperdir)
    else:
        upperdir_str = ''

    unionfs = shutil.which('unionfs') or shutil.which('unionfs-fuse') or die(
        'unionfs-fuse seems not to be installed')
    with catch_and_die([CalledProcessError]):
        check_call([
            unionfs, '-o', 'cow', '{upperdir}{lowerdirs}'.format(
                lowerdirs=lowerdirs_str, upperdir=upperdir_str), mountpoint
        ])


def mount_overlay(lowerdirs_list, mountpoint, changedir):
    if changedir:
        workdir = os.path.join(changedir, 'work')
        upperdir = os.path.join(changedir, 'data')
        os.makedirs(workdir, exist_ok=True)
        os.makedirs(upperdir, exist_ok=True)
    else:
        workdir = None
        upperdir = None
    with catch_and_die([CalledProcessError]):
        check_call([
            'mount', '-t', 'overlay', 'overlay', '-o',
            'lowerdir={lowerdir}{workdir}{upperdir}'.format(
                lowerdir=':'.join(lowerdir_list),
                upperdir=',upperdir=' + upperdir if upperdir else '',
                workdir=',workdir=' + workdir if workdir else ''), mountpoint
        ])


def check_mount_option_part(dir):
    if dir and not dir.replace('.', '').replace('/', '').replace(
            '_', '').replace('-', '').isalnum():
        die('cowardly dying: bad char(s) in unionfs-fuse/overlay arg: {}'.format(dir))


known_union_tastes = {'overlay': mount_overlay, 'unionfs-fuse': mount_unionfs}

with open(os.path.join(get_plash_data(), 'config', 'union_taste')) as f:
    union_taste = f.read().rstrip('\n')

try:
    mount_func = known_union_tastes[union_taste]
except KeyError:
    die('unexpected union taste: {}'.format(union_taste))

for l in lowerdir_list:
    check_mount_option_part(l)
check_mount_option_part(changedir)
mount_func(lowerdir_list, mountpoint, changedir)