#!/usr/bin/python -Es
#
# Authors: Dan Walsh <dwalsh@redhat.com>
#
# Copyright (C) 2012-2013 Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#

import gi
gi.require_version('LibvirtGObject', '1.0')
from gi.repository import LibvirtGObject
gi.require_version('LibvirtSandbox', '1.0')
from gi.repository import LibvirtSandbox
from gi.repository import GLib
import gi
import re
import os, sys, shutil, errno, stat
import exceptions
import rpm
from subprocess import Popen, PIPE, STDOUT
import gettext
import pwd

if os.path.exists("/sys/fs/selinux"):
    import selinux
else:
    selinux = None

LibvirtGObject.init_object_check(None)
LibvirtSandbox.init_check(None)

gettext.bindtextdomain("libvirt-sandbox", "/usr/share/locale")
gettext.textdomain("libvirt-sandbox")
try:
    gettext.install("libvirt-sandbox",
                    localedir="/usr/share/locale",
                    unicode=False,
                    codeset = 'utf-8')
except IOError:
    import __builtin__
    __builtin__.__dict__['_'] = unicode

CONFIG_PATH = "/etc/libvirt-sandbox/services/"
def get_config_path(name):
    return CONFIG_PATH + name + "/config/sandbox.cfg"

def get_legacy_config_path(name):
    return CONFIG_PATH + name + ".sandbox"

def read_config(name):
    path = get_config_path(name)
    if not os.path.exists(path):
        return None
    return LibvirtSandbox.Config.load_from_path(path)

# shutil.copytree throws a fit if it finds sockets
# or fifos, and has really bad behaviour on block
# and character devices too.
def copydirtree(src, dst):
    filenames = os.listdir(src)
    os.makedirs(dst)

    for filename in filenames:
        srcfilepath = os.path.join(src, filename)
        dstfilepath = os.path.join(dst, filename)

        st = os.lstat(srcfilepath)
        if stat.S_ISDIR(st.st_mode):
            copydirtree(srcfilepath, dstfilepath)

            os.utime(dstfilepath, (st.st_atime, st.st_mtime))
            os.chmod(dstfilepath, stat.S_IMODE(st.st_mode))
        elif stat.S_ISREG(st.st_mode):
            with open(srcfilepath, 'rb') as fsrc:
                with open(dstfilepath, 'wb') as fdst:
                    while 1:
                        buf = fsrc.read(1024*32)
                        if not buf:
                            break
                        fdst.write(buf)

            os.utime(dstfilepath, (st.st_atime, st.st_mtime))
            os.chmod(dstfilepath, stat.S_IMODE(st.st_mode))
        elif stat.S_ISLNK(st.st_mode):
            linkdst = os.readlink(srcfilepath)
            os.symlink(linkdst, dstfilepath)
        else:
            # Ignore all other special files (block/char/sock/fifo)
            pass

class Container:
    DEFAULT_PATH       = "/var/lib/libvirt/filesystems"
    DEFAULT_IMAGE      = "/var/lib/libvirt/images/%s.raw"
    SELINUX_FILE_TYPE  = "svirt_lxc_file_t"

    def __init__(self, name=None, uri = "lxc:///", path = DEFAULT_PATH, config=None, create=False):
        self.uri = uri
        self.use_image = False
        self.size = 10 * MB
        self.path = path
        self.config = config
        if self.config:
            self.name = self.config.get_name()
        else:
            self.name = name
        self.dest = "%s/%s" % (self.path, self.name)
        self.file_type = self.SELINUX_FILE_TYPE
        self.conn = None
        self.image = None
        self.uid = 0
        self.mounts = []

    def get_file_type(self):
        return self.file_type

    def set_file_type(self, file_type):
        self.file_type = file_type

    def set_uid(self, uid):
        self.config.set_userid(uid)

    def get_uid(self):
        return self.config.get_userid(uid)

    def set_gid(self, gid):
        self.config.set_groupid(gid)

    def get_gid(self):
        return self.config.get_groupid(gid)

    def set_username(self, username):
        self.config.set_username(username)

    def get_username(self):
        return self.config.get_username()

    def set_homedir(self, homedir):
        self.config.set_homedir(homedir)

    def get_homedir(self):
        return self.config.get_homedir()

    def set_mounts(self, mounts):
        self.mounts = mounts

    def get_mounts(self):
        return self.mounts

    def add_mounts(self):
        self.config.add_mount_strv(self.mounts)

    def get_config_path(self, name = None):
        if not name:
            name = self.name
        return get_config_path(name)

    def get_filesystem_path(self, name = None):
        if not name:
            name = self.get_name()
        return "%s/%s" % (self.path, name)

    def get_image_path(self, name = None):
        if not name:
            name = self.get_name()
        return self.DEFAULT_IMAGE % name

    def set_image(self, size):
        self.use_image = True
        self.size = size * MB

    def set_path(self, path):
        self.path = path
        self.dest = "%s/%s" % (self.path, self.name)

    def get_name(self):
        return self.name

    def set_name(self, name):
        if self.config:
            raise ValueError([_("Cannot modify Name")])
        self.name = name
        self.dest = "%s/%s" % (self.path, self.name)

    def set_security(self, val):
        self.config.set_security_opts(val)

    def add_network(self, val):
        self.config.add_network_opts(val)

    def get_security_dynamic(self):
        return self.config.get_security_dynamic()

    def get_security_label(self):
        return self.config.get_security_label()

    def set_security_label(self):
        if selinux is None:
            return

        if self.image or self.get_security_dynamic():
            return

        selabel = self.get_security_label()
        if selabel is None:
            raise ValueError([_("Missing security label configuration")])
        parts = selabel.split(":")
        selinux.chcon(self.dest, "system_u:object_r:%s:%s" % (
                self.get_file_type(), ":".join(parts[3:])), True)

    def gen_filesystems(self):
        if self.use_image:
            self.image = self.DEFAULT_IMAGE % self.get_name()
            mount = LibvirtSandbox.ConfigMountHostImage.new(self.image, self.dest)
            self.config.add_mount(mount)

    def fix_stat(self, f):
        try:
            s = os.stat(f)
            path = "%s%s" % (self.dest, f)
            os.chown(path, s.st_uid, s.st_gid)
            os.chmod(path, s.st_mode)
        except OSError, e:
            if not e.errno == errno.ENOENT:
                raise

    def fix_protection(self):
        l = len(self.dest)
        for root, dirs, files in os.walk(self.dest):
            for f in files:
                dest = root + "/" + f
                self.fix_stat(dest[l:])
            for d in dirs:
                dest = root + "/" + d
                self.fix_stat(dest[l:])

    def makedirs(self, d):
        try:
            path = "%s%s" % (self.dest, d)
            os.makedirs(path)
        except OSError, e:
            if not e.errno == errno.EEXIST:
                raise

    def makefile(self, f):
        self.makedirs(os.path.dirname(f))
        try:
            path = "%s%s" % (self.dest, f)
            fd=open(path, "w")
            fd.close()
        except OSError, e:
            if not e.errno == errno.EEXIST:
                raise

    def umount(self):
        p = Popen(["/bin/umount", self.dest])
        p.communicate()
        if p.returncode and p.returncode != 0:
            raise OSError(_("Failed to unmount image %s from %s") %  (self.image, self.dest))

    def create_image(self):
        fd = open(self.image, "w")
        fd.truncate(self.size)
        fd.close()
        p = Popen(["/sbin/mkfs","-F", "-t", "ext4", self.image],stdout=PIPE, stderr=PIPE)
        p.communicate()
        if p.returncode and p.returncode != 0:
            raise OSError(_("Failed to build image %s") % self.image )

        p = Popen(["/bin/mount", self.image, self.dest])
        p.communicate()
        if p.returncode and p.returncode != 0:
            raise OSError(_("Failed to mount image %s on %s") %  (self.image, self.dest))

    def save_config(self):
        self.connect()
        context = self.context()
        context.define()
        sys.stdout.write(_("Created sandbox config %s\n") % get_config_path(self.name))

    def update_config(self):
        self.connect()
        context = self.context()
        context.undefine()
        context.define()
        sys.stdout.write(_("Re-created sandbox config %s\n") % get_config_path(self.name))

    def delete(self):
        self.connect()
        self.conn.fetch_domains(None)
        dom = self.conn.find_domain_by_name(self.name)
        if dom is not None:
            info = dom.get_info()
            if info.state == LibvirtGObject.DomainState.RUNNING:
                raise ValueError([_("Cannot delete running container")])

        # Not sure we should remove content
        if os.path.exists(self.dest):
            shutil.rmtree(self.dest)

        image = self.get_image_path()
        if os.path.exists(image):
            os.remove(image)

        context = self.context()
        context.undefine()

    def get_security_model(self):
        model = None

        # Make sure we have a connection
        self.connect()

        # Loop over the security models from the host capabilities
        # The first in "selinux" and "apparmor" will be the returned model
        # Those two models can't coexist on a machine
        configCaps = self.conn.get_capabilities()
        hostCaps = configCaps.get_host()
        secmodels = hostCaps.get_secmodels()
        for secmodel in secmodels:
            if secmodel.get_model() == "selinux":
                model = "selinux"
                break
            elif secmodel.get_model() == "apparmor":
                model = "apparmor"
                break

        return model


    def create(self):
        self.connect()
        if self.get_security_model() is not None and \
           self.config.get_security_dynamic() and not self.use_image:
            raise ValueError([_("Dynamic security label only supported for image based containers")])
        if self.uri != "lxc:///":
            self.config.set_shell(True)
        if not os.path.exists(self.dest):
            os.mkdir(self.dest)

    def connect(self):
        if not self.conn:
            self.conn=LibvirtGObject.Connection.new(self.uri)
            self.conn.open(None)

    def disconnect(self):
        if self.conn:
            self.conn.close()
            self.conn = None

    def context(self):
        return LibvirtSandbox.ContextService.new(self.conn, self.config)

    def add_bind_mount(self, source, dest):
        if self.image is None:
            mount = LibvirtSandbox.ConfigMountHostBind.new(source, dest)
        else:
            mount = LibvirtSandbox.ConfigMountGuestBind.new(source, dest)
        self.config.add_mount(mount)

    def add_ram_mount(self, dest, size):
        mount = LibvirtSandbox.ConfigMountRam.new(dest, size);
        self.config.add_mount(mount)

class GenericContainer(Container):
    def __init__(self, name=None, uri = "lxc:///", path = Container.DEFAULT_PATH, config=None, create=False):
        Container.__init__(self, name, uri, path, config, create)

        if create:
            self.config = LibvirtSandbox.ConfigServiceGeneric.new(name)

    def gen_filesystems(self):
        Container.gen_filesystems(self)
        self.add_bind_mount(self.dest, self.path)
        self.add_mounts()

    def create_generic(self):
        Container.create(self)
        self.gen_filesystems()

        if self.image:
            self.create_image()
            self.umount()
            sys.stdout.write(_("Created sandbox container image %s\n") % self.image)
        else:
            sys.stdout.write(_("Created sandbox container dir %s\n") % self.dest)
        self.save_config()

    def create(self):
        try:
            self.create_generic()
        except Exception, e:
            try:
                self.delete()
            except Exception, e2:
                pass
            raise e

    def set_command(self, command):
        self.config.set_command(command)


def is_template_unit(unit):
    return '@' in unit

class SystemdContainer(Container):
    IGNORE_DIRS        = [ "/var/run/", "/etc/logrotate.d/", "/etc/pam.d" ]
    DEFAULT_DIRS       = [ "/etc", "/var" ]
    PROFILE_FILES      = [ ".bashrc", ".bash_profile", ".profile" ]
    MACHINE_ID         = "/etc/machine-id"
    HOSTNAME           = "/etc/hostname"
    SYSVINIT_PATH      = "/etc/rc.d"
    ANACONDA_WANTS_PATH = "/usr/lib/systemd/system/anaconda.target.wants"
    MULTI_USER_WANTS_PATH = "/usr/lib/systemd/system/multi-user.target.wants"
    SYSINIT_WANTS_PATH = "/usr/lib/systemd/system/sysinit.target.wants"
    SOCKET_WANTS_PATH  = "/usr/lib/systemd/system/sockets.target.wants"
    MAKE_SYSTEM_DIRS   = [ "/var/lib/dhclient", "/var/lib/dbus", "/var/log", "/var/spool", "/var/cache", "/var/tmp", "/var/lib/nfs/rpc_pipefs", SYSVINIT_PATH, "/lib/lsb" ]
    BIND_SYSTEM_DIRS   = [ "/var", "/home", "/root", "/etc/systemd/system", "/etc/rc.d", "/usr/lib/systemd/system/basic.target.wants", "/usr/lib/systemd/system/local-fs.target.wants", ANACONDA_WANTS_PATH, MULTI_USER_WANTS_PATH, SYSINIT_WANTS_PATH, SOCKET_WANTS_PATH ]
    BIND_SYSTEM_FILES  = [ MACHINE_ID, "/etc/fstab", HOSTNAME ]
    LOCAL_LINK_FILES   = { SYSINIT_WANTS_PATH : [ "systemd-tmpfiles-setup.service" ] , SOCKET_WANTS_PATH : [ "dbus.socket", "systemd-journald.socket", "systemd-shutdownd.socket", "systemd-initctl.socket" ] }

    DEFAULT_UNIT       = "/etc/systemd/system/%s_sandbox.service"

    def __init__(self, name=None, uri = "lxc:///", path = Container.DEFAULT_PATH, config=None, create=False, packages=[]):
        Container.__init__(self, name, uri, path, config, create)
        self.copy = False
        self.unit_file_list = []
        self.packages = packages
        if create:
            self.config = LibvirtSandbox.ConfigServiceSystemd.new(name)
            self.unitfile = None
        else:
            self.unitfile = self.get_unit_path()

    def follow_units(self):
        unitst=""
        for i, src in self.unit_file_list:
            unitst += "ReloadPropagatedFrom=%s\n" % i

        return unitst

    def get_unit_path(self, name = None):
        if not name:
            name = self.get_name()
        return self.DEFAULT_UNIT % name

    def set_unit_file_list(self, unit_file_list):
        self.unit_file_list = unit_file_list

    def get_sandboxed_service(self):
        return self.unit_file_list[0][0].split(".")[0]

    def create_system_unit(self):
        self.unitfile = self.get_unit_path()
        unit = r"""
[Unit]
Description=Secure Sandbox Container %(NAME)s
Requires=libvirtd.service
After=libvirtd.service
%(FOLLOW)s
[Service]
Type=simple
ExecStart=/usr/libexec/virt-sandbox-service-util -c %(URI)s -s %(NAME)s
ExecReload=/usr/bin/virt-sandbox-service -c %(URI)s reload -u %(RELOAD)s %(NAME)s
ExecStop=/usr/bin/virsh -c %(URI)s destroy %(NAME)s

[Install]
WantedBy=multi-user.target
""" % { 'NAME':self.name,
        'FOLLOW':self.follow_units(),
        'RELOAD': " -u ".join(map(lambda x: x[0], self.unit_file_list)),
        'URI': self.uri,
        }

        fd = open(self.unitfile, "w")
        fd.write(unit)
        fd.close()
        if selinux is not None:
            selinux.restorecon(self.unitfile)
        sys.stdout.write(_("Created unit file %s\n") %  self.unitfile)

    def add_dir(self, newd):
        if newd in self.all_dirs:
            return
        for ignd in self.IGNORE_DIRS:
            if newd.startswith(ignd):
                return
        for defd in self.DEFAULT_DIRS:
            if newd.startswith(defd):
                self.all_dirs.append(newd)
                tmp_dirs = []
                for d in self.dirs:
                    if newd.startswith(d):
                        return
                    if not d.startswith(newd):
                        tmp_dirs.append(d)
                self.dirs = tmp_dirs
                self.dirs.append(newd)
                break;

    def add_file(self, newf):
        if newf in self.files:
            return
        for d in self.IGNORE_DIRS:
            if newf.startswith(d):
                return
        for d in self.DEFAULT_DIRS:
            if newf.startswith(d):
                self.files.append(newf)
                break;

    def get_name(self):
        if self.config:
            return self.config.get_name()
        raise ValueError([_("Name not configured")])

    def set_copy(self, copy):
        self.copy = copy

    def get_security_dynamic(self):
        return self.config.get_security_dynamic()

    def extract_rpms(self):
        self.all_dirs = []
        self.dirs = []
        self.files = []

        self.ts = rpm.ts()

        nb_packages = 0
        for u, src in self.unit_file_list:
            rpm_name = self.get_rpm_for_unit(src)
            if rpm_name:
                self.extract_rpm(rpm_name)
                nb_packages += 1

        for package in self.packages:
            self.extract_rpm(package)
            nb_packages += 1

        if nb_packages == 0:
            raise ValueError([_("Cannot autodetect the package for unit files, please use --package")])

    def split_filename(self, filename):
        if filename[-4:] == '.rpm':
            filename = filename[:-4]

        archIndex = filename.rfind('.')
        arch = filename[archIndex+1:]

        relIndex = filename[:archIndex].rfind('-')
        rel = filename[relIndex+1:archIndex]

        verIndex = filename[:relIndex].rfind('-')
        ver = filename[verIndex+1:relIndex]

        epochIndex = filename.find(':')
        if epochIndex == -1:
            epoch = ''
        else:
            epoch = filename[:epochIndex]

        name = filename[epochIndex + 1:verIndex]
        return name, ver, rel, epoch, arch

    def get_rpm_for_unit(self, unitfile):
        mi = self.ts.dbMatch(rpm.RPMTAG_BASENAMES, unitfile)
        try:
            h = mi.next();
        except exceptions.StopIteration:
            return None
        return h['name']


    def extract_rpm(self, rpm_name):
        mi = self.ts.dbMatch('name', rpm_name)
        try:
            h = mi.next();
        except exceptions.StopIteration:
            raise ValueError([_("Cannot find package named %s") % rpm_name])

        for fentry in h.fiFromHeader():
            fname = fentry[0]

            if os.path.isdir(fname):
                self.add_dir(fname)
            if os.path.isfile(fname):
                self.add_file(fname)

        srcrpm = h[rpm.RPMTAG_SOURCERPM]
        srcrpmbits = self.split_filename(srcrpm)

        if srcrpmbits[0] == h[rpm.RPMTAG_NAME]:
            return

        mi = self.ts.dbMatch(rpm.RPMTAG_NAME, srcrpmbits[0])
        try:
            h = mi.next();
        except exceptions.StopIteration:
            raise ValueError([_("Cannot find base package %s") % srcrpmbits[0]])

        for fentry in h.fiFromHeader():
            fname = fentry[0]

            if os.path.isdir(fname):
                self.add_dir(fname)
            if os.path.isfile(fname):
                self.add_file(fname)

    def gen_hostname(self):
        fd=open(self.dest + self.HOSTNAME, "w")
        fd.write("%s\n" % self.name )
        fd.close()

    def gen_machine_id(self):
        uuid_fd = open("/proc/sys/kernel/random/uuid")
        uuid = uuid_fd.read().replace("-","").rstrip()
        uuid_fd.close()
        self.config.set_uuid(uuid)
        fd=open(self.dest + self.MACHINE_ID, "w")
        fd.write("%s\n" % uuid)
        fd.close()

        if not self.use_image:
            # Link /var/log/journal within the container to /var/log/journal/UUID
            # on host.  This will allow the hosts journalctl to easily read
            # containers journal information.
            jdir = "/var/log/journal/"
            jpath = jdir + uuid
            if not os.path.exists(self.dest + jpath):
                os.makedirs(self.dest + jpath)
            if not os.path.exists(jdir):
                os.makedirs(jdir)

            os.symlink(self.dest + jpath, jpath)

    def gen_filesystems(self):
        Container.gen_filesystems(self)
        # 10 MB /run
        mount = LibvirtSandbox.ConfigMountRam.new("/run", 10 * 1024 * 1024);
        self.config.add_mount(mount)

        # 100 MB /tmp
        mount = LibvirtSandbox.ConfigMountRam.new("/tmp", 100 * 1024 * 1024);
        self.config.add_mount(mount)

        # 100 MB /tmp
        mount = LibvirtSandbox.ConfigMountRam.new("/dev/shm", 100 * 1024 * 1024);
        self.config.add_mount(mount)

        for d in self.BIND_SYSTEM_DIRS:
            if d != "/var" and os.path.exists(d):
                source = "%s%s" % ( self.dest, d)
                self.add_bind_mount(source, d)

        for f in self.BIND_SYSTEM_FILES:
            if os.path.exists(f):
                source = "%s%s" % ( self.dest, f)
                self.add_bind_mount(source, f)

        for d in self.dirs:
            found = False
            # Dont add dirs whos parents are in SYSTEM_DIRS
            for s in self.BIND_SYSTEM_DIRS:
                if d.startswith(s):
                    found = True
                    break
            if not found:
                source = "%s%s" % ( self.dest, d)
                self.add_bind_mount(source, d)

        # /var contains the mounted image if there is an image: should be the
        # last thing to mount
        self.add_bind_mount("%s/var" % self.dest, "/var")
        self.add_mounts()

    def get_expanded_unit_template(self, unit):
        return unit.replace('@', '@' + self.name)

    def create_container_unit(self, src, dest, unit):
            if is_template_unit(unit):
                shutil.copy(src, dest + "/" + unit)
                unit = self.get_expanded_unit_template(unit)
                os.symlink(src, dest + "/" + unit)

            dropin_dir = "%s/%s.d" % (dest, unit)
            if not os.path.exists(dropin_dir):
                os.mkdir(dropin_dir)

            fd = open(dropin_dir + "/virt-sandbox.conf", "w")
            fd.write("""; file placed here by virt-sandbox-service
[Service]
PrivateTmp=false
PrivateNetwork=false
""" )
            fd.close()

    def gen_content(self):
        if self.copy:
            for d in self.dirs:
                copydirtree(d, "%s%s" % (self.dest, d))
            for f in self.files:
                self.makedirs(os.path.dirname(f))
                shutil.copy(f, "%s%s" % (self.dest, f))
        else:
            for d in self.all_dirs:
                self.makedirs(d)
            for f in self.files:
                self.makedirs(os.path.dirname(f))
                self.makefile(f)

        for d in self.BIND_SYSTEM_DIRS + self.MAKE_SYSTEM_DIRS:
            self.makedirs(d)

        for f in self.BIND_SYSTEM_FILES:
            self.makefile(f)

        destpath = self.dest + self.SYSVINIT_PATH
        for i in range(7):
            os.mkdir(destpath+("/rc%s.d" % i))

        # Copy both /etc/rc.d/init.d/functions and /lib/lsb/init-functions, even
        # though the latter is the one recommended
        if os.path.exists(self.SYSVINIT_PATH + "/init.d/functions"):
            os.mkdir(destpath+"/init.d")
            shutil.copy(self.SYSVINIT_PATH + "/init.d/functions" , destpath + "/init.d")

        if os.path.exists("/lib/lsb/init-functions"):
            shutil.copy("/lib/lsb/init-functions" , self.dest + "/lib/lsb/")

        self.gen_machine_id()
        self.gen_hostname()

        for k in self.LOCAL_LINK_FILES:
            for d in self.LOCAL_LINK_FILES[k]:
                src = "../%s" % ( d)
                dest = "%s%s/%s" % ( self.dest, k, d)
                os.symlink(src,dest)

        unitdir = "/etc/systemd/system"
        tgtdir = unitdir + "/multi-user.target.wants"

        self.makedirs(unitdir)
        self.makedirs(tgtdir)
        os.symlink("/run", self.dest + "/var/run")

        for i, src in self.unit_file_list:
            self.create_container_unit(src, self.dest + unitdir, i)
            if is_template_unit(i):
                i = self.get_expanded_unit_template(i)
            os.symlink(src, self.dest + tgtdir + "/" + i)

        tgtfile = unitdir + "/multi-user.target"
        try:
            fd = open(self.dest + tgtfile, "w")
            fd.write("[Unit]\n")
            fd.write("Description=Sandbox multi-user target\n")
            fd.close()
        except OSError, e:
            if not e.errno == errno.EEXIST:
                raise

        for p in self.PROFILE_FILES:
            profile = "/etc/skel/" + p
            if os.path.exists(profile):
                shutil.copy(profile, self.dest + "/root/")

        self.fix_protection()

    def delete(self):
        try:
            uuid = self.config.get_uuid()
            if uuid is not None:
                jpath = "/var/log/journal/" + uuid
                if os.path.lexists(jpath):
                    os.remove(jpath)
        except Exception, e:
            sys.stderr.write("%s: %s\n" % (sys.argv[0], e))
            sys.stderr.flush()

        Container.delete(self)

        if self.unitfile and os.path.exists(self.unitfile):
            p = Popen(["/usr/bin/systemctl","disable", self.unitfile],stdout=PIPE, stderr=PIPE)
            p.communicate()
            if p.returncode and p.returncode != 0:
                raise OSError(_("Failed to disable %s unit file") %  self.unitfile)
            os.remove(self.unitfile)

    def create_systemd(self):
        self.extract_rpms()
        Container.create(self)
        self.gen_filesystems()
        if self.image:
            self.create_image()
            self.gen_content()
            self.umount()
            sys.stdout.write(_("Created sandbox container image %s\n") % self.image)
        else:
            self.gen_content()
            sys.stdout.write(_("Created sandbox container dir %s\n") % self.dest)
        self.set_security_label()
        self.create_system_unit()
        self.config.set_boot_target("multi-user.target")
        self.save_config()

    def create(self):
        if os.path.exists(self.dest):
            raise OSError(_("%s already exists") % self.dest)

        try:
            self.create_systemd()
        except Exception, e:
            try:
                self.delete()
            except Exception, e2:
                sys.stderr.write("Cleanup failed: %s\n" % str(e2))
            raise

    def reload(self, unitfiles):
        class Args:
            command = []
            noseclabel = None
            name = self.name
            uri = self.uri
        args = Args()
        args.command = [ "systemctl", "reload" ] + map(lambda x: x[0], unitfiles)
        execute(args)

MB = int(1000000)

def delete(args):
    config = read_config(args.name)
    if config is None:
        sys.stderr.write("Sandbox '%s' does not exist\n" % args.name)
        sys.exit(1)

    if isinstance(config, gi.repository.LibvirtSandbox.ConfigServiceGeneric):
        container = GenericContainer(uri=args.uri, config = config)
    else:
        container = SystemdContainer(uri=args.uri, config = config)
    container.set_path(args.path)
    container.delete()

def create(args):
    if len(args.command) > 0 and len(args.unitfiles) > 0:
        raise ValueError([_("Commands cannot be specified with unit files")])

    if len(args.command) == 0  and len(args.unitfiles) == 0:
        raise ValueError([_("You must specify a command or a unit file")])

    if args.packages and len(args.unitfiles) != 1:
        raise ValueError([_("Option --package cannot be used without a unit file")])

    if len(args.command) > 0:
        container = GenericContainer(name = args.name, uri=args.uri, create = True)
        container.set_command(args.command)
    else:
        container = SystemdContainer(name = args.name, uri=args.uri, create = True, packages = args.packages)
        container.set_copy(args.copy)
        container.set_unit_file_list(args.unitfiles)
    for net in args.network:
        container.add_network(net)
    if args.security:
        container.set_security(args.security)
    container.set_uid(args.uid)
    if not args.homedir:
        args.homedir = pwd.getpwuid(args.uid).pw_dir
    container.set_homedir(args.homedir)
    if not args.username:
        args.username = pwd.getpwuid(args.uid).pw_name
    container.set_username(args.username)
    if not args.gid:
        args.gid = pwd.getpwuid(args.uid).pw_gid
    container.set_gid(args.gid)
    container.set_path(args.path)
    container.set_file_type(args.file_type)
    container.set_mounts(args.mounts)
    if args.imagesize:
        container.set_image(args.imagesize)

    container.create()

def usage(parser, msg):
    parser.print_help()

    sys.stderr.write("\n%s\n" % msg)
    sys.stderr.flush()
    sys.exit(1)

def sandbox_reload(args):
    config = read_config(args.name)
    if isinstance(config, gi.repository.LibvirtSandbox.ConfigServiceGeneric):
        raise ValueError([_("Generic Containers do not support reload")])
    container = SystemdContainer(uri = args.uri, config = config)
    container.reload(args.unitfiles)

def connect(args):
    if args.uri == "lxc:///":
        class Args:
            command = []
            noseclabel = None
            name = args.name
            uri = args.uri

        args = Args()
        args.command = [ "/bin/sh" ]
        execute(args)
        return

    print """\
Connected to %s.
Type 'Ctrl + ]' to detach from the console.
""" % ( args.name )
    os.execl("/usr/libexec/virt-sandbox-service-util",
             "virt-sandbox-service-util",
             "-c", args.uri,
             "-a", args.name)

#
# Search Path for command to execute within the container.
#
def fullpath(cmd):
    for i in [ "/", "./", "../" ]:
        if cmd.startswith(i):
            return cmd
    for i in  os.environ["PATH"].split(':'):
        f = "%s/%s" % (i, cmd)
        if os.access(f, os.X_OK):
            return f
    return cmd

def execute(args):
    if args.uri != "lxc:///":
        raise ValueError([_("Can only execute commands inside of linux containers.")])

    myexec = [ "virsh", "-c", args.uri, "lxc-enter-namespace" ]
    if args.noseclabel:
        myexec.append("--noseclabel")
    myexec.extend([ args.name, "--", fullpath(args.command[0])] +  args.command[1:])
    os.execv("/usr/bin/virsh", myexec)

def clone(args):
    config = read_config(args.source)
    if isinstance(config, gi.repository.LibvirtSandbox.ConfigServiceGeneric):
        container = GenericContainer(uri=args.uri, config=config)
    else:
        container = SystemdContainer(uri=args.uri, config=config)
    newcontainer = None

    container.set_path(args.path)

    old_path = container.get_filesystem_path()
    new_path = container.get_filesystem_path(args.dest)

    if os.path.exists(new_path):
        raise OSError(_("%s already exists") % new_path)

    try:
        fd = open(container.get_config_path(),"r")
        recs = fd.read()
        fd.close()

        newrec = recs.replace(old_path + "/", new_path + "/")
        newrec = newrec.replace("name=" + args.source, "name=" + args.dest)
        old_image_path = container.get_image_path()
        if os.path.exists(old_image_path):
            new_image_path = container.get_image_path(args.dest)
            newrec = newrec.replace(old_image_path, new_image_path)
            shutil.copy(old_image_path, new_image_path)
            sys.stdout.write(_("Created sandbox container image %s\n") % new_image_path)
            os.mkdir(new_path)
        else:
            copydirtree(old_path, new_path)
            sys.stdout.write(_("Created sandbox container dir %s\n") % new_path)

        if isinstance(config, gi.repository.LibvirtSandbox.ConfigServiceGeneric):
            newcontainer = GenericContainer(name=args.dest, uri=args.uri, create=True)
            newcontainer.set_path(args.path)
        else:
            fd = open(container.get_unit_path())
            recs = fd.read()
            fd.close()

            new_unit = container.get_unit_path(args.dest)
            fd = open(new_unit, "wx")
            fd.write(recs.replace(args.source, args.dest))
            fd.close()

            sys.stdout.write(_("Created unit file %s\n") % new_unit)

            config = LibvirtSandbox.Config.load_from_data(newrec)
            newcontainer = SystemdContainer(config=config, uri=args.uri)
            newcontainer.set_path(args.path)
            newcontainer.gen_machine_id()
            newcontainer.gen_hostname()

        if args.security:
            newcontainer.set_security(args.security)
        newcontainer.set_security_label()
        newcontainer.save_config()
    except Exception, e:
        if newcontainer is not None:
            newcontainer.delete()
        raise


def upgrade_config_legacy(path):
    config = LibvirtSandbox.Config.load_from_path(path)

    if isinstance(config, gi.repository.LibvirtSandbox.ConfigServiceGeneric):
        container = GenericContainer(uri=args.uri, config=config)
    else:
        container = SystemdContainer(uri=args.uri, config=config)

        fd = open(container.get_unit_path())
        unitfile = fd.read()
        fd.close()

        unitfile = unitfile.replace("/usr/bin/virt-sandbox-service start",
                                    "/usr/libexec/virt-sandbox-service-util -c lxc:/// -s")
        unitfile = unitfile.replace("/usr/bin/virt-sandbox-service reload",
                                    "/usr/bin/virt-sandbox-service -c lxc:/// reload")
        unitfile = unitfile.replace("/usr/bin/virt-sandbox-service stop",
                                    "/usr/bin/virsh -c lxc:/// destroy")

        unitfile = re.sub("WantedBy=.*\.target",
                          "WantedBy=multi-user.target",
                          unitfile)

        os.remove(container.get_unit_path())
        fd = open(container.get_unit_path(), "wx")
        fd.write(unitfile)
        fd.close()

        sys.stdout.write(_("Created unit file %s\n") %
                         container.get_unit_path())

    # Create new config file + libvirt persistent XML config
    container.save_config()
    # Kill legacy config file
    os.remove(path)


def upgrade_config_current(path):
    config = LibvirtSandbox.Config.load_from_path(path)

    if isinstance(config, gi.repository.LibvirtSandbox.ConfigServiceGeneric):
        container = GenericContainer(uri=args.uri, config=config)
    else:
        container = SystemdContainer(uri=args.uri, config=config)

    # Create new config file + libvirt persistent XML config
    container.update_config()


def upgrade_config(args):
    newconfigfile = get_config_path(args.name)
    oldconfigfile = get_legacy_config_path(args.name)
    if os.path.exists(oldconfigfile):
        upgrade_config_legacy(oldconfigfile)
    elif os.path.exists(newconfigfile):
        upgrade_config_current(newconfigfile)
    else:
        sys.stderr.write("Sandbox '%s' does not exist\n" % args.name)
        sys.exit(1)


def upgrade_filesystem(args):
    # This is where we'd look at RPM DB and upgrade the
    # filesystem with new info for the unit files
    pass

# This function must be capable of reading configs created by
# old releases and "fixing" them to work with the new release
def upgrade(args):
    upgrade_config(args)
    upgrade_filesystem(args)

import argparse
class AddMount(argparse.Action):
    def __call__(self, parser, namespace, values, option_string=None):
        newval = getattr(namespace, self.dest)
        if not newval:
            newval = []
        for v in values:
            newval.append(v)
        setattr(namespace, self.dest, newval)

class SizeAction(argparse.Action):
    def __call__(self, parser, namespace, values, option_string=None):
        setattr(namespace, self.dest, int(values))

class CheckUnit(argparse.Action):
    def __call__(self, parser, namespace, value, option_string=None):
        def check_unit(unit):
            src = "/etc/systemd/system/" + unit
            if os.path.exists(src):
                return src
            src = "/usr/lib/systemd/system/" + unit
            if os.path.exists(src):
                return src
            return None
        src = check_unit(value)
        if not src:
            src = check_unit(value + ".service")
            if src:
                value = value + ".service"
            else:
                raise OSError(_("Requested unit %s does not exist") % value)

        unitfiles = getattr(namespace, self.dest)
        if unitfiles:
            unitfiles.append((value, src))
        else:
            unitfiles = [ (value, src) ]
        setattr(namespace, self.dest, unitfiles)

class SetNet(argparse.Action):
    def __call__(self, parser, namespace, values, option_string=None):
        nets = getattr(namespace, self.dest)
        if nets:
            nets.append(values)
        else:
            nets = [values]
        setattr(namespace, self.dest, nets)

class CheckPackage(argparse.Action):
    def __call__(self, parser, namespace, value, option_string=None):
        nb_rpm = len(rpm.TransactionSet().dbMatch('name', value))
        if nb_rpm == 0:
            raise OSError(_("Cannot find %s rpm") % value)
        elif nb_rpm > 1:
            raise OSError(_("%s rpm is installed more than once") % value)
        packages = getattr(namespace, self.dest)
        if packages:
            packages.append(value)
        else:
            packages = [ value ]
        setattr(namespace, self.dest, packages)

def requires_name(parser):
    parser.add_argument("name",
                        help=_("name of the sandbox container"))

def default_security_opts():
    if selinux is None:
        return None

    # XXX vary per URI for kvm/qemu/lxc.
    # XXX generate a random category
    return "static,label=system_u:system_r:svirt_lxc_net_t:s0"

def gen_create_args(subparser):
    parser = subparser.add_parser("create",
                                  help=_("Create a sandbox container."))

    parser.add_argument("-C", "--copy", default=False,
                        action="store_true",
                        help=_("copy content from the hosts /etc and /var directories that will be mounted within the sandbox"))

    parser.add_argument("-f", "--filetype", dest="file_type",
                        default=c.get_file_type(),
                        help=_("SELinux file type to assign to content within the sandbox.  Default: %s") % c.get_file_type())
    parser.add_argument("--homedir", dest="homedir",
                        help=_("Specify the homedir for the container. Default: UID homedir."))
    parser.add_argument("-G", "--gid", dest="gid",
                        default=None, type=int,
                        help=_("Specify the login gid for the container. Default: login GID of the UID."))
    parser.add_argument("-i", "--imagesize", dest="imagesize", default = None,
                       action=SizeAction,
                       help=_("create image of this many megabytes."))
    parser.add_argument("-m", "--mount", dest="mounts",default=[], nargs="*", action=AddMount,
                        help=_("Mount a filesytem in the sandbox"))
    parser.add_argument("-N", "--network", dest="network",
                        action=SetNet, default=[],
                        help=_("Specify the network configuration"))
    parser.add_argument("-p", "--path", dest="path",  default=c.DEFAULT_PATH,
                        help=_("select path to store sandbox content.  Default: %s") % c.DEFAULT_PATH)
    parser.add_argument("-s", "--security", dest="security",
                        default=default_security_opts(),
                        help=_("Specify the security model configuration for the sandbox: Defaults to dynamic"))
    parser.add_argument("-u", "--unitfile",
                        action=CheckUnit,
                        dest="unitfiles", default=[],
                        help=_("Systemd Unit file to run within the systemd sandbox container. Commands cannot be specified with unit files."))
    parser.add_argument("-P", "--package",
                        action=CheckPackage,
                        dest="packages", default=[],
                        help=_("RPM package to be used in the container. Default: autodetected from unit files."))
    parser.add_argument("--username", dest="username",
                        help=_("Specify the username for the container. Default: UID username."))
    parser.add_argument("-U", "--uid", dest="uid",
                        default=os.getuid(),type=int,
                        help=_("Specify the uid for the container: Default to current UID."))

    requires_name(parser)
    parser.add_argument("command", default=[], nargs="*",
                        help=_("Command to run within the generic sandbox container. Commands cannot be specified with unit files."))

    parser.set_defaults(func=create)

def gen_connect_args(subparser):
    parser = subparser.add_parser("connect",
                                  help=_("Connect to a sandbox container"))
    requires_name(parser)
    parser.set_defaults(func=connect)

def gen_execute_args(subparser):
    parser = subparser.add_parser("execute",
                                  help=_("Execute a command within a sandbox container. Only available for lxc:///"))
    parser.add_argument("-N", "--noseclabel", dest="noseclabel",
                        default=False, action="store_true",
                        help=_("do not modify the label of the executable process.  By default all commands execute with the label of the sandbox"))
    requires_name(parser)
    parser.add_argument("command", nargs="+",
                        help=_("command to execute within the container"))
    parser.set_defaults(func=execute)

def gen_reload_args(subparser):
    parser = subparser.add_parser("reload",
                                   help=_("Reload a running sandbox container"))
    parser.add_argument("-u", "--unitfile", required=True,
                        action=CheckUnit, dest="unitfiles",
                        help=_("Systemd Unit file to reload within the sandbox container"))
    requires_name(parser)
    parser.set_defaults(func=sandbox_reload)

def gen_clone_args(subparser):
    parser = subparser.add_parser("clone",
                                  help=_("Clone an existing sandbox container"))
    parser.set_defaults(func=clone)
    parser.add_argument("-p", "--path", dest="path",  default=c.DEFAULT_PATH,
                        help=_("select path to copy sandbox content from/to.  Default: %s") % c.DEFAULT_PATH)
    parser.add_argument("-s", "--security", dest="security",
                        default=default_security_opts(),
                        help=_("Specify the security model configuration for the sandbox: Defaults to dynamic"))

    parser.add_argument("source",
                        help=_("source sandbox container name"))
    parser.add_argument("dest",
                        help=_("dest name of the new sandbox container"))

def gen_delete_args(subparser):
    parser = subparser.add_parser("delete",
                                   help=_("Delete a sandbox container"))
    parser.add_argument("-p", "--path", dest="path",  default=c.DEFAULT_PATH,
                        help=_("select path to delete sandbox content from.  Default: %s") % c.DEFAULT_PATH)
    requires_name(parser)
    parser.set_defaults(func=delete)

def gen_upgrade_args(subparser):
    parser = subparser.add_parser("upgrade",
                                   help=_("Upgrade the sandbox container"))
    requires_name(parser)
    parser.set_defaults(func=upgrade)

if __name__ == '__main__':
    c = Container()

    parser = argparse.ArgumentParser(description='Sandbox Container Tool')
    parser.add_argument("-c", "--connect", required=False, dest="uri",  default="lxc:///",
                        help=_("libvirt connection URI to use (lxc:/// [default] or qemu:///session)"))

    subparser = parser.add_subparsers(help=_("commands"))
    gen_create_args(subparser)
    gen_clone_args(subparser)
    gen_connect_args(subparser)
    gen_delete_args(subparser)
    gen_execute_args(subparser)
    gen_reload_args(subparser)
    gen_upgrade_args(subparser)

    try:
        args = parser.parse_args()
        if args.uri[0:3] != "lxc":
            sys.stderr.write("%s: only lxc:/// URIs are currently supported\n" % sys.argv[0])
            sys.exit(1)
        if os.geteuid() != 0:
            sys.stderr.write("%s: lxc:/// URIs are only supported when run as root\n" % sys.argv[0])
            sys.exit(1)
        args.func(args)
        sys.exit(0)
    except KeyboardInterrupt, e:
        sys.exit(0)
    except ValueError, e:
        for line in e:
            for l in line:
                sys.stderr.write("%s: %s\n" % (sys.argv[0], l))
        sys.stderr.flush()
        sys.exit(1)
    except IOError, e:
        sys.stderr.write("%s: %s: %s\n" % (sys.argv[0], e.filename, e.strerror))
        sys.stderr.flush()
        sys.exit(1)
    except OSError, e:
        sys.stderr.write("%s: %s\n" % (sys.argv[0], e))
        sys.stderr.flush()
        sys.exit(1)
    except GLib.GError, e:
        sys.stderr.write("%s: %s\n" % (sys.argv[0], e))
        sys.stderr.flush()
        sys.exit(1)
