# Copyright 1999-2017. Parallels IP Holdings GmbH. All Rights Reserved.
from optparse import OptionParser, OptionError
import subprocess
import sys
import os.path
import pwd
import re
import logging as log

from plesk_exec import drop_privileges

DEFAULT_LANG_CONFIG = "/etc/locale.conf"


def prepare_default_env(user):
    # man 5 crontab:
    # Several  environment  variables  are set up automatically by the cron(8) daemon.
    # SHELL is set to /bin/sh, and LOGNAME and HOME are set from the /etc/passwd line of the crontab's owner.
    # PATH is set to "/usr/bin:/bin".
    env = dict()
    env["SHELL"] = '/bin/sh'
    env["PATH"] = '/usr/bin:/bin'
    env["LOGNAME"] = user
    env["HOME"] = pwd.getpwnam(user).pw_dir
    env["PWD"] = env["HOME"]
    return env


def set_lang_env(env):
    if not DEFAULT_LANG_CONFIG:
        return env
    try:
        # if default locale is not configured then corresponding variables are not set.
        with open(DEFAULT_LANG_CONFIG) as f:
            for line in f:
                m = re.match(r'^(LANG|LANGUAGE)\s*=\s*"?([^\"\s]+)"?$', line)
                if m:
                    env[m.group(1)] = m.group(2)
    except IOError:
        pass
    return env


def set_user_specified_env(env, override_vars):
    # man 5 crontab:
    # HOME, SHELL, and PATH may be overridden by settings in the crontab;
    # LOGNAME is the user that the job is running from, and may not be changed.
    unchangable = ["LOGNAME"]
    if override_vars is not None:
        for pair in override_vars:
            (key, value) = pair.split("=", 1)
            if key not in unchangable:
                env[key] = value
    return env


def split_command(line):
    # man 5 crontab:
    # Percent-signs (%) in the command, unless escaped with backslash (\), will be changed into newline characters, and all data
    # after the first % will be sent to the command as standard input.
    lines = [x.replace('\%', '%') for x in re.split(r'(?<!\\)%', line)]
    cmd = lines[0]
    content = "".join([x + "\n" for x in lines[1:]])
    return (cmd, content)


def run_command(env, command_line):
    (command, content) = split_command(command_line)
    cmd = [env["SHELL"], "-c", command]
    # W/A for CloudLinux cagefs.
    # Note that cagefs_enter can't be run as root.
    # Note that cagefs_enter modifies env a bit so PATH in cron_task_executor and cron will be different
    # (/sbin:/usr/sbin:/bin:/usr/bin VS /usr/bin:/bin)
    if os.path.exists("/bin/cagefs_enter") and os.getuid() != 0:
        cmd = ["/bin/cagefs_enter"] + cmd
    p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=sys.stdout, stderr=sys.stderr, close_fds=True, cwd=env["PWD"], env=env)
    p.communicate(input=content)
    return p.returncode


def configure_logging(verbosity):
    log_level = log.WARNING
    if verbosity > 1:
        log_level = log.DEBUG
    elif verbosity == 1:
        log_level = log.INFO
    log.basicConfig(
        format="%(asctime)s %(levelname)s: %(message)s",
        level=log_level,
        datefmt="%Y-%m-%d %H:%M:%S",
    )


def main():
    try:
        parser = OptionParser()
        parser.description = "Execute COMMAND in cron-like environment of specified USER."
        parser.add_option("-u", "--user", action="store", type="string", help="run by specified USER", metavar="USER")
        parser.add_option("-c", "--command", action="store", type="string", help="specify COMMAND for execution", metavar="COMMAND")
        parser.add_option("-e", "--env", action="append", type="string", dest="override_env", help="additional environment variables", metavar="KEY=VALUE")
        parser.add_option("-v", "--verbose", action="count", help="increase verbosity")
        (options, args) = parser.parse_args()

        configure_logging(options.verbose)

        if options.user is None:
            parser.print_help()
            raise OptionError("USER is not specified", "--user")
        if options.command is None:
            parser.print_help()
            raise OptionError("COMMAND is not specified", "--command")
        cron_env = prepare_default_env(options.user)
        cron_env = set_lang_env(cron_env)
        cron_env = set_user_specified_env(cron_env, options.override_env)
        drop_privileges(options.user)
        sys.exit(run_command(cron_env, options.command))
    except Exception, e:
        log.error(e)
        log.debug(e, exc_info=True)
        sys.exit(1)


if __name__ == "__main__":
    main()

# vim: ft=python ts=4 sts=4 sw=4 et :