#!/usr/bin/env python
# -*- coding: utf-8 -*-

# LXCF - LXC Facility
# Copyright (C) 2013-2014 FUJITSU LIMITED

# 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; version 2
# of the License.
#
# 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., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.

#
# A scripts for creating LXC domain in which some daemons are running.
# running systemd, rsyslog, sshd in a container.
#

import sys
import os
import platform
import shutil
import fcntl
import ConfigParser
import uuid
import IPy
from optparse import OptionParser

# check root
if os.geteuid():
    print("error: Because you are not root, you cannot execute this command.")
    sys.exit(1)

parser = OptionParser()
parser.add_option("-s", "--separate", action="store_true", default=False,
                  dest="separate", help="separate mode")
parser.add_option("-j", "--joint", action="store_true", default=False,
                  dest="joint", help="joint mode")
(options, args) = parser.parse_args(sys.argv)

if len(args) != 2:
    print("usage: lxcf setup LXCNAME")
    sys.exit(1)

domain_name = args[1]
ROOTDIR = "/opt/lxcf/" + domain_name
arch = platform.machine()

CONFIG_FILE = "/etc/lxcf/lxcf.conf"
HOSTS_FILE = "/etc/hosts"
UUID_FILE = "/etc/lxcf/rsc/" + domain_name + "/uuid"

#
# set uuid
#
UUID = str(uuid.uuid1())


#
# directories created at domain creation
#


class InstallInfo:
    def __init__(self):
        self.DIRS = []
        self.FILES = []
        self.BINDDIRS = []
        self.SYMLINKS = []
        self.TMPFS = []
        self.PACKAGES = []
        self.MERGED = []

    def add_dirs(self, x):
        if isinstance(x, str):
            x = [x]
        self.DIRS = self.DIRS + x

    def dirs(self):
        return self.DIRS

    def add_files(self, x):
        if isinstance(x, str):
            x = [x]
        self.FILES += x

    def files(self):
        return self.FILES

    def add_binds(self, x):
        if isinstance(x, str):
            x = [x]
        self.BINDDIRS = self.BINDDIRS + x

    def binds(self):
        return self.BINDDIRS

    def add_links(self, x):
        self.SYMLINKS = self.SYMLINKS + x

    def links(self):
        return self.SYMLINKS

    def add_tmpfs(self, x):
        self.TMPFS = self.TMPFS + x

    def tmpfs(self):
        return self.TMPFS

    def add_packages(self, x):
        self.PACKAGES = self.PACKAGES + x

    def packages(self):
        return self.PACKAGES

    def merge(self):
        self.DIRS = list(set(self.DIRS))
        self.FILES = list(set(self.FILES))
        self.BINDDIRS = sorted(list(set(self.BINDDIRS)))
        self.TMPFS = sorted(list(set(self.TMPFS)))
        ret = True

        ents = []
        paths = []
        for ent in self.DIRS:
            ents.append((ent, "dir", ""))
            paths.append(ent)
        for ent in self.FILES:
            ents.append((ent, "file", ""))
            paths.append(ent)
        for ent in self.SYMLINKS:
            ents.append((ent[0], "link", ent[1]))
            paths.append(ent[0])

        if len(paths) != len(list(set(paths))):
            ret = False

        self.MERGED = sorted(ents)
        return ret

    def merged(self):
        return self.MERGED

#
# Function for IP address
#


def erase_ipaddr(name, hosts_file):
    """erase IP addres from /etc/hosts"""
    # read hosts file and check ip addr
    try:
        f = open(hosts_file, "r")
    except:
        return

    Lines = f.readlines()

    f = open(hosts_file, "w")
    fcntl.flock(f.fileno(), fcntl.LOCK_EX)

    for l in Lines:
        S = l.split()
        if len(S) >= 1:
            if (S[0])[0] == "#":
                continue
            if name != S[1]:
                f.write(l)

    # unlock hosts file
    fcntl.flock(f.fileno(), fcntl.LOCK_UN)

    f.close()


def find_ipaddr(name, hosts_file):
    """find IP addres from /etc/hosts"""
    # read hosts file and check ip addr
    try:
        f = open(hosts_file, "r")
    except:
        return IPy.IP(1)

    while True:
        Line = f.readline()
        if Line:
            S = Line.split()

            if len(S) >= 1:
                if (S[0])[0] == "#":
                    continue

                try:
                    ip_s = IPy.IP(S[0])
                except:
                    continue

                if name == S[1]:
                    f.close()
                    return ip_s
        else:
            break

    return IPy.IP(1)


def check_ipaddr(ip, hosts_file):
    """check IP address in /etc/hosts"""
    # read hosts file and check ip addr
    try:
        f = open(hosts_file, "r")
    except:
        return False

    while True:
        Line = f.readline()
        if Line:
            S = Line.split()

            if len(S) >= 1:
                if (S[0])[0] == "#":
                    continue

                try:
                    ip_s = IPy.IP(S[0])
                except:
                    continue

                if ip_s == ip:
                    f.close()
                    return False
        else:
            break

    return True


def set_ipaddr(hosts_file, ipaddr, domain_name):
    """set new IP address"""
    try:
        with open(hosts_file, "a") as f:
            f.write(ipaddr.strNormal() + "\t" + domain_name + "\n")
    except:
        pass


def get_ipaddr(hosts_file):
    """get new IP address"""
    # read the config file
    conf = ConfigParser.SafeConfigParser()
    conf.read(CONFIG_FILE)

    ipaddr_start = IPy.IP(conf.get("ipaddr_range", "ipaddr_start"))
    ipaddr_end = IPy.IP(conf.get("ipaddr_range", "ipaddr_end"))

    ip1 = ipaddr_start.int()
    ip2 = ipaddr_end.int()

    # looks for empty IP.address
    for i in range(ip1, ip2 + 1):
        ipa = IPy.IP(i)
        if check_ipaddr(ipa, hosts_file):
            break

    set_ipaddr(hosts_file, ipa, domain_name)
    return ipa


def remove_chkconfig(name):
    """Functions for chkconfig"""
    for i in range(0, 7):
        try:
            os.remove(ROOTDIR + "/etc/rc.d/rc" + str(i) + ".d/" + name)
        except:
            pass

#
# Functions for workarounds.
#


def systemd_tune():
    """create our own basic.target for avoid some startups."""
    #
    # we need to avoid some special services by systemd.
    # modify basic.target and avoid them.
    #
    if not os.path.exists("/etc/systemd/system"):
        return

    filename = ROOTDIR + "/etc/systemd/system/basic.target"
    data = """\
[Unit]
Description=Basic System
Documentation=man:systemd.special(7)
Requires=systemd-tmpfiles-setup.service sockets.target
After=systemd-tmpfiles-setup.service sockets.target
RefuseManualStart=yes
"""
    with open(filename, "w") as f:
        f.write(data)
    filename = ROOTDIR + "/etc/systemd/system/basic.target.wants"
    try:
        os.makedirs(filename)
    except:
        pass

    #
    # we need getty only with tty1
    #
    try:
        os.symlink(
            "/usr/lib/systemd/system/getty@.service", ROOTDIR
            + "/etc/systemd/system/getty.target.wants/getty@tty1.service")
    except:
        os.remove(
            ROOTDIR
            + "/etc/systemd/system/getty.target.wants/getty@tty1.service")
        os.symlink(
            "/usr/lib/systemd/system/getty@.service", ROOTDIR
            + "/etc/systemd/system/getty.target.wants/getty@tty1.service")


def eth0_service():
    """Create ifcfg-eth0 and add service to bring up it."""
    if os.path.exists("/etc/sysconfig/network"):
        filename = ROOTDIR + "/etc/sysconfig/network"
        data = """\
NETWORKING=yes
HOSTNAME=%s
GATEWAY=192.168.125.1
""" % domain_name
        with open(filename, "w") as f:
            f.write(data)

    #
    # ifconfig setting for eth0
    #
    erase_ipaddr(domain_name, HOSTS_FILE)
    newipaddr = get_ipaddr(HOSTS_FILE)
    for hfile in os.listdir("/opt/lxcf/"):
        hf = "/opt/lxcf/" + hfile + "/etc/hosts"
        erase_ipaddr(domain_name, hf)
        set_ipaddr(hf, newipaddr, domain_name)

    if os.path.exists("/etc/sysconfig/network-scripts"):
        filename = ROOTDIR + "/etc/sysconfig/network-scripts/ifcfg-eth0"
        data = """\
DEVICE=eth0
BOOTPROTO=static
ONBOOT=yes
NETMASK=255.255.255.0
IPADDR=%s
NAME=eth0
TYPE=Ethernet
USERCTL=no
""" % newipaddr.strNormal()
        with open(filename, "w") as f:
            f.write(data)

    if os.path.exists("/etc/systemd/system"):
        filename = ROOTDIR + "/etc/systemd/system/lxc-eth0.service"
        data = """\
[Unit]
Before=multi-user.target
Conflicts=shutdown.target
Description=bring up eth0 in this container
[Service]
ExecStart=/usr/sbin/ifup eth0
Type=simple
"""
        with open(filename, "w") as f:
            f.write(data)

    #
    # Bring up this.
    #
    if os.path.exists("/etc/systemd/system"):
        filename = (ROOTDIR +
                    "/etc/systemd/system/basic.target.wants/lxc-eth0.service")
        src = "/etc/systemd/system/lxc-eth0.service"
        try:
            os.symlink(src, filename)
        except:
            os.remove(filename)
            os.symlink(src, filename)


def empty_fstab():
    """Make fstab empty"""
    filename = ROOTDIR + "/etc/fstab"
    with open(filename, "w") as f:
        f.truncate(0)


def securetty_tune():
    """securetty set up for login via system console."""
    path = ROOTDIR + "/etc/securetty"
    with open(path, "a") as f:
        f.write("pts/0\n")


def hostname_setting():
    """set hostname of guest domain."""
    path = ROOTDIR + "/etc/hostname"
    with open(path, "w") as f:
        f.write(domain_name + "\n")


def containername_setting():
    """set container name and kind"""
    path = ROOTDIR + "/etc/lxcf/container_name"
    if options.separate:
        name_kind = domain_name + " separate"
    else:
        name_kind = domain_name + " joint"
    with open(path, "w") as f:
        f.write(name_kind)

#
# Main routine starts here !
#

#
# Check Domain name is passed.
#
if domain_name == "_unknown":
    print("Guest Domain name must be specified")
    sys.exit(1)

info = InstallInfo()

# Build a information.
info.add_dirs(["/tmp", "/root", "/home", "/run"])
info.add_dirs(["/boot", "/dev", "/media", "/mnt", "/srv"])
info.add_dirs(["/usr", "/opt"])

info.add_tmpfs(["/tmp"])
info.add_links([["/lib", "usr/lib"]])
info.add_links([["/lib64", "usr/lib64"]])
info.add_links([["/bin", "usr/bin"]])
info.add_links([["/sbin", "usr/sbin"]])
info.add_links([["/var/run", "../run"]])
# some systemd tweaks
#info.add_links([["/etc/systemd/system/default.target",
#        "/lib/systemd/system/multi-user.target"]])

if not info.merge():
    print("some confilction of files may happen...")

# Ok, make world.
for ents in info.merged():
    guestpath = ROOTDIR + ents[0]
    if ents[1] == "dir":
        if os.path.exists(ents[0]):
            if not os.path.exists(guestpath):
                try:
                    os.makedirs(guestpath)
                except:
                    pass
    elif ents[1] == "link":
        if os.path.exists(ents[0]):
            if not os.path.exists(guestpath):
                try:
                    os.symlink(ents[2], guestpath)
                except:
                    pass
    elif ents[1] == "file":
        if os.path.exists(ents[0]):
            if (
                not os.path.exists(guestpath)
                or os.path.getmtime(ents[0]) > os.path.getmtime(guestpath)
            ):
                try:
                    shutil.copy(ents[0], guestpath)
                except:
                    pass

#
# Tweak system settings.
#
# diable some services.
dir = ROOTDIR + "/etc/systemd/system/"

try:
    os.symlink("/dev/null", dir + "sysinit.target")
except:
    try:
        os.remove(dir + "sysinit.target")
        os.symlink("/dev/null", dir + "sysinit.target")
    except:
        pass

try:
    os.symlink("/dev/null", dir + "console-shell.service")
except:
    try:
        os.remove(dir + "console-shell.service")
        os.symlink("/dev/null", dir + "console-shell.service")
    except:
        pass

try:
    os.symlink("/dev/null", dir + "display-manager.service")
except:
    try:
        os.remove(dir + "display-manager.service")
        os.symlink("/dev/null", dir + "display-manager.service")
    except:
        pass

try:
    os.symlink("/dev/null", dir + "fedora-readonly.service")
except:
    try:
        os.remove(dir + "fedora-readonly.service")
        os.symlink("/dev/null", dir + "fedora-readonly.service")
    except:
        pass

try:
    os.symlink("/dev/null", dir + "fedora-storage-init.service")
except:
    try:
        os.remove(dir + "fedora-storage-init.service")
        os.symlink("/dev/null", dir + "fedora-storage-init.service")
    except:
        pass

try:
    os.symlink("/dev/null", dir + "plymouth-quit.service")
except:
    try:
        os.remove(dir + "plymouth-quit.service")
        os.symlink("/dev/null", dir + "plymouth-quit.service")
    except:
        pass

try:
    os.symlink("/dev/null", dir + "plymouth-quit-wait.service")
except:
    try:
        os.remove(dir + "plymouth-quit-wait.service")
        os.symlink("/dev/null", dir + "plymouth-quit-wait.service")
    except:
        pass

try:
    os.symlink("/dev/null", dir + "plymouth-read-write.service")
except:
    try:
        os.remove(dir + "plymouth-read-write.service")
        os.symlink("/dev/null", dir + "plymouth-read-write.service")
    except:
        pass

try:
    os.symlink("/dev/null", dir + "plymouth-halt.service")
except:
    try:
        os.remove(dir + "plymouth-halt.service")
        os.symlink("/dev/null", dir + "plymouth-halt.service")
    except:
        pass

try:
    os.symlink("/dev/null", dir + "plymouth-poweroff.service")
except:
    try:
        os.remove(dir + "plymouth-poweroff.service")
        os.symlink("/dev/null", dir + "plymouth-poweroff.service")
    except:
        pass

try:
    os.symlink("/dev/null", dir + "plymouth-reboot.service")
except:
    try:
        os.remove(dir + "plymouth-reboot.service")
        os.symlink("/dev/null", dir + "plymouth-reboot.service")
    except:
        pass

if os.path.exists(dir + "multi-user.target.wants/avahi-daemon.service"):
    os.remove(dir + "multi-user.target.wants/avahi-daemon.service")

if os.path.exists(dir + "multi-user.target.wants/chronyd.service"):
    os.remove(dir + "multi-user.target.wants/chronyd.service")

if os.path.exists(dir + "graphical.target.wants/rtkit-daemon.service"):
    os.remove(dir + "graphical.target.wants/rtkit-daemon.service")

if os.path.exists(dir + "multi-user.target.wants/sendmail.service"):
    os.remove(dir + "multi-user.target.wants/sendmail.service")

if os.path.exists(dir + "multi-user.target.wants/sm-client.service"):
    os.remove(dir + "multi-user.target.wants/sm-client.service")

if os.path.exists(dir + "multi-user.target.wants/NetworkManager.service"):
    os.remove(dir + "multi-user.target.wants/NetworkManager.service")

if os.path.exists(dir + "multi-user.target.wants/sssd.service"):
    os.remove(dir + "multi-user.target.wants/sssd.service")

if os.path.exists(dir + "multi-user.target.wants/lxcf.service"):
    os.remove(dir + "multi-user.target.wants/lxcf.service")

if os.path.exists(dir + "multi-user.target.wants/lxcf-sched.service"):
    os.remove(dir + "multi-user.target.wants/lxcf-sched.service")

remove_chkconfig("K90network")
remove_chkconfig("S10network")

remove_chkconfig("K03rhnsd")
remove_chkconfig("S97rhnsd")

systemd_tune()	         # modify basic.target etc...
eth0_service()	         # bringup eth0 without udev
empty_fstab()	         # make /etc/fstab empty
securetty_tune()         # add pts/0 to securetty
hostname_setting()       # set the hostname file
containername_setting()  # set the container_name file

#
# Generate a Domain Def.
#
domain = """\
<domain type='lxc'>
  <name>%(NAME)s</name>
  <memory unit='KiB'>%(MEMORY)s</memory>
  <vcpu>1</vcpu>
  <os>
    <type arch='%(ARCH)s'>exe</type>
    <init>/sbin/init</init>
  </os>
  <clock offset='utc'/>
  <on_poweroff>destroy</on_poweroff>
  <on_reboot>restart</on_reboot>
  <on_crash>destroy</on_crash>
  <devices>
    <emulator>/usr/libexec/libvirt_lxc</emulator>
    <filesystem type='mount' accessmode='passthrough'>
      <source dir='%(ROOTDIR)s'/>
      <target dir='/'/>
    </filesystem>
    <filesystem type='mount' accessmode='passthrough'>
      <source dir='/usr'/>
      <target dir='/usr'/>
      <readonly/>
    </filesystem>
    <filesystem type='mount' accessmode='passthrough'>
      <source dir='/proc/sys/kernel/msgmax'/>
      <target dir='/proc/sys/kernel/msgmax'/>
    </filesystem>
    <filesystem type='mount' accessmode='passthrough'>
      <source dir='/proc/sys/kernel/msgmnb'/>
      <target dir='/proc/sys/kernel/msgmnb'/>
    </filesystem>
    <filesystem type='mount' accessmode='passthrough'>
      <source dir='/proc/sys/kernel/msgmni'/>
      <target dir='/proc/sys/kernel/msgmni'/>
    </filesystem>
    <filesystem type='mount' accessmode='passthrough'>
      <source dir='/proc/sys/kernel/sem'/>
      <target dir='/proc/sys/kernel/sem'/>
    </filesystem>
    <filesystem type='mount' accessmode='passthrough'>
      <source dir='/proc/sys/kernel/shmall'/>
      <target dir='/proc/sys/kernel/shmall'/>
    </filesystem>
    <filesystem type='mount' accessmode='passthrough'>
      <source dir='/proc/sys/kernel/shmmax'/>
      <target dir='/proc/sys/kernel/shmmax'/>
    </filesystem>
    <filesystem type='mount' accessmode='passthrough'>
      <source dir='/proc/sys/kernel/shmmni'/>
      <target dir='/proc/sys/kernel/shmmni'/>
    </filesystem>
    <interface type='network'>
      <source network='lxcfnet1'/>
    </interface>
    <console type='pty'>
      <target type='lxc' port='0'/>
      <alias name='console0'/>
    </console>
 </devices>
</domain>
""" % {"NAME": domain_name, "ARCH": arch, "MEMORY": "1048576",
       "ROOTDIR": ROOTDIR}

domain_separate = """\
<domain type='lxc'>
  <name>%(NAME)s</name>
  <memory unit='KiB'>%(MEMORY)s</memory>
  <vcpu>1</vcpu>
  <os>
    <type arch='%(ARCH)s'>exe</type>
    <init>/sbin/init</init>
  </os>
  <clock offset='utc'/>
  <on_poweroff>destroy</on_poweroff>
  <on_reboot>restart</on_reboot>
  <on_crash>destroy</on_crash>
  <devices>
    <emulator>/usr/libexec/libvirt_lxc</emulator>
    <filesystem type='mount' accessmode='passthrough'>
      <source dir='%(ROOTDIR)s'/>
      <target dir='/'/>
    </filesystem>
    <filesystem type='mount' accessmode='passthrough'>
      <source dir='/proc/sys/kernel/msgmax'/>
      <target dir='/proc/sys/kernel/msgmax'/>
    </filesystem>
    <filesystem type='mount' accessmode='passthrough'>
      <source dir='/proc/sys/kernel/msgmnb'/>
      <target dir='/proc/sys/kernel/msgmnb'/>
    </filesystem>
    <filesystem type='mount' accessmode='passthrough'>
      <source dir='/proc/sys/kernel/msgmni'/>
      <target dir='/proc/sys/kernel/msgmni'/>
    </filesystem>
    <filesystem type='mount' accessmode='passthrough'>
      <source dir='/proc/sys/kernel/sem'/>
      <target dir='/proc/sys/kernel/sem'/>
    </filesystem>
    <filesystem type='mount' accessmode='passthrough'>
      <source dir='/proc/sys/kernel/shmall'/>
      <target dir='/proc/sys/kernel/shmall'/>
    </filesystem>
    <filesystem type='mount' accessmode='passthrough'>
      <source dir='/proc/sys/kernel/shmmax'/>
      <target dir='/proc/sys/kernel/shmmax'/>
    </filesystem>
    <filesystem type='mount' accessmode='passthrough'>
      <source dir='/proc/sys/kernel/shmmni'/>
      <target dir='/proc/sys/kernel/shmmni'/>
    </filesystem>
    <interface type='network'>
      <source network='lxcfnet1'/>
    </interface>
    <console type='pty'>
      <target type='lxc' port='0'/>
      <alias name='console0'/>
    </console>
 </devices>
</domain>
""" % {"NAME": domain_name, "ARCH": arch, "MEMORY": "1048576",
       "ROOTDIR": ROOTDIR}

xmlfile = "/etc/lxcf/rsc/" + domain_name + "/" + domain_name + ".xml"

with open(xmlfile, "w") as f:
    if options.separate:
        f.write(domain_separate)
    else:
        f.write(domain)

with open(UUID_FILE, "w") as f:
    f.write(UUID)

print("Setup completion of LXCF")

sys.exit(0)
