#!/usr/bin/env python

# Copyright C 2002-2008 by Isilon Systems, Inc. All rights not expressly
# granted herein are reserved.
# Created in the United States of America.
# 
# This digital media is protected by U.S. and international copyright and
# intellectual property laws. Unless otherwise specified, all information
# and screens appearing as part of this digital medium, including
# software, services, documents, text, images, icons, and logos design;
# the selection, assembly, arrangement, and design thereof; and the code
# that enables its presentation, are the sole property of Isilon Systems,
# Inc.
# 
# Except as otherwise required by applicable law, no part of any
# copyrighted material may be reproduced, modified, translated,
# distributed, published, or transmitted by any means, electronic or
# mechanical, for any purpose, without the prior written consent of Isilon
# Systems. 
#  
# Isilon and Isilon Systems are registered trademarks of Isilon Systems,
# Inc.  Legion is a trademark of Isilon Systems, Inc.  All other
# trademarks and registered trademarks are the property of their
# respective holders.

# This script makes a tar of all the files in boot/.files, along with the local
# patch record and files added from patches.  It sends the tarball out stdout.
# It assumes no filenames either in the original install, the patch
# files or anywhere in the local patch store contain tab characters or newline
# characters.  
import isi.misc
import isi.ui.lib.pkg as pkg
import os
import os.path
import isi.sys.update.util as util
import subprocess
import syslog
import sys
import traceback



def exit_with_error(errorstring, errorcode):
    syslog.syslog(syslog.LOG_ERR, "isi_create_installtar error: " + errorstring)
    os._exit(errorcode)

def tryopen(filename, options):
    try:
        return open(filename, options)
    except IOError, (errornumber, errorstring):
        exit_with_error("Failed to open %s, I/O error (%d): %s" % (filename, 
                errornumber, errorstring), os.EX_IOERR)

def trim_missing(current):
    tmp = current + '.tmp'
    orig = current + '.orig'

    currentfile = tryopen(current, 'r')
    tmpfile = tryopen(tmp, 'w')
    for line in currentfile.readlines():
        name = line.split('\t')[0].strip()
        fname = os.path.join('/', name)
        if os.path.islink(fname) or os.path.exists(fname):
            tmpfile.write(line)

    currentfile.close()
    tmpfile.close()
    if not os.path.exists(orig):
        os.rename(current, orig)
    os.rename(tmp, current)

if __name__ == '__main__':
    try:
        if isi.misc.pkg_list_installing() != [] or \
                isi.misc.pkg_list_installing_local() != []:
            exit_with_error("Cannot create install.tar, there are some partially "
            "installed patches on the source node.", os.EX_UNAVAILABLE)
        lockfile = "/boot/MD5_LOCK"
        patchkeventfile = "/var/db/patch_kevent_file"
        dotfiles = "/boot/.files"
        dotMD5 = "/boot/.md5"
        mylist = map(lambda a: {"name": a, "kind":"file"}, 
                pkg.Pkg.get_patched_files(local = True))
        cmd = "/usr/bin/tar -C /"
        cmd +=  " -T /boot/.files --format=ustar --one-file-system -ncf -"
        last_patch_time = os.path.getmtime(patchkeventfile);
        for root, dirs, files in os.walk("/var/db/isilon-pkg"):
            for name in files:
                mylist.append({"name": os.path.join(root, name), "kind":"file"})
            for name in dirs:
                mylist.append({"name": os.path.join(root, name), "kind":"dir"})


        #Removes starting /
        for i in range(len(mylist)):
            if mylist[i]["name"][0] == "/":
                mylist[i]["name"] = mylist[i]["name"][1:]

        # Aquire exlusive lock so that multiple instances of this script don't 
        # interfere with each other.
        modified_md5 = False

        try:
            lock = os.open(lockfile, os.O_CREAT | os.O_EXLOCK, 0400)
        except IOError, (errornumber, errorstring):
            exit_with_error("Failed to create %s, I/O error (%d): %s" % (filename, 
                    errornumber, errorstring), os.EX_IOERR)


        trim_missing(dotfiles)
        trim_missing(dotMD5)

        if mylist:
            newfiles = []
            files = tryopen(dotfiles, "r")
            # This will break horribly if any filenames have newlines or tabs.
            lines = files.readlines()
            for item in mylist:
                if not item["name"] + "\n" in lines:
                    newfiles.append(item["name"] + "\n")
            files.close()
            appendfiles = tryopen(dotfiles, "a")
            for item in newfiles:
                appendfiles.write(item)
            appendfiles.close()
            md5file = tryopen(dotMD5, "r")
            lines = md5file.readlines()
            newfiles = []
            filenames = []
            for line in lines:
                filenames.append(line.split('\t')[0])
            for item in mylist:
                # No directories allowed in the .md5 file
                if item["kind"] == "file" and not item["name"] in filenames:
                    newfiles.append(item["name"] + "\t0\n")
                    modified_md5 = True
            md5file.close()
            appendmd5 = tryopen(dotMD5, "a")
            for item in newfiles:
                appendmd5.write(item)
            appendmd5.close()


        md5 = util.Md5("/")

        error = md5.regen_md5()
        if not error == None:
            exit_with_error("Failed to regen the md5: " + error, os.EX_IOERR) 

        os.close(lock)
        tarproc = subprocess.Popen(cmd, shell = True, stderr = subprocess.PIPE)
        stderroutput = tarproc.stderr.readlines()
        exitcode = tarproc.wait()
        if exitcode:
            syslog.syslog(syslog.LOG_ERR, "isi_create_installtar error: tar "
                    " command failed\n")
            if len(stderroutput) <= 15:             
                for line in stderroutput:
                    syslog.syslog(syslog.LOG_ERR, line)
            else:
                syslog.syslog(syslog.LOG_ERR, "%d lines of output, first 15 are \n"
                    % len(stderroutput))
                for i in range(15):
                    syslog.syslog(syslog.LOG_ERR, stderroutput[i])
            os._exit(os.EX_UNAVAILABLE)
        new_patch_time = os.path.getmtime(patchkeventfile);
        if new_patch_time != last_patch_time:
            syslog.syslog(syslog.LOG_ERR, "isi_create_installtar error: Patch "
            "activity while creating install.tar.  Created install.tar is invalid")
            os._exit(os.EX_TEMPFAIL);
    except:
        tb_list = traceback.format_exception(sys.exc_type, sys.exc_value, 
                sys.exc_traceback)
        tb = ""
        for line in tb_list:
            tb +=line
        exit_with_error((tb), os.EX_UNAVAILABLE) 
