403Webshell
Server IP : 158.178.228.73  /  Your IP : 80.80.80.153
Web Server : Apache/2.4.37 (Oracle Linux Server) OpenSSL/1.1.1k
System : Linux ust-wp1-prod 5.15.0-308.179.6.el8uek.x86_64 #2 SMP Wed Apr 23 10:46:57 PDT 2025 x86_64
User : tomasFtp ( 1007)
PHP Version : 8.4.8
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : ON  |  Sudo : ON  |  Pkexec : ON
Directory :  /proc/thread-self/root/proc/self/root/proc/2802828/root/sbin/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /proc/thread-self/root/proc/self/root/proc/2802828/root/sbin/uptrack-show
#!/usr/libexec/platform-python -s

# See included LICENSE file for additional license and copyright information.

from __future__ import print_function
import sys
from six.moves import input
# Disable Launchpad's apport traceback hooks
sys.excepthook = sys.__excepthook__

from io import StringIO, BytesIO  # noqa: #402
if sys.version_info[0] < 3:
    from cStringIO import StringIO as NativeStringIO
else:
    from io import StringIO as NativeStringIO
import datetime
import errno  # noqa: #402
import fcntl  # noqa: #402
import grp  # noqa: #402
import hashlib  # noqa: #402
import logging  # noqa: #402
import logging.handlers  # noqa: #402
import os  # noqa: #402
import posixpath  # noqa: #402
import pwd  # noqa: #402
import pycurl  # noqa: #402
import random  # noqa: #402
import re  # noqa: #402
import shutil  # noqa: #402
import signal  # noqa: #402
import stat  # noqa: #402
import subprocess  # noqa: #402
import tempfile  # noqa: #402
import textwrap  # noqa: #402
import time  # noqa: #402
import traceback  # noqa: #402
from six.moves.urllib import parse  # noqa: #402
import six  # noqa: #402
import yaml  # noqa: #402
from optparse import OptionParser, Option, SUPPRESS_HELP  # noqa: #402

# Specially deal with dbus
have_dbus = 0
try:
    import dbus
    import dbus.service
    import dbus.mainloop.glib
    if dbus.version >= (0, 80, 0):
        from dbus.mainloop.glib import DBusGMainLoop
        DBusGMainLoop(set_as_default=True)
    dbus.SystemBus()
    have_dbus = 1
except Exception:
    pass

import Uptrack  # noqa: #402
import UptrackDepSolver  # noqa: #402

from Uptrack import is_offline

from uptrack import security_modules_check # noqa: #402

try:
    from offline.Offline import *
except:
    from OfflineStub import *

__version__ = Uptrack.__version__
LOGFILE = '/var/log/uptrack.log'
LOGUSER = 'root'
LOGGROUP = 'adm'
LOGMODE = 0o640
KEYRING = '/usr/share/uptrack/uptrack.gpg'
UPTRACK_GPG_HOMEDIR = '/etc/uptrack'
SERVER_KEYRING = '/usr/share/uptrack/uptrack-server.gpg'
SERVER_KEY_FINGERPRINT = "9C99586684B64DE53F0885700EE0EADBD74EE7FC"
API_VERSION_FILE = "/usr/share/uptrack/ksplice-tools-api-version"
KSPLICE_DEBUG_FILE = '/var/run/ksplice/debug'
MODPROBE_LOCK = '/var/run/.modprobe.ksplice.lock'
DEPMOD_NEEDED_FILE = os.path.join(Uptrack.UPTRACK_CACHE_DIR, "depmod-needed")
UPTRACK_PACKAGES_PROTOCOL_VERSION = '2'
CODE_BUSY_RETRIES = 2  # for a total of 3 tries
CODE_BUSY_MAX_DELAY = 5.0  # seconds
MAX_RETRIES = CODE_BUSY_RETRIES + 1  # 1 for trying to remove modules

# What should this be?
HTTP_CODE_EXPIRED = 420

# The number of seconds to wait before giving up on acquiring the
# repository lock.
LOCK_TIMEOUT = 10

INIT = 'Init'
UPGRADE = 'Upgrade'

# Note: We depend on these constants having these particular values,
# because they occasionally get shown directly to users. (Probable
# future i18n implications here)
INSTALL = 'Install'
REMOVE = 'Remove'
SHOW = 'Show'

AUTOGEN_FLAG_FILE = '/var/lib/uptrack/autogen'
AUTOGEN_URL = 'https://updates.ksplice.com/cgi/code?terms=1&noninteractive=1&noemail=1'
TOS_FILE = '/usr/share/doc/uptrack/tos'

alert = None
desupported = False
tray_icon_error = None

config = None
local = None
repo = None
in_offline_mode = False

HASH_PRIORITIES = ['SHA-256', 'SHA-1']

now = datetime.datetime.now(datetime.timezone.utc)

def hash_valid(actual, expected):
    for hash_type in HASH_PRIORITIES:
        if hash_type in expected and expected[hash_type]:
            return actual[hash_type] == expected[hash_type]
    return False


def hash_file(filename):
    text = Uptrack.read_file(filename, 'rb')
    hashers = {
        'SHA-1': hashlib.sha1,
        'SHA-256': hashlib.sha256
    }

    hashes = {}
    for name, fn in hashers.items():
        hashes[name] = fn(text).hexdigest()

    return hashes


def makeUpdate(item, local_dir, remote_dir, order):
    return Update(local_dir=local_dir,
                  remote_dir=remote_dir,
                  id=item['ID'],
                  filename=item['Filename'],
                  name=item['Name'],
                  sha1=item['SHA-1'],
                  sha256=item.get('SHA-256', None),
                  targets=item['Targets'],
                  order=order)


def toModuleName(filename, canonicalize=True):
    # Logic matches smells_like_module from module-init-tools' depmod.c.
    if filename[-3:] != '.ko' and filename[-6:] != '.ko.gz':
        return None
    # Logic matches filename2modname from module-init-tools' modprobe.c.
    module_name = os.path.basename(filename).split('.')[0]
    if canonicalize:
        module_name = module_name.replace('-', '_')
    return module_name


def getLoadedModules():
    result = {}
    with open('/proc/modules') as f:
        for line in f:
            # Format:
            #
            # iwlmvm 1294336 - - Live 0x0000000000000000
            # ksplice_pwt2yjrj_vmlinux_new 24576 1 - Live 0xffffffffa4c2d000 (O)
            # ksplice_pwt2yjrj 204800 2 ksplice_pwt2yjrj_vmlinux_new, Live 0xffffffffa4e09000 (O)
            # xen_blkback 45056 1 ksplice_ka80p57r_xen_blkback_new, Live 0xffffffffa0e6e000 (E)

            name, size, use_count, used_by = line.rstrip('\n').split(' ')[:4]
            result[name] = {
                'Size': size,
                'UseCount': 0 if use_count == '-' else int(use_count),
                'UsedBy': [] if used_by == '-' else used_by.split(',')[:-1],
            }

    return result


def rmmod(module):
    """
    Attempt to unload the given module.
    Returns True if successful.
    """
    logging.debug("Trying to rmmod %s" % module)
    p = subprocess.Popen(['rmmod', module], stdout=subprocess.PIPE,
                         stderr=subprocess.STDOUT)
    logging.debug(p.communicate()[0].decode('utf-8'))
    return (p.returncode == 0)


def get_modprobe_lock():
    perms = 0o600
    lock = getLock(MODPROBE_LOCK, create_mode=perms)
    if lock is False:
        logging.debug("Unable to acquire the Uptrack modprobe.ksplice lock: %s",
                      MODPROBE_LOCK)
        logging.error("""\
It appears that a modprobe process is currently running on this
system. Please wait a minute and try again.  If you are unable to
resolve this issue, please contact %s.""" % (Uptrack.BUG_EMAIL,))
        sys.exit(1)

    mode = os.fstat(lock).st_mode

    # Fixup permissions to 600 if they're wrong
    if (mode & 0o777) != perms:
        os.fchmod(lock, perms)

    return lock

def modprobe_lock(func):
    def run_func_with_modprobe_lock(*args, **kwargs):
        lock = get_modprobe_lock()
        try:
            return func(*args, **kwargs)
        finally:
            releaseLock(lock)
    return run_func_with_modprobe_lock

def python_six_unicode_hack(klass):
    if six.PY2 and 'python_2_unicode_compatible' in dir(six):
        return six.python_2_unicode_compatible(klass)
    return klass

def get_dmesg_output(current_ts):
    try:
        result = subprocess.run(["dmesg"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True, universal_newlines=True)
        # regex to capture timestamps and the rest of the line
        pattern = re.compile(r'^\[\s?([0-9]+\.[0-9]+)\]\s+(.*)$')
        filtered_lines = []
        for line in result.stdout.splitlines():
            match = pattern.match(line)
            if match:
                ts = float(match.group(1))
                # capture dmesg message around timestamp
                if ts > (current_ts * 2):
                    filtered_lines.append(line)
        return filtered_lines
    except subprocess.CalledProcessError as e:
        logging.error("Failed to capture dmesg output: %s",e)
        return None

class TimeoutException(Exception):
    pass

def timeout_handler(sig, frame):
    raise TimeoutException()

def run_apply_with_timeout(update, cmd, timeout=120):
    """
    Logic to determine if an update is in a hung (timed out) state

    - if no timeout occurs, this function returns the result of runKspliceCommand()
    - if a timeout occurs, it returns an ActionResult() with a -1 code, an unexpected
    abort status, and captures the dmesg message around the current timestamp
    """
    if timeout <= 0:
        raise ValueError("Timeout value must be a positive integer")
    signal.signal(signal.SIGALRM, timeout_handler)
    signal.alarm(timeout)
    # ensure that res is defined up front incase a timeout happens before its created
    res = Uptrack.ActionResult(update, cmd)  
    try:
        # timestamp to filter out dmesg messages later if needed
        current_ts = time.clock_gettime(time.CLOCK_MONOTONIC)
        res = update.runKspliceCommand(cmd,
                                        ['/usr/lib/uptrack/ksplice-apply', '--partial', update.tree_path])
        signal.alarm(0) # cancel the alarm since the command ran successfully
        return res
    except TimeoutException:
        logging.debug("The process applying the update has failed to complete, check the debug logs")
        dmesg_output = get_dmesg_output(current_ts)
        if dmesg_output is not None:
            logging.debug(dmesg_output)
        res.code = -1
        res.abort_code = "timeout"
        return res
    finally:
        signal.alarm(0)

@python_six_unicode_hack
class Update(object):
    def __init__(self, id, name, filename, sha1, sha256,
                 targets, local_dir, remote_dir,
                 order):
        self.id = id
        self.name = name
        self.filename = filename
        self.verify_hashes = {}
        self.verify_hashes['SHA-1'] = sha1
        self.verify_hashes['SHA-256'] = sha256
        self.targets = targets[:]
        self.local_dir = local_dir
        self.remote_dir = remote_dir
        self.order = order

    def __str__(self):
        return "[%s] %s" % (self.id, self.name)

    def __repr__(self):
        return self.__str__()

    def __eq__(self, other):
        return self.id == other.id

    def __hash__(self):
        return hash(self.id)

    def _remote_path(self):
        return posixpath.join(self.remote_dir, self.filename)
    remote_path = property(_remote_path)

    def _local_path(self):
        return os.path.join(self.local_dir, self.filename)
    local_path = property(_local_path)

    def _tree_path(self):
        return os.path.join(self.local_dir, 'updates', 'ksplice-' + self.id)
    tree_path = property(_tree_path)

    def _tree_flag_path(self):
        return os.path.join(self.local_dir, 'updates', 'ksplice-' + self.id + '.incomplete')
    tree_flag_path = property(_tree_flag_path)

    def get_verify_hashes(self):
        return self.verify_hashes

    def checkValidFile(self):
        if not os.path.isfile(self.local_path):
            return Uptrack.Result(1, "Update file does not exist for update %s" % (self.id,))
        try:
            file_hash = hash_file(self.local_path)
        except IOError:
            logging.debug(traceback.format_exc())
            return Uptrack.Result(1, "Unable to read update file %s" % (self.local_path,))
        verify_hash = self.get_verify_hashes()
        if not hash_valid(file_hash, verify_hash):
            mismatches = []
            for algo in HASH_PRIORITIES:
                if algo in verify_hash:
                    mismatches.append('{algo}(got {got}, expected {expected})'.format(algo=algo,
                                                                                      got=file_hash[algo],
                                                                                      expected=verify_hash[algo]))
            logging.debug("%s: invalid checksum: %s"
                          % (self.id, ', '.join(mismatches)))
            return Uptrack.Result(1, "Invalid checksum for update %s." % (self.id,))
        if (not os.path.isdir(self.tree_path)
                or os.path.isfile(self.tree_flag_path)):
            return Uptrack.Result(1, "Update %s has not been unpacked." % (self.id,))
        return Uptrack.Result()

    def isValidFile(self):
        return self.checkValidFile().code == 0

    def unpack(self):
        def fix_sigpipe():
            signal.signal(signal.SIGPIPE, signal.SIG_DFL)

        open(self.tree_flag_path, "w")
        shutil.rmtree(self.tree_path, ignore_errors=True)
        p = subprocess.Popen(['tar', '--no-same-owner', '--force-local',
                              '-xzf', self.local_path,
                              '-C', os.path.join(self.local_dir, 'updates')],
                             stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
                             preexec_fn=fix_sigpipe)
        output = p.communicate()[0].decode('utf-8')
        if p.returncode:
            return Uptrack.Result(p.returncode, output)
        os.unlink(self.tree_flag_path)
        return Uptrack.Result()

    def getDetails(self):
        details = os.path.join(self.tree_path, 'details')
        if not os.path.isfile(details):
            details = os.path.join(self.tree_path, 'patch')
        try:
            return Uptrack.read_file(details)
        except IOError:
            logging.debug("Could not retrieve details from %s" % self)
            logging.debug(traceback.format_exc())
            return ''

    def getCoreVersion(self):
        # update is applied using ksplice standalone
        ksplice_dir = '/sys/module/ksplice_%s/ksplice' % self.id
        if os.path.exists(ksplice_dir):
            core_version_file = os.path.join(ksplice_dir, 'core_version')
            if os.path.exists(core_version_file):
                return Uptrack.read_file(core_version_file)
            else:
                return '0'

        # update is applied using ksplice integrated
        ksplice_dir = '/sys/kernel/ksplice/%s' % self.id
        if os.path.exists(ksplice_dir):
            core_version_file = os.path.join(ksplice_dir, 'core_version')
            if os.path.exists(core_version_file):
                return Uptrack.read_file(core_version_file)
            else:
                return '0'

        # update is not applied, so grab the data from tree_path
        core_version_file = os.path.join(self.tree_path, 'core_version')
        if os.path.exists(core_version_file):
            return Uptrack.read_file(core_version_file)
        else:
            return '0'

    def lockedModules(self, loadedModules=None):
        locked = []
        modules = loadedModules
        if not modules:
            modules = getLoadedModules()
        targets = [toModuleName(x) for x in self.targets]
        for t in targets:
            if t in modules and 'ksplice_%s_%s_new' % (self.id, t) not in modules:
                locked.append(t)
        return locked

    def isLocked(self, loadedModules=None):
        return len(self.lockedModules(loadedModules)) != 0

    def runKspliceCommand(self, command, args):
        res = Uptrack.ActionResult(self, command)
        p = subprocess.Popen(args,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.STDOUT)

        output = p.communicate()[0].decode('utf-8')
        if p.returncode == 0:
            return res

        Uptrack.mkdirp(os.path.dirname(KSPLICE_DEBUG_FILE))
        p = subprocess.Popen(args + ['--debugfile', KSPLICE_DEBUG_FILE, "--raw-errors"],
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE)
        lines = p.communicate()[1].decode('utf-8').split("\n")
        if p.returncode == 0:
            # Cool, it worked this time.
            try:
                os.remove(KSPLICE_DEBUG_FILE)
            except OSError:
                pass
            return res

        res.code = p.returncode
        res.abort_code = lines[0]
        res.message = output
        try:
            res.debug = Uptrack.read_file(KSPLICE_DEBUG_FILE, 'rb')
        except IOError:
            pass

        if res.abort_code == 'code_busy':
            res.stack_check_processes = [line.split(" ") for line in lines[1:] if len(line.strip())]
        elif res.abort_code == 'cold_update_loaded':
            res.locked_modules = self.lockedModules()
        elif res.abort_code == 'failed_to_find' or res.abort_code == 'no_match':
            # Compute the list of modules loaded and affected by this
            # upgrade, for later display

            res.nomatch_modules = []

            # This code is a bit of a hack because Ksplice itself
            # doesn't export which module was responsible for the
            # failure
            target_modules = [toModuleName(x) for x in self.targets]

            logging.debug("Run/pre matching failed; targets were:")
            logging.debug(target_modules)

            res.nomatch_modules = list(set(getLoadedModules()).intersection(target_modules))

            logging.debug("Loaded target modules were:")
            logging.debug(res.nomatch_modules)

            # get list and check if there are any conflicting security modules
            loaded_modules = list(getLoadedModules().keys())
            flagged_modules = security_modules_check.conflicting_modules_check(loaded_modules)
            if flagged_modules:
                logging.warning(
                    "Conflicting 3rd party security module(s) found:\n* %s\n" 
                    "Please see knowledge based article 2733385.1 at support.oracle.com",
                    "\n* ".join(flagged_modules)
                )   
        elif res.abort_code == 'module_busy':
            usedby_modules = set()

            for modname, modinfo in getLoadedModules().items():
                if modname == 'ksplice_' + self.id or modname.startswith('ksplice_' + self.id + '_'):
                    usedby_modules |= set([d for d in modinfo['UsedBy'] if not d.startswith('ksplice_')])

            res.usedby_modules = list(usedby_modules)

        return res

    def shouldRetry(self, res, duration, retryData):
        """
        Logic for deciding whether to retry a runKspliceCommand.

        'retryData' is a dict which stores information on the number
        of times we have retried the command due to various causes.
        The caller can pass a new empty dict the first time it tries
        to execute a given command.

        shouldRetry() returns one of the strings 'Success', 'Failure',
        or 'Retry'.  In the 'Retry' case, the 'retryData' argument
        will have been modified to reflect the retry that occurred.
        """

        if 'DidRfcommRetry' not in retryData:
            # Have we tried to rmmod rfcomm and then retry?
            retryData['DidRfcommRetry'] = False
        if 'CodeBusyRetries' not in retryData:
            # number of previous retries due to other code_busy results
            retryData['CodeBusyRetries'] = 0
        if 'RemoveModulesRetries' not in retryData:
            # number of previous retries due to no_match/failed_to_find
            retryData['RemoveModulesRetries'] = 0

        if not res.code:
            return 'Success'
        elif res.abort_code == 'code_busy':
            rfcomm_stack_check = 'krfcommd' in [proc[0] for proc in res.stack_check_processes]
            if rfcomm_stack_check and not retryData['DidRfcommRetry']:
                logging.debug("Stack check against rfcomm module; trying to remove")
                rmmod('rfcomm')
                time.sleep(1)
                retryData['DidRfcommRetry'] = True
                return 'Retry'
            elif duration > CODE_BUSY_MAX_DELAY:
                logging.debug("Slow stack check failure")
                return 'Failure'
            elif retryData['CodeBusyRetries'] < CODE_BUSY_RETRIES:
                logging.debug("Stack check failure %d, retrying" % (retryData['CodeBusyRetries'] + 1))
                time.sleep(1)
                retryData['CodeBusyRetries'] += 1
                return 'Retry'
            else:
                return 'Failure'
        elif res.abort_code in ['no_match', 'failed_to_find'] \
                and retryData['RemoveModulesRetries'] < 1 and config.removableModules:
            loadedModules = getLoadedModules()
            targetModules = [toModuleName(t) for t in self.targets]
            # Preserve the order of config.removableModules,
            # which is the correct order to remove the modules in
            removableModules = [m for m in config.removableModules if m in loadedModules and m in targetModules]
            modulesRemoved = []
            if not config.no_rmmod:
                for module in removableModules:
                    if rmmod(module):
                        modulesRemoved += [module]
            if modulesRemoved:
                logging.debug("Removed modules " + str(modulesRemoved) + ", retrying")
                retryData['RemoveModulesRetries'] += 1
                return 'Retry'
            else:
                return 'Failure'
        elif res.abort_code == 'key_not_available':
            logging.debug("Signing key was not found in the kernel")
            return 'Failure'
        else:
            return 'Failure'

    @modprobe_lock
    def applyUpdate(self):
        cmd = INSTALL

        r = self.checkValidFile()
        if r.code:
            res = Uptrack.ActionResult(self, cmd)
            res.code = r.code
            res.message = r.message
            return res

        retryData = {}
        for previousRetries in range(0, MAX_RETRIES + 1):
            starttime = time.time()
            res = run_apply_with_timeout(self, cmd)
            endtime = time.time()
            cont = self.shouldRetry(res, endtime - starttime, retryData)
            if cont == 'Success':
                break
            elif cont == 'Retry' and previousRetries < MAX_RETRIES:
                pass  # retry
            else:
                return res

        # finally, on-disk application

        # Make sure that modprobe uses the patched modules when they
        # are (re-)loaded
        if not self.targets:
            res.depmod_needed = False
            return res
        modroot = "/var/run/ksplice/modules/%s" % config.release
        backupdir = "/var/run/ksplice/modules.old/%s" % config.release
        moddir = os.path.join(modroot, "ksplice")
        Uptrack.mkdirp(moddir)
        Uptrack.mkdirp(backupdir)
        targets = dict([(toModuleName(x), toModuleName(x, canonicalize=False)) for x in self.targets])
        try:
            update_modroot = os.path.join(self.tree_path, 'modules')
            for dirname, _, filenames in os.walk(update_modroot):
                for filename in filenames:
                    canonical_target = toModuleName(filename)
                    if canonical_target is None or canonical_target not in targets:
                        continue
                    target_proper_casing = targets.pop(canonical_target)
                    modpath = ("%s/ksplice/%s.ko" % (modroot,
                                                     target_proper_casing))
                    if os.path.isfile(modpath):
                        backup = os.path.join(backupdir,
                                              "%s_pre_%s.ko" % (target_proper_casing,
                                                                self.id))
                        os.rename(modpath, backup)
                    os.symlink(os.path.join(update_modroot, dirname, filename),
                               modpath)
        except IOError:
            res.code = Uptrack.ERROR_GENERIC_ERROR
            res.message = "Failure in extracting modules from %s" % self
            logging.debug(res.message)
            logging.debug(traceback.format_exc())
            return res
        # Some of the target modules that were affected by this patch
        # were not found in the modroot/modprobe path
        if targets:
            res.code = Uptrack.ERROR_GENERIC_ERROR
            res.message = ("Could not retrieve some modules from %s:\n" % self
                           + " missing " + " ".join(targets))
            logging.debug(res.message)
            return res

        depmod_dir = "/var/run/ksplice/depmod.d"
        Uptrack.mkdirp(depmod_dir)
        for target in [toModuleName(x, canonicalize=False) for x in self.targets]:
            depmod_file = "%s/%s.conf" % (depmod_dir, target)
            if not os.path.exists(depmod_file):
                entry = ("override %s %s.ksplice-updates ksplice\n"
                         % (target, config.release))
                logging.debug("Adding new depmod entry: %s" % entry.strip())
                Uptrack.write_file(depmod_file, entry)
        res.depmod_needed = True
        return res

    def undoUpdate(self):
        cmd = REMOVE

        retryData = {}
        for previousRetries in range(0, MAX_RETRIES + 1):
            starttime = time.time()
            res = self.runKspliceCommand(cmd,
                                         ['/usr/lib/uptrack/ksplice-undo', self.id])
            endtime = time.time()
            cont = self.shouldRetry(res, endtime - starttime, retryData)
            if cont == 'Success':
                break
            elif cont == 'Retry' and previousRetries < MAX_RETRIES:
                pass  # retry
            else:
                return res

        # Reverse on-disk application
        if not self.targets:
            res.depmod_needed = False
            return res
        depmod_dir = "/var/run/ksplice/depmod.d"
        modroot = "/var/run/ksplice/modules/%s" % config.release
        backupdir = "/var/run/ksplice/modules.old/%s" % config.release
        for target in [toModuleName(x, canonicalize=False) for x in self.targets]:
            depmod_file = "%s/%s.conf" % (depmod_dir, target)
            modpath = "%s/ksplice/%s.ko" % (modroot, target)
            backup = os.path.join(backupdir, "%s_pre_%s.ko" % (target, self.id))
            if os.path.isfile(modpath):
                os.remove(modpath)
            else:
                logging.warning("Missing patched module %s while undoing %s"
                                % (modpath, self.id))
            if os.path.isfile(backup):
                try:
                    os.rename(backup, modpath)
                except IOError:
                    logging.warning("Failed to restore old module %s -> %s" %
                                    (backup, modpath))
                    logging.debug(traceback.format_exc())
                # Don't remove the depmod entry, since someone is using it
            elif os.path.isfile(depmod_file):
                try:
                    logging.debug("Removing depmod entry: %s" % Uptrack.read_file(depmod_file).strip())
                    os.remove(depmod_file)
                except IOError:
                    logging.warning("Failed to remove depmod file %s" %
                                    (depmod_file,))
                    logging.debug(traceback.format_exc())
            else:
                logging.warning("Missing depmod override file %s while undoing %s"
                                % (depmod_file, self.id))
        res.depmod_needed = True
        return res


def kspliceToolsApiVersion():
    if os.path.exists(API_VERSION_FILE):
        return Uptrack.read_file(API_VERSION_FILE).strip()
    return '-1'


def serverFingerprint(url):
    try:
        netloc = parse.urlparse(url)[1]
        if ':' not in netloc:
            netloc += ":443"
        conn = subprocess.Popen(['openssl', 's_client', '-connect', netloc],
                                stdin=open('/dev/null'),
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE)
        x509 = subprocess.Popen(['openssl', 'x509', '-fingerprint', '-sha1'],
                                stdin=conn.stdout,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE)
        x509.wait()
        for line in x509.stdout.read().decode('utf-8').split('\n'):
            if line.lower().startswith("sha1 fingerprint="):
                return line[len("SHA1 Fingerprint="):].strip()
    except subprocess.CalledProcessError:
        logging.debug("Error reading server certificate fingerprint.")
        logging.debug(conn.stderr.read().decode('utf-8'))
        logging.debug(x509.stderr.read().decode('utf-8'))
        return None
    return None

def parse_version_date(s):
    """
    Example dates that we may have to parse:

    #2 SMP Tue Aug 18 13:41:50 PDT 2020
    #39-Ubuntu SMP Mon Mar 9 10:34:11 UTC 2020
    #1 SMP Debian 4.9.88-1+deb9u1 (2018-05-07)
    """

    months = 'Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'.split()
    months_map = dict([(mon, 1 + i) for i, mon in enumerate(months)])

    # strptime() relies on the current locale... we'll have to do the
    # parsing ourselves *sigh*
    m = re.search(r'(?P<dow>Mon|Tue|Wed|Thu|Fri|Sat|Sun) (?P<mon>Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (?P<dom>\d{1,2}) (?P<hour>\d\d):(?P<min>\d\d):(?P<sec>\d\d) (?P<tz>.*) (?P<year>20\d\d)$', s)
    if m:
        return datetime.datetime(
            int(m.group('year')),
            months_map[m.group('mon')],
            int(m.group('dom')),
            int(m.group('hour')),
            int(m.group('min')),
            int(m.group('sec')),
            # This is not strictly speaking correct, but timezones is
            # such a trash fire it's practically impossible to handle
            # correctly (for example, the Olson database doesn't even
            # recognize "EDT" as a valid timezone!).
            #
            # Just treat everything as UTC and add a buffer of a few
            # hours. It's all approximate anyway.
            tzinfo=now.tzinfo
        )

    m = re.search(r'\((?P<year>\d\d\d\d)-(?P<mon>\d\d)-(?P<dom>\d\d)\)', s)
    if m:
        return datetime.datetime(
            int(m.group('year')),
            int(m.group('mon')),
            int(m.group('dom')),
            tzinfo=now.tzinfo)

    # Tough luck, we weren't able to parse a date from the version string...
    return None

def test_parse_version_date():
    test_data = (
        ('#2 SMP Tue Aug 18 13:41:50 PDT 2020',         (2020, 8, 18, 13, 41, 50)),
        ('#39-Ubuntu SMP Mon Mar 9 10:34:11 UTC 2020',  (2020, 3, 9, 10, 34, 11)),
        ('#1 SMP Debian 4.9.88-1+deb9u1 (2018-05-07)',  (2018, 5, 7)),
    )

    for version, expected_date in test_data:
        parsed_date = parse_version_date(version)
        assert parsed_date is not None

        year, month, day_of_month = expected_date[:3]
        assert parsed_date.year == year
        assert parsed_date.month == month
        assert parsed_date.day == day_of_month

        if len(expected_date) > 3:
            hour, minute, second = expected_date[3:]
            assert parsed_date.hour == hour
            assert parsed_date.minute == minute
            assert parsed_date.second == second

    test_data = (
        '#1 SMP Debian 3.2.73-2+deb7u2~bpo60+1'
    )

    for version in test_data:
        parsed_date = parse_version_date(version)
        assert parsed_date is None

def is_new_kernel(version):
    date = parse_version_date(config.version)
    if date is None:
        return False

    delta = now - date
    delta_days = delta.days + delta.seconds / 86400.
    return delta_days < 4

class UptrackRepo(object):
    def __init__(self, config):
        self.updates = None
        self.protocolVersion = UPTRACK_PACKAGES_PROTOCOL_VERSION
        self.remote_dir = posixpath.join(config.remote,
                                         parse.quote(config.sysname),
                                         parse.quote(config.arch),
                                         parse.quote(config.release),
                                         parse.quote(config.version))
        self.local_dir = config.local
        self.local_package_list = os.path.join(self.local_dir, "packages.yml")
        self.kspliceToolsApiVersion = kspliceToolsApiVersion()
        self.clientVersion = __version__
        self.userStatus = None
        self.expired = False
        self.ignore_sig_time = (
            config.options.init
            and config.config.has_option("Settings", "ignore-boot-time-warp")
            and config.config.getboolean("Settings", "ignore-boot-time-warp")
        )
        self.is_new_kernel = is_new_kernel(config.version)

    def verifyKeyring(self, keyring, fingerprint):
        logging.debug("Verifying key fingerprint...")
        p = subprocess.Popen(['gpg',
                              '--no-options',
                              '--homedir', UPTRACK_GPG_HOMEDIR,
                              '--no-default-keyring',
                              '--batch',
                              '--keyring', keyring,
                              '--fingerprint',
                              '--with-colons'],
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE)
        stdout, stderr = p.communicate()
        stdout = stdout.decode('utf-8')
        stderr = stderr.decode('utf-8')
        if stdout:
            logging.debug(stdout)
        if stderr:
            logging.debug(stderr)
        if p.returncode:
            return Uptrack.Result(1, "Ksplice Uptrack failed to read fingerprint")
        listings = [l.split(':') for l in stdout.strip().split("\n")]
        fprs = [l for l in listings if l[0] == "fpr"]
        if len(fprs) < 1:
            return Uptrack.Result(1, "Ksplice Uptrack could not find any fingerprints")
        if len(fprs) > 1:
            return Uptrack.Result(1, "Ksplice Uptrack found too many fingerprints")
# 10. Field:  User-ID.  [..]
#             An FPR record stores the fingerprint here.
#             The fingerprint of an revocation key is stored here.
        if fprs[0][9] != fingerprint:
            return Uptrack.Result(1, "Ksplice Uptrack could not verify the key fingerprint")
        logging.debug("Verified GPG fingerprint on %s", keyring)
        return None

    def validateServer(self):
        if Uptrack.server_is_oracle(config):
            return None

        res = self.verifyKeyring(SERVER_KEYRING, SERVER_KEY_FINGERPRINT)
        if res:
            return res

        fingerprint = serverFingerprint(config.remoteroot)
        logging.debug("Got a server fingerprint: %s", fingerprint)

        if fingerprint is None:
            return Uptrack.Result(
                Uptrack.ERROR_NO_NETWORK,
                "Unable to communicate with your site's Uptrack server.\n\n"
                "Please ensure that the update_repo_url setting is correct in\n"
                "/etc/uptrack/uptrack.conf, and that your Uptrack server is properly configured.\n"
                "If you need help resolving this issue, please contact %s."
                % (Uptrack.BUG_EMAIL,))

        sigpath = os.path.join(config.localroot, fingerprint + ".sig")
        try:
            code = Uptrack.download(Uptrack.getCurl(),
                                    posixpath.join(config.remoteroot,
                                                   'server',
                                                   fingerprint + ".sig"),
                                    sigpath, in_offline_mode)
            if code not in (200, 304):
                return Uptrack.Result(
                    1,
                    "Unable to download the signature for your Uptrack server.\n"
                    "Please contact %s for assistance." % (Uptrack.BUG_EMAIL,))
        except pycurl.error as e:
            logging.debug("cURL error %d (%s) while checking server signature."
                          % (e.args[0], e.args[1]))
            logging.debug(traceback.format_exc())
            return Uptrack.resultFromPycurl(config, e)

        fpfile = None
        try:
            (fd, fpfile) = tempfile.mkstemp()
            fh = os.fdopen(fd, 'w')
            fh.write(fingerprint + "\n")
            fh.close()
            if not self.verifySignature(fpfile, sigpath, keyring=SERVER_KEYRING):
                return Uptrack.Result(
                    1,
                    "Unable to verify that the server at <%s>\n"
                    "is an authorized Ksplice Uptrack server. Please contact\n"
                    "%s for assistance." %
                    (config.remoteroot, Uptrack.BUG_EMAIL))
        finally:
            try:
                if fpfile:
                    os.unlink(fpfile)
            except OSError:
                pass

        return None

    def downloadUserStatus(self):
        err = None
        key_check = posixpath.join(config.remote,
                                   parse.quote('status'))
        localpath = os.path.join(config.localroot, 'status')

        logging.debug("Verifying your access key is valid by requesting: %s" % key_check)
        stream = BytesIO()
        try:
            request = dict(Serial=config.incrementSerial())
            Uptrack.Status.addIdentity(config, request)
            contents = Uptrack.yaml_dump(request, version=(1, 1),
                                         explicit_start=True,
                                         explicit_end=True)

            c = Uptrack.getCurl()
            c.setopt(pycurl.URL, key_check.encode('ISO-8859-1'))
            c.setopt(pycurl.HTTPPOST, [('request', contents)])
            c.setopt(pycurl.WRITEFUNCTION, stream.write)
            c.perform()
            code = c.getinfo(pycurl.RESPONSE_CODE)
            if code == 200 or code == 304:
                try:
                    Uptrack.write_file_binary(localpath, stream.getvalue())
                except IOError as e:
                    logging.debug("Error writing status file.")
                    logging.debug(traceback.format_exc())

                try:
                    self.userStatus = Uptrack.yaml_load(stream.getvalue().decode('utf-8'))
                except yaml.YAMLError:
                    logging.debug("Malformed YAML response when checking the key.")
                    # This shouldn't happen---the server should always return
                    # valid YAML. Stop! Do not pass go. Do not collect $200.
                    err = Uptrack.Result(
                        Uptrack.ERROR_MALFORMED_YAML,
                        "The server returned an unexpected response.\n"
                        "Please try again in a few minutes. If this problem persists, \n"
                        "please contact %s for assistance." % (Uptrack.BUG_EMAIL,))
            elif 400 <= code <= 499:
                logging.debug("Your access key (%s) is invalid." % config.accesskey)
                err = Uptrack.Result(Uptrack.ERROR_INVALID_KEY,
                                     "Could not connect to the Ksplice Uptrack "
                                     "server with your access key.\n"
                                     "Please check that the key in %s is valid.\n" %
                                     Uptrack.UPTRACK_CONFIG_FILE)
            else:
                logging.debug(u"Unexpected error retrieving status file (%d)", code)
                logging.debug(u"The server said:")
                logging.debug(stream.getvalue().decode('utf-8'))
                if 500 <= code <= 599:
                    err = Uptrack.server_error_exception.result
                else:
                    err = Uptrack.Result(
                        Uptrack.ERROR_INTERNAL_SERVER_ERROR,
                        "Unexpected error communicating with the server.\n"
                        "Please try again in a few minutes. If this problem persists, \n"
                        "please contact %s for assistance." % (Uptrack.BUG_EMAIL,))

        except pycurl.error as e:
            logging.debug("cURL error %d (%s) while checking key."
                          % (e.args[0], e.args[1]))
            logging.debug(traceback.format_exc())
            err = Uptrack.resultFromPycurl(config, e)
        except Uptrack.ResultException as e:
            return e.result
        return err

    def showUserStatus(self):
        if self.userStatus is None:
            return None
        try:
            if 'Error' in self.userStatus:
                err = self.userStatus['Error']
                return Uptrack.Result(err['Code'], err['Message'])

            if config.cron and not self.userStatus.get('Cron'):
                return

            if 'Message' in self.userStatus:
                logging.info(self.userStatus['Message'])

            if 'Warning' in self.userStatus:
                logging.error(self.userStatus['Warning'])
        except (TypeError, AttributeError):
            logging.debug("Error parsing user status: ", exc_info=True)
            pass

    def handleStatus(self):
        err = self.downloadUserStatus()
        if err:
            return err
        try:
            if 'RegenerateCron' in self.userStatus:
                config.regenerateCron()
            if 'Backoff' in self.userStatus:
                config.updateBackoff(self.userStatus['Backoff'])
            if 'RegenerateUUID' in self.userStatus and not config.use_hw_uuid:
                # Must process this last, because it overwrites self.userStatus
                config.setNewUUID()
                err = self.downloadUserStatus()
                if err:
                    return err
        except TypeError:
            # If self.userStatus is not a dict.
            pass

        return self.showUserStatus()

    def downloadPackageList(self):
        logging.debug("Getting package list and signature from " + self.remote_dir)
        err = None
        try:
            c = Uptrack.getCurl()
            for filename in ['packages.yml', 'packages.yml.sig']:
                url = posixpath.join(self.remote_dir, filename)
                filename = os.path.join(self.local_dir, filename)
                try:
                    os.unlink(filename + ".expired")
                except OSError:
                    pass

                stream = BytesIO()
                rcode = Uptrack.download(c, url, filename, in_offline_mode, bytesio=stream)
                if rcode in [200, 304]:
                    continue
                elif rcode == HTTP_CODE_EXPIRED:
                    logging.debug("Writing expired data to %s" % (filename + ".expired",))
                    Uptrack.write_file_binary(filename + ".expired", stream.getvalue())
                    continue
                elif rcode in [404] and self.is_new_kernel:
                    err = Uptrack.Result(0, "Your kernel is so recent there are no updates to install.")
                elif rcode in [403, 404]:
                    if in_offline_mode:
                        err = Uptrack.Result(Uptrack.ERROR_UNSUPPORTED,
                                             "Cannot find Ksplice Uptrack information "
                                             "for your kernel version\n(%s %s).\n"
                                             "Have you installed the uptrack-updates package "
                                             "containing updates for this kernel?\n"
                                             "Please contact %s with questions." %
                                             (config.release, config.version, Uptrack.BUG_EMAIL))
                    else:
                        err = Uptrack.Result(Uptrack.ERROR_UNSUPPORTED,
                                             "Cannot find Ksplice Uptrack information "
                                             "for your kernel version\n(%s %s).\n"
                                             "Your kernel is probably not yet supported "
                                             "by Ksplice Uptrack.\n"
                                             "See http://www.ksplice.com/uptrack/supported-kernels "
                                             "for a summary of\nwhat kernels are supported.\n"
                                             "Please contact %s with questions." %
                                             (config.release, config.version, Uptrack.BUG_EMAIL))
                    logging.debug("Error downloading package list.  Checking key validity.")
                else:
                    err = Uptrack.Result(Uptrack.ERROR_NO_NETWORK,
                                         "Unexpected error connecting to the "
                                         "Ksplice Uptrack server.\n"
                                         "The web server returned "
                                         "HTTP code %03d.  If this error persists,\n"
                                         "please contact %s." % (rcode, Uptrack.BUG_EMAIL))
                    logging.debug("Received unexpected HTTP error code %03d." % rcode)

                return err
        except pycurl.error as e:
            logging.debug("cURL error %d (%s) while downloading package list."
                          % (e.args[0], e.args[1]))
            logging.debug(traceback.format_exc())
            err = Uptrack.resultFromPycurl(config, e)
        except IOError:
            err = Uptrack.Result(Uptrack.ERROR_NO_NETWORK,
                                 "Could not save the package list from the "
                                 "Ksplice Uptrack server.\n"
                                 "More details may be available in %s.\n"
                                 "If this error continues, please contact %s." %
                                 (LOGFILE, Uptrack.BUG_EMAIL))
            logging.debug(traceback.format_exc())

        return err

    def verifySignature(self, file, sigfile, keyring=None):
        if keyring is None:
            keyring = KEYRING
        logging.debug("Verifying signature on %s..." % (file,))
        cmd = ['gpgv',
               '--homedir', UPTRACK_GPG_HOMEDIR,
               '--keyring', keyring]
        if self.ignore_sig_time:
            logging.warning("Ignoring package signature timestamps!")
            cmd.append('--ignore-time-conflict')
        cmd.append(sigfile)
        cmd.append(file)
        p = subprocess.Popen(cmd,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE)
        stdout, stderr = p.communicate()
        stdout = stdout.decode('utf-8')
        stderr = stderr.decode('utf-8')
        if stdout:
            logging.debug(stdout)
        if stderr:
            logging.debug(stderr)
        if p.returncode:
            return False
        return True

    def readPackageList(self):
        if not os.path.exists(self.local_package_list):
            return (Uptrack.Result(Uptrack.ERROR_NO_NETWORK,
                                   "Ksplice Uptrack could not find the package list."), None)

        if not self.verifySignature(self.local_package_list,
                                    self.local_package_list + ".sig"):
            return (Uptrack.Result(1, "Ksplice Uptrack could not verify"
                                   " the package list signature."),
                    None)

        logging.debug("Trying to read package list at %s" % self.local_package_list)
        try:
            pl = Uptrack.yaml_load(open(self.local_package_list))
            if os.path.isfile(self.local_package_list + ".expired"):
                if not self.verifySignature(
                        self.local_package_list + ".expired",
                        self.local_package_list + ".sig.expired"):
                    return (Uptrack.Result(1, "Ksplice Uptrack could not verify"
                                           " the package list signature."),
                            None)
                expired = Uptrack.yaml_load(open(
                    self.local_package_list + ".expired", 'r'))
                pl['Expired'] = expired
        except (IOError, yaml.YAMLError):
            logging.debug("Error reading the package list", exc_info=1)
            err = Uptrack.Result()
            if not config.init:
                err.code = Uptrack.ERROR_NO_NETWORK
            err.message = "Cannot load the package list.\n"
            # If config.allow_no_net is false, we already downloaded the
            # package list, so barring possible weird edge cases, this
            # is almost certainly a bug.

            # If we don't have network, instruct the user to try again
            # with network, in the hopes that re-downloading the
            # package list will fix things.
            if config.allow_no_net:
                err.message += "Please re-run the Ksplice Uptrack client with a network connection available."
            else:
                err.message += "Please report a bug to %s" % (Uptrack.BUG_EMAIL,)
            return err, None

        client = pl['Client']

        version = pl['Protocol version']
        # Check versions
        if self.protocolVersion != version:
            local.new_client = True
            return (Uptrack.Result(1,
                                   "Protocol version mismatch: %s != %s\n"
                                   "Please use your package manager to update this client."
                                   % (self.protocolVersion, version)),
                    None)

        needVersion = client.get('Version to Parse', '0')
        if Uptrack.compareversions(self.clientVersion, needVersion) < 0:
            local.new_client = True
            return (Uptrack.Result(Uptrack.ERROR_TOO_OLD_PARSE,
                                   "Ksplice Uptrack client too old: %s, require %s\n"
                                   "Please use your package manager to update this client."
                                   % (self.clientVersion, needVersion)),
                    None)

        # Sanity check

        kernel = pl['Kernel']
        if config.release != kernel['Release']:
            return (Uptrack.Result(1, "Kernel release mismatch: %s != %s"
                                      % (config.release, kernel['Release'])), None)
        if config.version != kernel['Version']:
            return (Uptrack.Result(1, "Kernel version mismatch: %s != %s"
                                      % (config.version, kernel['Version'])), None)
        if config.arch != kernel['Architecture']:
            return (Uptrack.Result(1, "Wrong architecture: %s != %s"
                                      % (config.arch, kernel['Architecture'])), None)

        if 'Error' in pl:
            e = pl['Error']
            return Uptrack.Result(e['Code'], e['Message']), None

        return None, pl

    def parsePackageList(self):
        err, pl = self.readPackageList()
        if pl is None:
            return err
        packages = {}
        for i, item in enumerate(pl['Updates']):
            u = makeUpdate(item, self.local_dir, self.remote_dir, i)
            packages[u.id] = u
        self.updates = packages

        version = pl['Client'].get('Version to Install', '0')
        if Uptrack.compareversions(self.clientVersion, version) < 0:
            local.new_client = True
            return Uptrack.Result(Uptrack.ERROR_TOO_OLD_INSTALL,
                                  "Ksplice Uptrack client too old: %s, require %s\n"
                                  "Please use your package manager to update this client."
                                  % (self.clientVersion, version))

        version = pl['Client']['Ksplice Tools API version']

        if self.kspliceToolsApiVersion == '-1':
            return Uptrack.Result(1,
                                  "Error: %s: No such file or directory" % (API_VERSION_FILE,))
        if Uptrack.compareversions(self.kspliceToolsApiVersion,
                                   version) < 0:
            local.new_client = True
            return Uptrack.Result(1,
                                  "Ksplice Uptrack client too old: tools API version %s < %s\n"
                                  "Please use your package manager to update this client."
                                  % (self.kspliceToolsApiVersion, version))
        elif self.kspliceToolsApiVersion != version:
            return Uptrack.Result(1,
                                  "Ksplice Uptrack client too new: tools API version %s > %s\n"
                                  "Please report this problem to %s."
                                  % (self.kspliceToolsApiVersion, version,
                                     Uptrack.BUG_EMAIL))

        global alert, desupported, tray_icon_error
        alert = pl.get('Alert')
        desupported = pl.get('Desupported')
        tray_icon_error = pl.get('TrayIconError')
        if alert and (desupported or not config.cron):
            logging.warning(alert)

        expired = pl.get('Expired')
        if expired:
            self.expired = True
            if 'Message' in expired:
                logging.warning(expired['Message'])

        return err

    def downloadPackages(self):
        logging.debug("Downloading packages.")
        for u in sorted(self.updates.values(), key=lambda u: u.order):
            if u.isValidFile():
                logging.debug("Already downloaded %s, skipping" % u)
                continue
            logging.debug("Downloading %s" % u)
            try:
                Uptrack.mkdirp(os.path.dirname(u.local_path))
                rcode = Uptrack.download(Uptrack.getCurl(),
                                         u.remote_path,
                                         u.local_path,
                                         in_offline_mode,
                                         ifmodified=False)
                if rcode != 200:
                    return Uptrack.Result(1,
                                          "Unexpected error downloading update %s.\n"
                                          "The web server returned HTTP code %03d.\n"
                                          "If this error persists, please contact %s." %
                                          (u.id, rcode, Uptrack.BUG_EMAIL))

            except (IOError, pycurl.error):
                err = Uptrack.Result()
                err.code = Uptrack.ERROR_GENERIC_ERROR
                err.message = ("Couldn't download update '%s'.\n"
                               "Please check your network connection and try "
                               "again.\nIf this error continues, contact %s." %
                               (u, Uptrack.BUG_EMAIL))
                logging.debug("Error downloading update %s." % u.filename)
                logging.debug("Remote: %s" % u.remote_path)
                logging.debug("Local: %s" % u.local_path)
                logging.debug(traceback.format_exc())
                return err
            logging.debug("Unpacking %s" % u)
            res = u.unpack()
            if res.code:
                return res

    def downloadAll(self):
        res = repo.downloadPackageList()
        if res:
            return res
        res = repo.parsePackageList()
        if res:
            return res
        return repo.downloadPackages()

    def getAllUpdates(self):
        if self.updates is None:
            raise Exception("BUG: Package list has not yet been read.")
        return set(self.updates.values())

    def idToUpdate(self, id):
        """
        Turns ID into Update, or returns
        None if we don't know about it
        """
        if self.updates:
            return self.updates.get(id, None)


class AlarmSignaled(Exception):
    pass


def onAlarm(sig, frame):
    raise AlarmSignaled


def getLock(lockfile, create_mode=0o644):
    lockdir = os.path.dirname(lockfile)
    if not os.path.isdir(lockdir):
        try:
            os.makedirs(lockdir)
        except IOError:
            return False
    old_handler = None
    try:
        try:
            old_handler = signal.signal(signal.SIGALRM, onAlarm)
            f = os.open(lockfile, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, create_mode)
            signal.alarm(LOCK_TIMEOUT)
            fcntl.flock(f, fcntl.LOCK_EX)
        except (IOError, AlarmSignaled):
            return False
    finally:
        signal.alarm(0)
        if old_handler is not None:
            signal.signal(signal.SIGALRM, old_handler)

    return f


def releaseLock(lock):
    """
    The fd is closed when the variable goes out
    of scope or the process is exited, so this shouldn't
    really be necessary, but is here in case you want
    to explicitly release the lock
    """
    fcntl.flock(lock, fcntl.LOCK_UN)
    os.close(lock)


class BaseUptrackClientConfig(object):
    clientConfig = None
    def __init__(self, program, args):
#        super(UptrackClientConfig, self).__init__()
        command = extractCommand(os.path.basename(program))
        if command == INSTALL:
            usage = """\
usage: %prog [options] <id>...
Install selected patch to a running kernel."""
        elif command == REMOVE:
            usage = """\
usage: %prog [options] <id>...
Remove selected patches from a running kernel."""
        elif command == SHOW:
            usage = """\
usage: %prog [options] [<id>...]
Display which updates have been installed."""
        elif command == UPGRADE:
            usage = """\
usage: %prog [options]
Apply available updates to a running kernel."""
        parser = OptionParser(usage=usage)
        # Global Options
        parser.add_option("-q", "--quiet",
                          action="store_const",
                          const=-1,
                          dest="verbose",
                          help="don't print status messages")
        parser.add_option("-v", "--verbose",
                          action="count",
                          dest="verbose",
                          help="provide more detail about what this program is doing",
                          default=0)
        parser.add_option("-y",
                          action="store_true",
                          dest="answer_yes",
                          default=False,
                          help="answer 'yes' to all user prompts")
        parser.add_option("-n",
                          action="store_true",
                          dest="answer_no",
                          default=False,
                          help="answer 'no' to all user prompts")
        parser.add_option("--all",
                          action="store_true", dest="all", default=False,
                          help="take action for all updates")
        # Options used during startup
        parser.add_option("--init",
                          action="store_const", dest="init", default=None,
                          const='early', help=SUPPRESS_HELP)
        parser.add_option("--late-init",
                          action="store_const", dest="init", const="late",
                          help=SUPPRESS_HELP)
        parser.add_option("--shutdown",
                          action="store_const", dest="init", const="shutdown",
                          help=SUPPRESS_HELP)
        parser.add_option("--check-init",
                          action="store_true", dest="check_init", default=False,
                          help=SUPPRESS_HELP)
        # end startup options
        parser.add_option("-V", "--version",
                          action="store_true",
                          dest="show_version",
                          help="print the version information and exit",
                          default=False)
        parser.add_option('--i-accept-the-terms-of-service',
                          action='store_true', dest='accept_tos', default=False,
                          help=SUPPRESS_HELP)
        opt_no_net = Option("--no-network",
                            action="store_true", dest="allow_no_net", default=False,
                            help="Don't use the network.")
        opt_available = Option('--available', action="store_true", dest="available",
                               default=False, help='Also show available updates.')
        opt_wait = Option('--wait', type="float", dest="wait", default=0,
                          help="time to wait between applying updates")
        opt_cron = Option("--cron",
                          action="store_true", dest="cron", default=False,
                          help=SUPPRESS_HELP)
        opt_count = Option("--count",
                           action="store_true", dest="count", default=False,
                           help="Display the number of upgrades installed.")
        opt_uninstall = Option("--uninstall",
                               action="store_true", dest="uninstall", default=False,
                               help=SUPPRESS_HELP)
        # command specific options
        optmap = {SHOW: [opt_available, opt_count, opt_no_net, ],
                  INSTALL: [opt_wait, ],
                  REMOVE: [opt_no_net, opt_wait, opt_uninstall],
                  UPGRADE: [opt_wait, opt_cron],
                  }
        if command in optmap:
            for option in optmap[command]:
                parser.add_option(option)

        self.parser = parser
        self.args = args

    def processArgs(self):
        (options, args) = self.parser.parse_args(self.args)
        self.options = options
        self.args = args

        if self.options.answer_yes and self.options.answer_no:
            self.parser.error("Please supply only one of -y or -n.")

        if hasattr(self.options, 'wait') and self.options.wait < 0:
            self.parser.error("--wait argument must be positive")

        self.setVerbosity()
        self.setTask()

    def setVerbosity(self):
        self.verbose = self.options.verbose

    def setTask(self):
        self.disabled = os.path.isfile("/etc/uptrack/disable")
        self.disablecmd = 'nouptrack' in Uptrack.read_file("/proc/cmdline")

        self.answer_yes = self.options.answer_yes
        self.answer_no = self.options.answer_no
        self.allow_no_net = getattr(self.options, 'allow_no_net', False)
        self.all = self.options.all
        if hasattr(self.options, 'available'):
            self.available = self.options.available

        # Unattended operation modes
        self.cron = False
        self.uninstall = False
        self.init = False

        if hasattr(self.options, 'uninstall') and self.options.uninstall:
            self.uninstall = True
            self.all = True
            self.answer_yes = True

        self.cron_autoinstall = Uptrack.getConfigBooleanOrDie(
            self.config, 'Settings', 'autoinstall', False)

        self.install_on_reboot = Uptrack.getConfigBooleanOrDie(
            self.config, 'Settings', 'install_on_reboot', True)
        self.upgrade_on_reboot = Uptrack.getConfigBooleanOrDie(
            self.config, 'Settings', 'upgrade_on_reboot', False)

        if hasattr(self.options, 'cron') and self.options.cron:
            self.cron = True
            self.answer_yes = self.cron_autoinstall
            self.answer_no = not self.cron_autoinstall
            self.verbose = -2

        if hasattr(self.options, 'init') and self.options.init:
            self.init = self.options.init
            self.verbose -= 1
            if self.init == 'early':
                self.answer_yes = True
                self.answer_no = False
                self.allow_no_net = True
            elif self.init == 'late':
                self.answer_yes = self.upgrade_on_reboot
                self.answer_no = not self.answer_yes
            elif self.init == 'shutdown':
                self.answer_yes = False
                self.answer_no = True

        if hasattr(self.options, 'wait'):
            self.wait = self.options.wait
        self.accept_tos = self.options.accept_tos

    def checkDeprecatedOptions(self):
        if self.cron:
            return

        show_cron_output_warning = False
        for suffix in ('install', 'available', 'error'):
            option = 'cron_output_' + suffix
            if Uptrack.getConfigBooleanOrDie(self.config, 'Settings', option, False):
                show_cron_output_warning = True

        if show_cron_output_warning:
            logging.warning("Warning: The cron output configuration options have been removed.")
            logging.warning("Please visit <http://www.ksplice.com/uptrack/notification-options>")
            logging.warning("for more information.")

    # (Slight) hack to pass on requests for config items that reside in the client
    # config to that object
    def __getattr__(self, key):
        if self._intialized:
            if key in self.__dict__:
                return self.__dict__[key]

            return getattr(self.clientConfig, key)
        else:
            try:
                return self.__dict__[key]
            except KeyError:
                raise AttributeError(key)

    def __setattr__(self, key, value):
        if key in ['_intialized'] or not self._intialized:
            self.__dict__[key] = value
            return

        if key in self.__dict__:
            self.__dict__[key] = value
        else:
            setattr(self.clientConfig, key,value)

    # Slightly more of a hack.  These are methods in the clientConfig called by other
    # parts of uptrack-upgrade.  No reason for those other parts to have to really
    # distinguish the types of config, so just pass on those method calls
    def regenerateCron(self):
        self.clientConfig.regenerateCron()

    def updateBackoff(self, backoff):
        self.clientConfig.updateBackoff(backoff)

    def setNewUUID(self):
        self.clientConfig.olduuid = self.clientConfig.uuid
        self.clientConfig.setUUID(self.clientConfig.newUUID())

class OnlineUptrackClientConfig(BaseUptrackClientConfig):
    def __init__(self, program, args):
        self._intialized = False
        self.clientConfig = Uptrack.OnlineUptrackConfig()
        self.config = self.clientConfig.config
        super(OnlineUptrackClientConfig, self).__init__(program, args)
        super(OnlineUptrackClientConfig, self).processArgs()
        # These aren't use in the online version
        self.effective_version = None
        self.list_effective = None

        self._intialized = True

class OfflineUptrackClientConfig(BaseUptrackClientConfig):
    def __init__(self, program, args):
        self._intialized = False
        self.clientConfig = Uptrack.OfflineUptrackConfig()
        self.config = self.clientConfig.config
        super(OfflineUptrackClientConfig, self).__init__(program, args)
        self.parser.add_option("--yum",
                               action="store_true", dest="yum_upgrade",
                               help="Install latest update packages with YUM",
                               default=False)
        self.parser.add_option("-E", "--effective",
                               dest="effective_version", default=None,
                               help="Upgrade to the specified effective kernel version")
        self.parser.add_option("-L", "--list-effective",
                               action="store_true", dest="list_effective", default=False,
                               help="List available effective kernel versions")
        super(OfflineUptrackClientConfig, self).processArgs()

        if self.options.effective_version:
            if self.clientConfig.effective_version:
                logging.info('Duplicate effective version options.')
                logging.info('Ignoring config file option in favor of command line.')
            self.effective_version = self.options.effective_version
        else:
            self.effective_version = self.clientConfig.effective_version

        if self.options.list_effective:
            self.list_effective = self.options.list_effective
        else:
            self.list_effective = None
        self._intialized = True

    def setTask(self):
        super(OfflineUptrackClientConfig, self).setTask()

class PermissionedRotatingFileHandler(logging.handlers.RotatingFileHandler):
    """
    A subclass of logging.handlers.RotatingFileHandler which sets user,
    group, and permissions, and turns buffering off.
    """

    def __init__(self, filename, **kwargs):
        self._ks_filename = filename

        try:
            self._ks_uid = pwd.getpwnam(LOGUSER)[2]
            self._ks_gid = grp.getgrnam(LOGGROUP)[2]
        except KeyError:
            self._ks_uid = None
            self._ks_gid = None
        self._ks_umask = LOGMODE ^ 0o777

        old = os.umask(self._ks_umask)
        logging.handlers.RotatingFileHandler.__init__(self, filename, **kwargs)
        os.umask(old)

        self._ksplice_chown()

    def _ksplice_chown(self):
        # not security critical; by default the file will be root:root
        if self._ks_uid is None:
            return

        try:
            os.chown(self._ks_filename, self._ks_uid, self._ks_gid)
        except OSError:
            pass

    def emit(self, *args, **kwargs):
        old = os.umask(self._ks_umask)
        logging.handlers.RotatingFileHandler.emit(self, *args, **kwargs)
        if self.stream is not None:
            try:
                os.fsync(self.stream)
            except Exception:
                pass
        os.umask(old)

    def doRollover(self, *args, **kwargs):
        logging.handlers.RotatingFileHandler.doRollover(self, *args, **kwargs)
        self._ksplice_chown()


class Logger(object):
    def __init__(self):
        my_logger = logging.getLogger('')
        my_logger.setLevel(logging.DEBUG)
        consoleLevel = logging.INFO

        logging.raiseExceptions = False
        self.console_logger = logging.StreamHandler(sys.stdout)
        self.console_logger.setLevel(consoleLevel)
        formatter = logging.Formatter('%(message)s')
        self.console_logger.setFormatter(formatter)

        fformat = logging.Formatter('%(asctime)s %(levelname)-8s %(message)s')
        filelogger = PermissionedRotatingFileHandler(LOGFILE,
                                                     maxBytes=2.5 * 1024 * 1024,
                                                     backupCount=2)
        filelogger.setLevel(logging.DEBUG)
        filelogger.setFormatter(fformat)

        self.debug_log = NativeStringIO()
        self.debug_logger = logging.StreamHandler(self.debug_log)
        self.debug_logger.setLevel(logging.DEBUG)
        self.debug_logger.setFormatter(fformat)

        logging.getLogger('').addHandler(filelogger)
        logging.getLogger('').addHandler(self.console_logger)
        logging.getLogger('').addHandler(self.debug_logger)

    def getDebugLog(self):
        try:
            logging.getLogger('').removeHandler(self.debug_logger)
            return self.debug_log.getvalue()
        except Exception:
            return None

    def configure(self, config):
        consoleLevel = logging.INFO
        if config.verbose < -1:
            consoleLevel = logging.CRITICAL
        elif config.verbose < 0:
            consoleLevel = logging.ERROR
        elif config.verbose > 0:
            consoleLevel = logging.DEBUG

        self.console_logger.setLevel(consoleLevel)


def confirm(msg):
    if config.answer_yes:
        return True
    if config.answer_no:
        return False

    logging.info("")
    take_action = 'n'
    try:
        take_action = input("%s [y/N]? " % msg)
    except (KeyboardInterrupt, EOFError):
        print()
        pass
    if len(take_action) < 1 or take_action[0].lower() != 'y':
        return False
    return True

def doRemove(repo, local, wish_to_remove):
    if config.all:
        wish_to_remove = [u.id for u in local.getInstalledUpdates()]
    return planAndDoActions(repo, local, [(REMOVE, x) for x in wish_to_remove])

def doUpgrade(repo, local):
    # Shortcut the answer_no case to avoid an additional round-trip
    if config.answer_no:
        try:
            (_, new_upgrade, new_init, new_remove) = planActions(repo, local, [])
        except Uptrack.ResultException as e:
            return e.result
        if len(new_upgrade) == 0:
            if not config.cron:
                printEffective()
                logging.info("Nothing to be done.")
        else:
            printEffective()
            displayAndConfirm(new_upgrade)
        local.writeOutStatus(None, new_upgrade, new_init, new_remove)
        return Uptrack.Result()

    res = planAndDoActions(repo, local, [(INSTALL, 'head')])
    if not res.code:
        if not config.cron:
            logging.info("Your kernel is fully up to date.")
            printEffective()
    return res

def all_already_installed(update_ids):
    return set(update_ids).issubset(set(local.getInstalledIDs()))

def listAvailableEffectiveVersionsAndQuit():
    sorted_versions, unused_dict = group_updates_by_version(config, local, INSTALL)
    quit(Uptrack.Result(0, """Available effective kernel versions:

%s""" % ('\n'.join(sorted_versions))))

def doUpgradeToEffectiveVersion(repo, local, wanted_effective_version):
    sorted_versions, updates_by_version = group_updates_by_version(config, local, INSTALL)
    if not wanted_effective_version in sorted_versions:
        quit(Uptrack.Result(Uptrack.ERROR_GENERIC_ERROR, """
The specified effective kernel version: '%s' is invalid or not available in the currently installed uptrack-updates package.

Available effective kernel versions:

%s

Check the uptrack-updates package contents by running:

  rpm -ql $(rpm -qa uptrack-updates*) |less
        """ % (wanted_effective_version, '\n'.join(sorted_versions))))

    to_install = updates_by_version[wanted_effective_version]
    kernel = local.getEffective()
    if kernel is None:
        quit(Uptrack.Result(
            Uptrack.ERROR_GENERIC_ERROR,
            "Could not determine the current effective version.\n"
            "Not upgrading to selected effective version.\n"
            "Check %s for corruption" % (Uptrack.UPTRACK_EFFECTIVE_KERNEL_FILE,)))
    current_effective = kernel['Release'] + '/' + kernel['Version']
    if all_already_installed(to_install) and current_effective != wanted_effective_version:
        quit(Uptrack.Result(Uptrack.ERROR_GENERIC_ERROR, """
The current effective kernel version (%s) is already newer than the requested effective kernel version.
        """ % (current_effective)))
    res = planAndDoActions(repo, local,
                           [(INSTALL, x) for x in updates_by_version[wanted_effective_version]])
    printEffective()
    return res

def doInstall(repo, local, wish_to_install):
    if config.all:
        return doUpgrade(repo, local)
    else:
        res = planAndDoActions(repo, local, [(INSTALL, x) for x in wish_to_install])
        printEffective()
        return res

def printEffective(blank_line=False):
    kernel = local.getEffective()
    if kernel is not None:
        if blank_line:
            logging.info("")
        logging.info("Effective kernel version is %s"
                     % (kernel['PackageVersion'],))

def planActions(repo, local, actions):
    """:: Repo -> Local -> [Action] -> IO (Plan, Plan, Plan, Plan)
    data Action = Install Kid | Remove Kid
    -- Implemented as (INSTALL|REMOVE, Kid)
    -- Installing the pseudo-kid "head" means "upgrade fully".
    type Plan = [(INSTALL|REMOVE,Update)]

    Given a set of requested actions, return a set of four plans
    appropriate to carry out those actions and then update our state.

    The four returned plans are, in order:
    *) A plan that includes the requested actions.
    *) A new upgrade plan that is what one would need to do to fully
       update the machine assuming we perform the first plan
       succesfully.
    *) A new init-time plan that is used to take a rebooted machine
       back to the state it would be in after we perform the first
       plan succesfully.
    *) A new remove-all plan, assuming we perform the first plan
       succesfully. (this is used if we are trying to remove all
       updates and don't have network connectivity).

    """

    logging.debug("Constructing plans for the actions: " + str(actions))

    installed = local.getInstalledUpdates()

    new_actions = []
    for act in actions:
        command, id = act
        update = repo.idToUpdate(id)
        if update is None and id != "head":
            logging.warning("Unknown update %s, skipping." % (id,))
        elif command == INSTALL and update in installed:
            logging.warning("Update %s is already installed, skipping." % (id,))
        elif command == REMOVE and update not in installed:
            logging.warning("Update %s is not installed, skipping." % (id,))
        else:
            new_actions.append(act)

    actions = new_actions

    installed_ids = [u.id for u in installed]
    loaded_modules = getLoadedModules()
    locked_ids = [u.id for u in installed if u.isLocked(loaded_modules)]
    if config.in_offline_mode:
        augmenter = None
        kernel_key = "%s/%s/%s/%s" % (config.sysname, config.arch, config.release, config.version)
        ek = load_effective_kernel_data(kernel_key)
        augmenter = ek.augmentPlan(kernel_key)
        solver = OfflineUptrackDepSolver(local, installed_ids, locked_ids)
        response = solver.getPlans(actions, augmenter)
    else:
        solver = UptrackDepSolver.UptrackDepSolver(local, installed_ids, locked_ids)
        response = solver.getPlans(actions)

    plan, upgrade, init, remove = [response[k] for k in
                                   ('Steps', 'Upgrade Plan', 'Init Plan', 'Remove Plan')]
    if 'EffectiveKernel' in response:
        local.setEffective(response['EffectiveKernel'])
    if False in [act['ID'] in repo.updates for act in plan + upgrade + init + remove]:
        # Make sure packages.yml and associated state are up to date
        res = repo.downloadAll()
        if res and res.code != 0:
            raise Uptrack.ResultException(res.code, res.message)

        for act in plan + upgrade + init + remove:
            if act['ID'] not in repo.updates:
                raise Uptrack.ResultException(Uptrack.ERROR_INTERNAL_SERVER_ERROR,
                                              textwrap.fill(
                                                  """Error: Server asked that we %s unknown update %s.
Please report this issue to ksplice-support_ww@oracle.com.
""" % (act['Command'].lower(), act['ID'])))

    local.unpackPlan(plan)
    local.unpackPlan(upgrade)
    local.unpackPlan(init)
    local.unpackPlan(remove)

    return (plan, upgrade, init, remove)


def planAndDoActions(repo, local, actions):
    """:: Repo -> Local -> [Action] -> Uptrack.Result

    Construct a plan to perform the requested actions, and then carry
    out said plan. In addition, write new upgrade, init-time, and
    remove plans appropriate for the new state.
    """  
    try:
        (plan, new_update, new_init, new_remove) = planActions(repo, local, actions)
    except Uptrack.ResultException as e:
        return e.result

    if len(plan) == 0:
        if not config.cron:
            logging.info("Nothing to be done.")
    elif not displayAndConfirm(plan):
        return Uptrack.Result(Uptrack.ERROR_USER_NO_CONFIRM, "Aborting.")

    res = doActions(plan, local)

    if res.code:
        return res

    # We succeeded. Write out the new update, init-time, and remove
    # plans the server gave us. Don't write out our result, since
    # quit() will do that for us.
    local.writeOutStatus(None, new_update, new_init, new_remove)

    return res


def doInitClean(local):
    dir = "/var/run/ksplice"
    file = "/var/run/uptrack"
    if os.path.isdir(dir):
        logging.debug("Cleaning up stale state in %s and %s" % (dir, file))
        try:
            shutil.rmtree(dir)
        except Exception as e:
            if isinstance(e, OSError) and e.errno == errno.ENOENT:
                pass
            else:
                logging.error("An error occurred while removing %s" % dir)
                logging.debug(traceback.format_exc())
    if os.path.exists(file):
        try:
            os.unlink(file)
        except e:
            if isinstance(e, OSError) and e.errno == errno.ENOENT:
                pass
            logging.error("An error occurred while removing %s" % file)
            logging.debug(traceback.format_exc())


def doInit(repo, local):
    """
    Execute the init-time plan previously written to disk
    """
    inst = local.getInstalledUpdates()
    if len(inst) != 0:
        msg = "\nError: Uptrack init script started, but some updates are already installed!\n\n" + \
              textwrap.fill(
                  "This is most likely because you manually ran '/etc/init.d/uptrack start'. " +
                  "You never need to run the Uptrack init script manually: There is no Uptrack " +
                  "daemon to start.  This init script only exists to reinstall your updates " +
                  "during the boot process.  Please contact %s if you have any questions." % Uptrack.BUG_EMAIL)
        return Uptrack.Result(1, msg)

    doInitClean(local)

    if not config.install_on_reboot:
        logging.debug("init-time installation is disabled.")
        return Uptrack.Result()

    plan = []
    try:
        logging.debug("Reading init-time plan...")
        plan = local.readInitPlan()
        logging.debug("Installing updates according to the init-time plan:")
        logging.debug(plan)
    except (IOError, yaml.YAMLError):
        logging.debug("Error reading init-time plan; doing nothing")
        logging.debug(traceback.format_exc())
        return Uptrack.Result(1, "Error reading init-time plan")

    return doActions(plan, local)


def doOfflineRemoveAll(repo, local):
    """
    Execute the remove_plan previously written to disk
    """
    res = Uptrack.Result()

    plan = []
    try:
        logging.debug("Reading remove plan...")
        plan = local.readRemovePlan()
    except (IOError, yaml.YAMLError):
        logging.debug("Error reading remove-all plan; doing nothing")
        logging.debug(traceback.format_exc())
        res.code = Uptrack.ERROR_GENERIC_ERROR
        res.message = "Error reading remove-all plan"
        return res

    if len(plan) == 0:
        if not config.cron:
            logging.info("Nothing to be done.")
    elif not displayAndConfirm(plan):
        logging.info("Aborting.")
        return Uptrack.Result()

    res = doActions(plan, local)
    if res.code:
        return res

    # We succeeded, so write out empty init and remove plans (since
    # we've successfully removed all of the updates, it's reasonable
    # to do nothing at boot-time).

    # We could potentially reverse the remove plan we just executed
    # and write it out as an upgrade plan, but since we will refuse to
    # execute it without network, anyways, we just write out an empty
    # one, and an error message informing the user to try again when
    # network is available.

    if not config.uninstall:
        res = Uptrack.Result(Uptrack.ERROR_NO_NETWORK,
                             "New updates %s available. Please re-run "
                             "Uptrack with a network connection available\n"
                             "to view and install them" %
                             ({True: 'are', False: 'may be'}[len(plan) > 0],))

    # Don't write out a result, since quit() will do that for us.
    local.writeOutStatus(None, [], [], [])
    return res


def displayAndConfirm(actions):
    if not config.cron:
        logging.info("The following steps will be taken:")
        for act in actions:
            logging.info("%s %s" % (act['Command'], act['Update']))
    return confirm("Go ahead")


def doActions(actions, local):
    res = Uptrack.Result()
    inst = local.getInstalledUpdates()
    count = len(actions)
    depmod_needed = os.path.exists(DEPMOD_NEEDED_FILE)
    for i in range(count):
        command, u = [actions[i][k] for k in ('Command', 'Update')]
        new_effective = actions[i].get('EffectiveKernel')
        commandwords = {INSTALL: 'Installing', REMOVE: 'Removing'}
        logging.info("%s %s" % (commandwords[command], u))
        config.notify.WorkingOnUpdate(command.upper(), i, count, u.id, u.name)
        if command == INSTALL:
            if u in inst:
                logging.warning("...%s is already installed, so skipping" % u.id)
                continue
            else:
                r = u.applyUpdate()
        elif command == REMOVE:
            if u not in inst:
                logging.warning("...%s is not installed, so not removing" % u.id)
                continue
            else:
                r = u.undoUpdate()
        else:
            pass

        if r.code:
            logging.warning("Error processing %s" % u.filename)
            res.failed.append(r)
            res.code = r.code
            if config.debug_to_server:
                res.debug = r.debug
            break
        else:
            res.succeeded.append(r)
            if command == INSTALL:
                inst.add(u)
            elif command == REMOVE:
                inst.remove(u)
            depmod_needed = depmod_needed or r.depmod_needed
            if new_effective is not None:
                local.setEffective(new_effective)

        if config.wait:
            logging.debug("Sleeping %s seconds between updates, as requested" % config.wait)
            time.sleep(config.wait)

    if depmod_needed:
        try:
            os.unlink(DEPMOD_NEEDED_FILE)
        except OSError:
            pass
        config.notify.WorkingOnUpdate('DEPMOD', count, count, '', '')
        # modprobe.ksplice uses the presence of /var/run/uptrack to
        # decide whether it should use the modules.dep file managed
        # by Ksplice or the normal modules.dep file.
        # /var/run/uptrack is automatically removed after every
        # reboot
        if not os.path.isfile('/var/run/uptrack'):
            Uptrack.write_file('/var/run/uptrack', "uptrack\n")

        p = subprocess.Popen(["/bin/sh", "-c", "/sbin/ksplice-depmod -a"],
                             stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        stdout, stderr = p.communicate()
        stdout = stdout.decode('utf-8')
        stderr = stderr.decode('utf-8')
        if p.returncode:
            logging.debug("Error in ksplice-depmod (%d)" % (p.returncode))
            if stdout:
                logging.debug("stdout:")
                logging.debug(stdout)
            if stderr:
                logging.debug("stderr:")
                logging.debug(stderr)
            try:
                Uptrack.write_file(DEPMOD_NEEDED_FILE, "True")
            except IOError:
                pass
            if not res.code:
                res.code = 1
                res.message = "Error running ksplice-depmod."
                return res

    return res


def doShowUpgradePlan(repo, local):
    try:
        plan = local.readUpgradePlan()
    except IOError:
        logging.error("Unable to read the list of available updates.\n"
                      "Please run 'uptrack-upgrade -n' to update "
                      "the list of available updates.")
        sys.exit(1)

    logging.info("Available updates:")
    if plan:
        for act in plan:
            if act['Command'] == 'Install':
                logging.info(act['Update'])
    else:
        logging.info(None)


def doShow(repo, local, wish_to_show):
    """
    show has two modes of operation:

    When run without arguments, it lists the current status (i.e. what
    is installed). When arguments are passed to it, it shows you the
    detailed information for those updates.
    """
    inst = local.getInstalledUpdates()

    if config.options.count:
        logging.info(len(inst))
        sys.exit(0)

    can_show = set()
    for id in wish_to_show:
        u = repo.idToUpdate(id)
        if u is None:
            logging.warning("Don't know about update %s; skipping." % id)
            continue
        can_show.add(u)

    if len(wish_to_show) == 0:
        if config.available or config.all:
            doShowUpgradePlan(repo, local)

        if (not config.available) or config.all:
            logging.info("Installed updates:")
            if not len(inst):
                logging.info("None")
            else:
                inst_sorted = list(inst)
                inst_sorted.sort(key=lambda u: u.order)

                for u in inst_sorted:
                    logging.info(u)

        printEffective(blank_line=True)
    else:
        for u in sorted(can_show, key=lambda u: u.order):
            if u in inst:
                logging.info("Update %s is installed on your system.  Detailed description:\n" % u.id)
            else:
                logging.info("Update %s is NOT installed on your system.  Detailed description:\n" % u.id)
            p = u.getDetails()
            if p == '':
                p = "Unable to retrieve detailed description of update %s." % u.id
            else:
                p = p.strip() + "\n"
            logging.info(p)
    return None


if have_dbus:
    class DbusNotifications(dbus.service.Object):
        def __init__(self, object_path):
            dbus.service.Object.__init__(self, dbus.SystemBus(), object_path)

        def ClientStartStop(self, action):
            logging.debug("dbus: Action: %s" % (action))
        ClientStartStop = dbus.service.signal(dbus_interface='com.ksplice.uptrack.Client',
                                              signature='s')(ClientStartStop)

        def WorkingOnUpdate(self, action, num, total, id, desc):
            logging.debug("dbus: Working on update: %s %i %i %s %s" %
                          (action, num, total, id, desc))
        WorkingOnUpdate = dbus.service.signal(dbus_interface='com.ksplice.uptrack.Client',
                                              signature='siiss')(WorkingOnUpdate)


class NonDbusNotifications(object):
    def __init__(self, object_path): pass

    def ClientStartStop(self, action):
        pass

    def WorkingOnUpdate(self, action, num, total, id, desc):
        pass


def isOk(res):
    if res:
        quit(res)


def quit(result):
    """:: Uptrack.Result -> IO ()

    Exit the program, performing appropriate reporting and cleanup
    before we do so. In particular, we must write the current status
    and result to disk (for the UI) and back to the server.

    In addition, we need to make sure we have written out an upgrade
    plan (for the GUI to display), an init-time plan (for next boot),
    and a remove plan (for if we try to remove all updates without
    network).

    If we are exiting successfully, the previous code paths have
    written out new plans if needed. If we're exiting with failure,
    however, we don't know what state we're in, so compute new plans
    to be safe.
    """
    if result:
        if alert:
            result.alert = alert
        if desupported:
            result.desupported = desupported
        if tray_icon_error:
            result.tray_icon_error = tray_icon_error

    if result and result.code == Uptrack.ERROR_USER_NO_CONFIRM:
        logging.debug("User did not confirm actions. Not writing new plans.")
        local.writeOutStatus(result, None, None, None)
    elif not config.uninstall and \
         not config.allow_no_net and result and result.code and repo.updates and \
            result.code not in (Uptrack.ERROR_UNSUPPORTED,
                                Uptrack.ERROR_NO_NETWORK,
                                Uptrack.ERROR_INVALID_KEY,
                                Uptrack.ERROR_MACHINE_NOT_ACTIVATED,
                                Uptrack.ERROR_EXPIRED,
                                Uptrack.ERROR_SYS_NOT_MOUNTED,
                                Uptrack.ERROR_MISSING_KEY):
            # Need network because planActions may call out to network
        try:
            logging.debug("Determining new upgrade/init-time/remove plans.")
            (_, upgrade, init, remove) = planActions(repo, local, [])
            local.writeOutStatus(result, upgrade, init, remove)
        except Exception:
            if result.code == Uptrack.ERROR_INTERNAL_SERVER_ERROR:
                report = logging.debug
            else:
                report = logging.error
            report("Error making upgrade/init-time/remove plans")
            logging.debug(traceback.format_exc())
            report("")

            if result and result.code != 0:
                local.writeOutStatus(result, None, None, None)
            else:
                # Something did go wrong. However, if we just used `result`,
                # the GUI wouldn't notice because the result code is zero.
                internal_error = Uptrack.Result()
                internal_error.code = Uptrack.ERROR_GENERIC_ERROR
                internal_error.message = ("Internal error while writing Uptrack"
                                          " status files.\nSee %s for more details.") % LOGFILE
                local.writeOutStatus(internal_error, None, None, None)
    elif result and result.code:
        logging.debug("Failed, but running without network. Not writing new plans.")
        local.writeOutStatus(result, None, None, None)
    else:
        logging.debug("Exiting with success. Not writing new plans.")
        local.writeOutStatus(result, None, None, None)

    if result and result.message:
        if result.code != 0 and result.code != Uptrack.ERROR_USER_NO_CONFIRM:
            if config.cron and result.code in (Uptrack.ERROR_NO_NETWORK,
                                               Uptrack.ERROR_INTERNAL_SERVER_ERROR,
                                               Uptrack.ERROR_EXPIRED,
                                               Uptrack.ERROR_MACHINE_NOT_ACTIVATED):
                # Don't send cron email about these transient failures; just log them.
                logging.debug(result.message)
            else:
                logging.error(result.message)
        else:
            logging.info(result.message)
    config.notify.ClientStartStop('STOP')
    code = 0
    if result:
        code = result.code
    sys.exit(code)


def prettyResult(act):
    msg = None
    why = act.abort_code
    if why:
        if why == 'code_busy' and len(act.stack_check_processes) == 1 and \
           act.stack_check_processes[0][0] == 'krfcommd':
            msg = "Ksplice was unable to " + act.command.lower() + \
                " the update because the rfcomm module (used for bluetooth)" + \
                " is erroneously triggering a conservative Ksplice safety check. " + \
                " If you are not using bluetooth on this system, you can install this" + \
                " update by first unloading the rfcomm module using \"rmmod rfcomm\" and trying" + \
                " again.  Please contact %s if you have any questions." % Uptrack.BUG_EMAIL
            msg = "\n" + textwrap.fill(msg)
        elif why == 'code_busy':
            msg = "Ksplice was unable to " + act.command.lower() + \
                " the update because one or more programs are constantly" + \
                " using the kernel functions patched by this update.  You" + \
                " should be able to install this update by trying again.  If" + \
                " trying again does not work, please report this problem to" + \
                " %s. " % Uptrack.BUG_EMAIL + \
                " Even if trying again does not work, closing the following" + \
                " programs should make it possible to install this update:"
            msg = "\n" + textwrap.fill(msg) + "\n\n"
            for proc in act.stack_check_processes:
                pid_i = 1
                for i, word in enumerate(proc):
                    if word.isdigit():
                        pid_i = i

                p = ' '.join(proc[:pid_i])
                pid = proc[pid_i]
                msg += "  - %s (pid %s)\n" % (p, pid)
        elif why == 'out_of_memory':
            msg = "Ksplice failed to " + act.command.lower() + \
                " this update because your kernel is out of memory.  Ksplice's memory" + \
                " consumption is minimal, so this is likely caused by some other problem" + \
                " on your system"
            msg = "\n" + textwrap.fill(msg)
        elif why == 'cold_update_loaded':
            msg = "Ksplice was unable to remove the update because modules" + \
                " that Uptrack patched off-line are currently loaded.  In order" + \
                " to remove this update, you will need to first unload the" + \
                " following kernel modules:"
            msg = "\n" + textwrap.fill(msg)
            msg += "\n\n"
            msg += "\n".join([" - %s" % (m,) for m in act.locked_modules])
        elif why in ['no_match', 'failed_to_find']:
            if Uptrack.inVirtualBox():
                msg = "\n" + textwrap.fill(
                    "Ksplice was unable to " + act.command.lower() +
                    " the update because it could not match the code to be"
                    " patched in your running kernel.  This could be caused by"
                    " running Ksplice inside VirtualBox without the VT-x/AMD-V"
                    " setting enabled.")
                msg += (
                    "\n"
                    "For more information, see http://www.ksplice.com/uptrack/help/virtualbox"
                    "\n\n")
                msg += textwrap.fill(
                    "If you are not running VirtualBox, or enabling VT-x/AMD-V"
                    " does not solve the problem, please report this bug to "
                    + Uptrack.BUG_EMAIL + ".")
            elif len(act.nomatch_modules) > 0:
                msg = "\n" + textwrap.fill(
                    "Ksplice was unable to install this update because the code in your running" +
                    " kernel does not match the expected version.") + "\n"
                if os.path.exists('/etc/debian_version'):
                    # This only happens on Debian / Ubuntu, due to their policy of not always
                    # updating 'uname -r'.
                    msg += "\n" + textwrap.fill(
                        "You may have upgraded your on-disk kernel package and subsequently loaded"
                        " one of the updated modules.  You may also be running a backported or"
                        " custom-compiled kernel module.  The non-matching modules are:") + "\n\n"
                else:
                    msg += "\n" + textwrap.fill(
                        "You may be running a backported or custom-compiled version of" +
                        " one or more of the following kernel modules that were provided by"
                        " your Linux vendor:") + "\n\n"
                for module in act.nomatch_modules:
                    msg += "  - %s\n" % module
                msg += "\n" + textwrap.fill(
                    "If you are not intentionally using a different version of these modules," +
                    " you should be able to install this update by running the following"
                    " commands as root:") + "\n"
                for module in act.nomatch_modules:
                    msg += "\n" + "rmmod " + module
                msg += "\n\n" + textwrap.fill("and then trying again. " +
                                              " If you are unable to resolve this issue, please contact" +
                                              " %s for assistance. " % Uptrack.BUG_EMAIL)
            else:
                msg = "\n" + textwrap.fill(
                    "Ksplice was unable to install this update because your running" +
                    " kernel has been modified from the version provided by your vendor.")
                msg += (
                    "\n"
                    "Please contact %s for help resolving this issue." % (Uptrack.BUG_EMAIL,))
        elif why == 'module_busy' and act.command == REMOVE and act.usedby_modules:
            msg = "\n" + textwrap.fill("Ksplice was unable to remove" +
                                       " the update because it is in use by one or more kernel modules" +
                                       " that have been loaded since the update was applied.  In order" +
                                       " to remove this update, you will need to first unload the" +
                                       " following kernel modules:"
                                       )
            msg += "\n\n"
            msg += "\n".join([" - %s" % (m,) for m in act.usedby_modules])
            msg += "\n\n" + textwrap.fill(
                "You can unload a module by running \"rmmod <module name>\" as root." +
                " If you are unable to resolve this issue, please contact" +
                " %s for assistance. " % Uptrack.BUG_EMAIL)
        elif why == 'key_not_available':
            msg = "\n" + textwrap.fill("Ksplice was unable to apply the update" +
                                       " because the kernel does not trust the key that was used to" +
                                       " sign the update. This is most likely caused by your system" +
                                       " using UEFI secure boot." +
                                       " Ksplice does not yet support secure boot on platforms other" +
                                       " than Oracle Linux." +
                                       " Please contact %s if you have any further questions." % (Uptrack.BUG_EMAIL))

    if not msg:
        msg = "Ksplice was unable to " + act.command.lower() + " this update" + \
            " due to an unexpected internal error."
        msg = "\n" + textwrap.fill(msg)

        msg += "\n\n"

        msg += "Please report this bug to <%s>.\n" % (Uptrack.BUG_EMAIL,)
        msg += "Uptrack log file: %s" % (LOGFILE,)

    return msg


def initializeDBus():
    if have_dbus and not config.init:  # No dbus at init!
        dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
        notify = DbusNotifications('/com/ksplice/uptrack/UptrackUI')
    else:
        logging.debug("D-Bus not present, will not notify uptrack-manager.")
        notify = NonDbusNotifications('/com/ksplice/uptrack/UptrackUI')
    config.notify = notify


def desync():
    logging.debug("Invoked by the cron job.")

    counterfile = config.localroot + '/backoff-counter'
    try:
        backoff = float(Uptrack.read_file(config.localroot + '/backoff'))
        backoff_counter = float(Uptrack.read_file(counterfile))
    except (IOError, ValueError):
        backoff = 1
        backoff_counter = 0
    backoff_counter += 1
    if backoff_counter < backoff:
        logging.debug("Counter is %s/%s, waiting for next time.",
                      backoff_counter, backoff)
        Uptrack.write_file(counterfile, str(backoff_counter) + '\n')
        sys.exit(0)
    logging.debug("Counter is %s/%s, proceeding.", backoff_counter, backoff)
    backoff_counter -= backoff
    Uptrack.write_file(counterfile, str(backoff_counter) + '\n')

    # Sleep between 0 and 60s to desync the cron jobs across the
    # minute.
    time.sleep(random.randint(0, 59))


def extractCommand(name):
    command = None
    if name == 'uptrack-upgrade':
        command = UPGRADE
    elif name == 'uptrack-install':
        command = INSTALL
    elif name == 'uptrack-remove':
        command = REMOVE
    elif name == 'uptrack-show':
        command = SHOW
    else:
        logging.error("I don't know what you want me to do.")
        sys.exit(-1)
    return command


def checkCommand(command):
    """Verify that command is allowed in combination with current options. """
    if (command == REMOVE and not config.all) and config.allow_no_net:
        logging.error("Sorry, removing individual updates requires a network connection.")
        logging.error("(Run this command again without --no-network?)")
        logging.error("")
        logging.error("If you wish to remove all updates, run this command again")
        logging.error("with --all (this does not require a network connection).")
        sys.exit(1)
    if (command == REMOVE and not config.all) and len(config.args) == 0:
        logging.error("Please specify update IDs to remove or use the --all argument.")
        sys.exit(1)
    if config.uninstall and command != REMOVE:
        logging.error("--uninstall may only be used with uptrack-remove.")
        sys.exit(1)
    if config.all and config.args:
        logging.error("Specifying an update as well as --all makes no sense!")
        sys.exit(1)
    if command == SHOW:
        if config.all and config.options.count:
            logging.error("Using --all and --count at the same time is not supported.")
            sys.exit(1)
        if config.available and config.options.count:
            logging.error("Using --available and --count at the same time is not supported.")
            sys.exit(1)


def needs_access_key(command, config):
    """Indicates if the user is both missing an access key and needs one
    for the current operation. Always false in offline edition, since
    offline edition doesn't actually talk to the server. """

    if in_offline_mode or config.allow_no_net:
        return False
    elif command == REMOVE and config.uninstall:
        return False
    else:
        return config.accesskey in ['', 'INSERT_ACCESS_KEY']


def selinux_label():
    restorecon = '/sbin/restorecon'
    """Restores the default SELinux labelling for the updates cache"""
    try:
        p = subprocess.Popen([restorecon, '-R', config.localroot],
                             stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        stdout, stderr = p.communicate()
        stdout = stdout.decode('utf-8')
        stderr = stderr.decode('utf-8')

        if p.returncode:
            logging.debug("Error executing %s" % restorecon)
            if stdout:
                logging.debug("stdout:")
                logging.debug(stdout)
            if stderr:
                logging.debug("stderr:")
                logging.debug(stderr)
    except OSError as e:
        logging.debug("Failed to execute %s" % restorecon)
        logging.debug(str(e))


def check_directory_perms(name, uid, gid, mode):
    try:
        result = os.stat(name)
    except OSError as e:
        if e.errno == errno.ENOENT:
            return
        raise
    current_mode = result.st_mode & (stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR |
                                     stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP |
                                     stat.S_IROTH | stat.S_IWOTH | stat.S_IXOTH)
    if result.st_uid != uid or result.st_gid != gid \
       or not stat.S_ISDIR(result.st_mode) \
       or current_mode != mode:
        shutil.rmtree(name, ignore_errors=True)

def spawn_yum_upgrade():
    def offline_updates_package_name():
        _, _, release, _, machine = os.uname()
        return "uptrack-updates-" + release

    def yum_install_arguments():
        if config.answer_yes:
            return ['yum', '-y', 'install', offline_updates_package_name()]
        return ['yum', 'install', offline_updates_package_name()]

    def cleanup_before_exec():
        config.notify.ClientStartStop('STOP')
        logging.shutdown()

    args = yum_install_arguments()
    logging.debug("Spawning: %s" % (args))
    cleanup_before_exec()
    os.execlp('yum', *args)

def main(program, args):
    global config
    global local, repo
    global logger
    global in_offline_mode

    default_path = "/usr/sbin:/usr/bin:/sbin:/bin"
    if 'PATH' not in os.environ or not os.environ['PATH']:
        os.environ['PATH'] = default_path
    else:
        os.environ['PATH'] += ":" + default_path

    in_offline_mode = is_offline()

    try:
        if in_offline_mode:
            config = OfflineUptrackClientConfig(program, args)
        else:
            config = OnlineUptrackClientConfig(program, args)
        logger.configure(config)
        config.checkDeprecatedOptions()
    except Uptrack.ResultException as e:
        logging.error("Error loading configuration file:")
        logging.error(e.result.message)
        sys.exit(e.result.code)

    if config.options.show_version:
        print("%s" % __version__)
        sys.exit(0)

    if config.options.check_init:
        if config.disabled or not config.install_on_reboot:
            sys.exit(1)
        sys.exit(0)

    os.umask(0o022)  # GUI needs to be able to read status files etc.

    Uptrack.initCurl(config.clientConfig)

    initializeDBus()

    logging.debug("")
    logging.debug("Client invoked as: %s %s" % (program, ' '.join(args)))

    if in_offline_mode and config.options.yum_upgrade:
        spawn_yum_upgrade()

    command = extractCommand(os.path.basename(program))
    if config.init == 'early':
        command = INIT
    elif config.init == 'late' and not config.upgrade_on_reboot:
        # No reason to delay reboot if we aren't upgrading
        logging.debug("Client exiting due to early init and no upgrade_on_reboot")
        sys.exit(0)

    checkCommand(command)

    for name, uid, gid, mode in Uptrack.dir_perms:
        check_directory_perms(name, uid, gid, mode)
    Uptrack.mkdirp(config.local)
    Uptrack.mkdirp(os.path.join(config.local, 'updates'))

    if config.rh_derivative:
        selinux_label()

    if config.cron:
        desync()

    lock = getLock(config.lockfile)
    if lock is False:
        logging.debug("Unable to acquire the Uptrack repository lock: %s",
                      config.lockfile)
        logging.error("""\
It appears that another Uptrack process is currently running on this
system. Please wait a minute and try again.  If you are unable to
resolve this issue, please contact %s.""" % (Uptrack.BUG_EMAIL,))
        sys.exit(1)

    # Check that we have an access key, before going any further.
    if needs_access_key(command, config) and os.path.exists(AUTOGEN_FLAG_FILE):
        accepted = False
        if config.accept_tos:
            accepted = True
        elif not config.init and os.isatty(sys.stdin.fileno()):
            tos = Uptrack.read_file(TOS_FILE)
            p = subprocess.Popen(['more'], stdin=subprocess.PIPE)
            p.communicate(('In order to use the Ksplice Uptrack service, you must \n'
                           'agree to the Ksplice Uptrack terms of service:\n\n%s' % tos).encode('utf-8'))
            while True:
                choice = input('Do you agree to the Ksplice Uptrack terms of service? [yes|no] ').strip().lower()
                if choice == 'yes':
                    accepted = True
                    break
                elif choice == 'no':
                    accepted = False
                    break

        if accepted:
            logging.warning('Requesting access key... (this may take a few moments)')
            bytesio = BytesIO()

            try:
                code = Uptrack.download(Uptrack.getCurl(), AUTOGEN_URL,
                                        '/dev/null', in_offline_mode,  bytesio=bytesio)
                if code != 200:
                    logging.error('The Ksplice Uptrack server gave a response code of %d\n'
                                  'while requesting access key.  Please contact\n'
                                  '%s for assistance.' % (code, Uptrack.BUG_EMAIL,))
                    sys.exit(1)
            except pycurl.error as e:
                logging.debug("cURL error %d (%s) while requesting access key."
                              % (e.args[0], e.args[1]))
                logging.debug(traceback.format_exc())
                logging.error(Uptrack.resultFromPycurl(config, e).message)
                sys.exit(1)

            p = subprocess.check_call(['sed', '-i', '-e', 's/^\\s*accesskey\\s*=.*/accesskey = %s/' %
                                       bytesio.getvalue().decode('utf-8'), '/etc/uptrack/uptrack.conf'])
            logging.info('Access key successfully requested')
            releaseLock(lock)
            # We're just marking ourselves as accepted for the GUI
            if config.accept_tos:
                sys.exit(0)

            # Restart with the updated config file
            os.execv(sys.argv[0], sys.argv)
        else:
            logging.info('You must accept the Ksplice Uptrack terms of service\n'
                         'in order to use the service.')
            sys.exit(Uptrack.ERROR_MISSING_KEY)

    # Some initialization has to happen inside the lock to serial accesses.
    try:
        config.initWithLock()
    except Uptrack.ResultException as e:
        logging.error("Error loading configuration file:")
        logging.error(e.result.message)
        sys.exit(e.result.code)

    config.notify.ClientStartStop('START')
    repo = UptrackRepo(config)
    local = Uptrack.LocalStatus(config, repo, logger)

    if needs_access_key(command, config):
        res = Uptrack.Result()
        res.code = Uptrack.ERROR_MISSING_KEY
        res.message = "You must specify an access key to use the service.\n"
        res.message += ("Please add your key to %s" % Uptrack.UPTRACK_CONFIG_FILE)
        quit(res)

    will_download = False

    if command != SHOW and not config.allow_no_net and not config.uninstall:
        if not in_offline_mode:
            isOk(repo.validateServer())
            isOk(repo.handleStatus())
        will_download = True
    else:
        res = repo.parsePackageList()
        if res and res.code == Uptrack.ERROR_NO_NETWORK and command == SHOW and \
                config.options.count and config.allow_no_net:
                # uptrack-show --count --no-network (invoked by removal hooks)
                #
                # No packages.yml file exists on disk.  Assuming packages.yml
                # wasn't deleted, there can be no updates installed.
            logging.info("0")
            sys.exit(0)
        if res and res.code and command == SHOW:
            logging.error("Unable to read the package list.\n"
                          "Please run 'uptrack-upgrade -n' to download "
                          "the latest package list.")
            sys.exit(1)
        if res and res.code and command == INIT:
            # There's no packages.yml, but we still need to clean /var/run/{ksplice,uptrack}
            doInitClean(local)
            if res.code == Uptrack.ERROR_NO_NETWORK:
                    # User rebooted into a new kernel, so don't be alarmed
                    # that there's no packages.yml
                res.code = 0
                res.newkernel = True
                res.message = "Ksplice Uptrack: booting into a new kernel, so not installing any updates."
                quit(res)

        isOk(res)

    if not os.path.isdir('/sys/module') and (
            command != SHOW or not config.options.count):
        # Put this check after they've confirmed it is a supported kernel,
        # so that we don't need to worry about CONFIG_MODULES being off.
        if os.path.exists('/proc/vz'):
            message = ("Error: You are running Ksplice Uptrack inside a Virtuozzo/OpenVZ container,\n"
                       "  but it needs to run on the hardware node instead.\n"
                       "\n"
                       "  If you have purchased a virtual private server (VPS) from a hosting company,\n"
                       "  please contact them and ask them to purchase Ksplice Uptrack for their VPS\n"
                       "  systems. If you are the VPS provider, please install Ksplice Uptrack on the\n"
                       "  hardware node rather than in a container.\n"
                       "  If you need help resolving this issue, please contact %s."
                       % (Uptrack.BUG_EMAIL,))
        else:
            message = ("Error: The directory /sys/module was not found.\n"
                       "  You must have the /sys filesystem mounted in order to use Ksplice Uptrack.\n"
                       "  If you are running inside a chroot, you must mount /sys inside the chroot.\n"
                       "  This could also be caused by an old or non-standard system configuration.\n"
                       "  If you need help resolving this issue, please contact %s."
                       % (Uptrack.BUG_EMAIL,))

        res = Uptrack.Result(Uptrack.ERROR_SYS_NOT_MOUNTED, message)
        quit(res)

    if will_download:
        isOk(repo.downloadAll())

    if config.disablecmd and command == INIT:
        open('/etc/uptrack/disable', 'w')
        config.disabled = True
    if config.disabled and command != SHOW:
        res = Uptrack.Result()
        res.code = Uptrack.ERROR_GENERIC_ERROR
        res.message = "Uptrack disabled by system administrator, remove /etc/uptrack/disable to enable."
        quit(res)

    if repo.expired:
        if command != SHOW and not (command == REMOVE and config.all):
            if command == REMOVE:
                msg = "Removing individual updates is disabled."
            else:
                msg = "Installing updates is disabled."

            quit(Uptrack.Result(Uptrack.ERROR_EXPIRED, msg))

    if command == INIT:
        res = doInit(repo, local)
    elif command == UPGRADE:
        if config.list_effective:
            listAvailableEffectiveVersionsAndQuit()
        if config.effective_version:
            res = doUpgradeToEffectiveVersion(repo, local, config.effective_version)
        else:
            res = doUpgrade(repo, local)
    elif command == INSTALL:
        res = doInstall(repo, local, config.args)
    elif command == REMOVE:
        # Special case: We are able to remove all updates using the
        # saved remove_plan without going to the server.

        # We do this for 'remove --no-network', if our access key has
        # expired (in which case we won't be able to reach the
        # server), or when uninstalling the package, so we can be
        # removed without network.
        if config.uninstall or \
                config.all and (config.allow_no_net or repo.expired):
            res = doOfflineRemoveAll(repo, local)
        else:
            res = doRemove(repo, local, config.args)
        printEffective()
    elif command == SHOW:
        res = doShow(repo, local, config.args)

    if res and command in [INIT, UPGRADE, INSTALL, REMOVE]:
        if res.code == Uptrack.ERROR_USER_NO_CONFIRM:
            quit(res)
        if len(res.succeeded):
            logging.debug("")
            logging.debug("The following actions were successful:")
            for act in res.succeeded:
                logging.debug("%s %s" % (act.command, act.update))
        if len(res.failed):
            logging.info("")
            logging.error("The following actions failed:")
            for act in res.failed:
                logging.error("%s %s" % (act.command, act.update))
                logging.error(prettyResult(act))
                logging.debug("Message:\n" + act.message)
            logging.error("")
            # merge all failed.*.code's into one code
            res.code = Uptrack.ERROR_GENERIC_ERROR

    quit(res)


if __name__ == "__main__":
    try:
        logger = Logger()
    except Exception:
        print("Unable to set up the logger.", file=sys.stderr)
        if os.getuid() != 0:
            print("The Uptrack client must be run as root.", file=sys.stderr)
            sys.exit(1)
        else:
            raise

    try:
        main(sys.argv[0], sys.argv[1:])
    except KeyboardInterrupt:
        logging.error("Interrupted!")
        logging.debug("", exc_info=1)
        sys.exit(1)
    except SystemExit:
        raise
    except Exception:
        # Catch unhandled exceptions and report them to the server.
        res = Uptrack.Result(Uptrack.ERROR_INTERNAL_ERROR, traceback.format_exc())
        if local is None:
            logging.error("Unexpected error starting the Uptrack client.")
            logging.error("Please submit a copy of %s to %s." % (LOGFILE, Uptrack.BUG_EMAIL))
            logging.debug(res.message)
            sys.exit(-1)
        try:
            local.writeOutStatus(res, None, None, None)
        except SystemExit:
            raise
        except Exception:
            pass

        logging.debug("Unhandled exception", exc_info=1)
        logging.error("Unexpected error.")
        logging.error("Please submit a copy of %s to %s." % (LOGFILE, Uptrack.BUG_EMAIL))
        sys.exit(-1)

"""
=head1 NAME

uptrack - Manage Ksplice rebootless kernel updates

=head1 SYNOPSIS

B<uptrack-upgrade> [I<OPTION>]

B<uptrack-install> [I<OPTION>] I<id>...

B<uptrack-remove> [I<OPTION>] I<id>...

B<uptrack-show> [I<OPTION>] [I<id>...]

=head1 DESCRIPTION

The Uptrack command-line tools manage the set of Ksplice rebootless
kernel updates installed on your system. There are four major modes of
operation:

=over 4

=item B<uptrack-upgrade>

Downloads and installs the latest Ksplice updates available for your system.

=item B<uptrack-install>

Takes as arguments the update IDs to install, and installs them,
downloading them if necessary.

=item B<uptrack-remove>

Takes as arguments the update IDs to remove, and removes them.

=item B<uptrack-show>

If invoked without additional arguments, shows the list of Ksplice
updates currently installed.  If update IDs are passed as arguments,
displays the status of those updates as well as the detailed
information associated with them.

=back

=head1 OPTIONS

=over 4

=item B<-v>, B<--verbose>

Provide more detail about what the program is doing.

=item B<-q>, B<--quiet>

Do not print status messages.

=item B<-y>

Assume "yes" to all user prompts.

=item B<-n>

Assume "no" to all user prompts.

=item B<--all>

Take action for all of the IDs that Uptrack knows about, instead of
specifying them at the command-line.

For uptrack-show, this will list both installed updates and available
updates.

=item B<--available>

With C<uptrack-show>, instead of showing installed updates, shows the
available updates.

=item B<--count>

With C<uptrack-show>, instead of showing installed updates, print the
number of updates installed.

=item B<--wait=N>

When installing or removing a sequence of updates, wait B<N> seconds
after processing each update before processing the next one.

=item B<-V>, B<--version>

Print the version information and exit.

=back

=head1 FILES

=over 4

=item I</etc/uptrack/uptrack.conf>

Configuration file for Uptrack.

=item I</etc/uptrack/disable>

If this file exists, Uptrack will refuse to install or remove updates.
If 'nouptrack' is passed on the kernel command line, then no updates
will be installed at boot time, and I</etc/uptrack/disable> will
automatically be created during the boot process.

=back

=head1 BUGS

Please report bugs to <ksplice-support_ww@oracle.com>.

=head1 AUTHORS

Waseem Daher and Tim Abbott

=head1 COPYRIGHT

See included LICENSE file for additional license and copyright information.

=cut
"""

Youez - 2016 - github.com/yon3zu
LinuXploit