#!/usr/bin/python
# copyright (C) 2013-2014 FUJITSU LIMITED All Rights Reserved

# 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.
#

# -*- coding: utf-8 -*-

import sys, os, shutil
import fcntl
import ConfigParser
import IPy
from subprocess import Popen, PIPE
from optparse import OptionParser,OptionGroup
import re

CONFIG_FILE = '/etc/lxcf/lxcf.conf'
HOSTS_FILE = '/etc/hosts'

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)

argc = len(args)

if (argc != 2) :
  print "usage : lxcf setup LXCNAME"
  quit()

domain_name = args[1]

#
# directories created at domain creation 
#

ROOTDIR= "/opt/lxcf/" + domain_name

class InstallInfo :
    def __init__(self) :
        self.DIRS = []
        self.BINDDIRS = []
        self.TMPFS = []
        self.SYMLINKS=[]
        self.PACKAGES = []
        self.FILES = []
        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
#

#
# erase IP addres from /etc/hosts
#

def erase_ipaddr(name, hosts_file) :
  # 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()

#
# find IP addres from /etc/hosts
#

def find_ipaddr(name, hosts_file) :
  # 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)

#
# check IP address in /etc/hosts
#

def check_ipaddr(ip, hosts_file) :
  # 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

#
# set new IP address 
#

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

#
# get new IP address 
#

def get_ipaddr(hosts_file):
  # 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

#
# Functions for chkconfig
#

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

#
# Functions for workarounds.
#

#
# create our own basic.target for avoid some startups.
#
def systemd_tune() :
    #
    # we need to avoid some special services by systemd.
    # modify basic.target and avoid them.
    #
    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)

    # print "Creating %s" % filename

    filename = ROOTDIR + "/etc/systemd/system/basic.target.wants"
    try:
      os.makedirs(filename)
    except:
      # print "dir exists : " + filename
      pass

    # print "Creating %s" % filename

    #
    # 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")

    # print "Creating /usr/lib/systemd/system/getty@.service" 


#
# Create ifcfg-eth0 and add service to bring up it.
#
def eth0_service() :
    filename = ROOTDIR + "/etc/sysconfig/network"
    data="""NETWORKING=yes
HOSTNAME=""" + domain_name + '\n' + """GATEWAY=192.168.125.1
"""

    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)

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

    # print "Creating %s" % filename
   
    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)

    # print "Creating %s" % filename

    #
    # Bring up this.
    #
    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)

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

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

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

#
# set container name and kind
#
def containername_setting() :
    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)
    # print "create container_name : " + path

#
# Main routine starts here !
#

#
# Check Domain name is passed.
#
if (domain_name == '_unknown') :
    print "Guest Domain name must be specified"
    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 (not os.path.exists(guestpath)) :
            # print "Creating dir %s" % (guestpath)
            try:
              os.makedirs(guestpath)
            except:
              #print "dir exists : "+guestpath
              pass
    elif (ents[1] == 'link') :
        if (not os.path.exists(guestpath)) :
            # print "Creating symlink %s => %s" % (guestpath, ents[2])
            try:
              os.symlink(ents[2], guestpath)
            except:
              #print "sym link exists : "+guestpath
              pass
    elif (ents[1] == 'file') :
        if (not os.path.exists(guestpath) or 
            file_is_newer(ents[0], guestpath)) :
            # print "Copyfile %s" % (guestpath)
            try:
              shutil.copy(ents[0], guestpath)
            except:
              #print "file exists : "+guestpath
              pass


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

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

# print "disabled console-shell.service"
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

# print "disabled display-manager.service"
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

# print "disabled fedora-readonly.service"
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

# print "disabled fedora-storage-init.service"
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

# print "disabled plymouth-quit.service"
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

# print "disabled plymouth-quit-wait.service"
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

# print "disabled plymouth-read-write.service"
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

# print "disabled plymouth-halt.service"
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

# print "disabled plymouth-poweroff.service"
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

# print "disabled plymouth-reboot.service"
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


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

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

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

# print "disabled sendmail.service & sm-client.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")

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

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

# print "disabled network.service"
remove_chkconfig("K90network")
remove_chkconfig("S10network")

# print "disabled rhnsd.service"
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 = r"""
<domain type='lxc'>
  <name>%(NAME)s</name>
  <memory unit='KiB'>%(MEMORY)s</memory>
  <vcpu>1</vcpu>
  <os>
    <type arch='x86_64'>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>
    <interface type="network">
      <source network="lxcfnet1"/>
    </interface>
    <console type='pty'>
      <target type='lxc' port='0'/>
      <alias name='console0'/>
    </console>
 </devices>
</domain>
""" % {'NAME':domain_name,
       'MEMORY':1048576,
       'ROOTDIR':ROOTDIR}

domain_separate = r"""
<domain type='lxc'>
  <name>%(NAME)s</name>
  <memory unit='KiB'>%(MEMORY)s</memory>
  <vcpu>1</vcpu>
  <os>
    <type arch='x86_64'>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>
    <interface type="network">
      <source network="lxcfnet1"/>
    </interface>
    <console type='pty'>
      <target type='lxc' port='0'/>
      <alias name='console0'/>
    </console>
 </devices>
</domain>
""" % {'NAME':domain_name,
       'MEMORY':1048576,
       'ROOTDIR':ROOTDIR}

xmlfile = ROOTDIR + "/etc/lxcf/rsc/" + domain_name + "/" + domain_name + ".xml"
# print "create " + xmlfile 

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


print "Setup completion of LXCF"
