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


#!/usr/bin/env python3
#
# usage: plash add-layer PARENT-CONTAINER IMPORT-DIR
# Stack a layer on top of a container.  Please note that it uses the rename
# syscall, this means your IMPORT-DIR will be moved into the plash data
# directory.  The container "0" is the empty root container, use that to start
# a container from scratch.
#
# This subcommand is very low-level, usually you would use `plash build`.
# Parameters may be interpreted as build instruction.
#
# Examples:
#
# Create a container from a complete root file system:
# $ plash add-layer 0 /tmp/rootfs
# 66
#
# Add a new layer on top of an existing container:
# $ plash add-layer 33 /tmp/mylayer
# 67

import os
import sys
from os.path import join
from plash import unshare

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


unshare.unshare_if_user()

handle_help_flag()
handle_build_args()

plash_data = get_plash_data()

try:
    base_container = sys.argv[1]
    import_dir = sys.argv[2]
except IndexError:
    die_with_usage()

#
# prepare the node with the dir being imported
# this is the payload data we want to put in the builds folder later
#
prepared_new_node = mkdtemp()
os.chmod(prepared_new_node, 0o755)
os.mkdir(join(prepared_new_node, '_data'), 0o755)

# the next line will actually make the import_dir "disappear" for the user.
# the "root" folder will hold the access rights, user rights and maybe other meta
# data for the root folder ('/' after a chroot)
with catch_and_die([OSError], debug='rename'):
    os.rename(import_dir, join(prepared_new_node, '_data', 'root'))


#
# get an node id candidate and try to register it
#
while True:

    # get a new id, this does have race condition, in which the 
    # node_id_candidate will already be taken
    with open(join(plash_data, 'id_counter'), 'a') as f:
        f.write('A')
        node_id_candidate = str(f.tell())
    
    # where the layer data will be saved
    new_node_path = join(
        nodepath_or_die(base_container, allow_root_container=True), node_id_candidate)

    # try to claim this node_id, if it is already taken, reiterate
    try:
        # Register that we are using this new_node_path,
        # so it does not gets lost in the 'build' folder tree if this program crashes.
        os.symlink(new_node_path, join(plash_data, 'index', node_id_candidate))
    except FileExistsError:
        continue
    break

#
#  put our new node with the import directory in the builds folder
#  with this we are so to speak saving the "real data"
#
os.rename(prepared_new_node, new_node_path)

#
#  inform the user about the new container
#
print(node_id_candidate)