Fork me on GitHub

Usage

plash mount CONTAINER MOUNTPOINT [ CHANGESDIR ]

Description

Mount a container-filesystem. 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.

Tested behaviour

Preface

#!/bin/bash

    
  
    
      
tmp=$(mktemp -d)
plash mount 1 $tmp
grep  $tmp' '  /proc/mounts >/dev/null || {
  echo 'Could not find mountpoint in /proc/mounts '
  exit 1
}
test -d $tmp/usr
test -d $tmp/bin
test -d $tmp/var

    
  
    
      

Check bad chars

tmp=$(mktemp -d)
changesdir=$tmp/'!@#$%^&*()'
mkdir $tmp/mp
mkdir $changesdir
(! plash mount 1 /tmp/mp "$changesdir" )

Source Code

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

from plash import utils

utils.assert_initialized()

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

nodepath = utils.plash_call("nodepath", 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 = utils.plash_call("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 utils.die("unionfs-fuse seems not to be installed")
    )
    with utils.catch_and_die([CalledProcessError, FileNotFoundError]):
        check_call(
            [
                unionfs,
                "-o",
                "cow",
                "{upperdir}{lowerdirs}".format(
                    lowerdirs=lowerdirs_str, upperdir=upperdir_str
                ),
                mountpoint,
            ],
            stdout=2,
        )


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 utils.catch_and_die([CalledProcessError, FileNotFoundError]):
        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,
            ],
            stdout=2,
        )


def mount_fuse_overlayfs(lowerdirs_list, mountpoint, changedir):
    if not changedir:
        changedir = utils.plash_call("mkdtemp")
        check_mount_option_part(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)
    with utils.catch_and_die([CalledProcessError, FileNotFoundError]):
        check_call(
            [
                "fuse-overlayfs",
                "-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()
    ):
        utils.die(
            "cowardly dying: bad char(s) in unionfs-fuse/overlay arg: {}".format(dir)
        )


known_union_tastes = {
    "overlay": mount_overlay,
    "unionfs-fuse": mount_unionfs,
    "fuse-overlayfs": mount_fuse_overlayfs,
}

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

try:
    mount_func = known_union_tastes[union_taste]
except KeyError:
    utils.die(
        "unexpected union taste: {} (try {})".format(
            union_taste, " or ".join(known_union_tastes.keys())
        )
    )

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