#!/usr/bin/env python

import sys
import os
import re
import stat
import time
import subprocess
import logging, logging.handlers
import platform
import ConfigParser
import dbus

# defaults
config_file = '/etc/synce-hal.conf'
log_level = logging.WARNING
default_network = "192.168.131"
pppopts = ["nodefaultroute", "noauth", "local", "crtscts"]

# ppp setup
#
PPPD = "/usr/sbin/pppd"
VAR_PPP = "/var/run"
DEVSPEED = '115200'

DBUS_DBUS_BUSNAME       = "org.freedesktop.DBus"
DBUS_DBUS_IFACE         = "org.freedesktop.DBus"
DBUS_DBUS_OBJPATH       = "/org/freedesktop/DBus"

DBUS_HAL_BUSNAME         = "org.freedesktop.Hal"
DBUS_HAL_MANAGER_IFACE   = "org.freedesktop.Hal.Manager"
DBUS_HAL_MANAGER_OBJPATH = "/org/freedesktop/Hal/Manager"
DBUS_HAL_DEVICE_IFACE    = "org.freedesktop.Hal.Device"

def synce_serial_start_device(device_file, local_ip, remote_ip, linkname):

    ipaddr = local_ip+":"+remote_ip

    cmd_list = [PPPD, device_file, DEVSPEED, "connect", "/usr/libexec/synce-serial-chat", ipaddr, "ms-dns", local_ip]
    for item in pppopts:
        cmd_list.append(item)

    if platform.system() != "FreeBSD":
        cmd_list.append("linkname")
        cmd_list.append(linkname)

    logger.debug("calling pppd as:")
    logger.debug(cmd_list)

    # Now bring up the connection

    # returns a Popen object
    try:
        proc = subprocess.Popen(cmd_list, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
        output_text = proc.communicate()[0]
    except Exception,e:
        logger.error("failure running pppd: %s" % e)
        return False

    rc = proc.returncode

    if rc != 0:
        logger.error("failed to initialize ppp connection, return code %d",rc)
        return False

    return True


def synce_serial_stop_device(linkname):

    pidfile = VAR_PPP+"/ppp-"+linkname+".pid"

    if os.path.isfile(pidfile) != True:
        logger.warning("unable find a running SynCE connection for %d", linkname)
        sys.exit(1)

    while os.path.isfile(pidfile):
        try:
            f = open(pidfile, 'r')
            textpid = f.readline().rstrip()
            f.close()
        except Exception, e:
            logger.warning("unable to read PID from %s: %s", pidfile, e)
            sys.exit(1)

        try:
            pid = int(textpid)
        except Exception, e:
            logger.error("found invalid PID in %s: %s", pidfile, e)
            sys.exit(1)

        logger.info("killing process with PID %d", pid)

        try:
            os.kill(pid, signal.SIGTERM)
        except Exception,e:
            logger.error("unable stop the SynCE connection with PID %d: %s", pid, e)
            sys.exit(1)

        logger.info("requested shutdown of %s", linkname)

        time.sleep(3)


#
# run dccm after the interface is configured
#
def RunDccm(device_ip, local_ip):

    logger.debug("starting hal-dccm ...")

    dccm_log_level = "1"

    if log_level <= logging.DEBUG:
        dccm_log_level = "6"
    elif log_level <= logging.INFO:
        dccm_log_level = "4"
    elif log_level <= logging.WARNING:
        dccm_log_level = "3"

    cmd_list = ["/usr/libexec/hal-dccm", "--device-ip="+device_ip, "--local-ip="+local_ip, "--log-level="+dccm_log_level]

    logger.debug("calling hal-dccm as:")
    logger.debug(cmd_list)

    try:
        os.execv("/usr/libexec/hal-dccm", cmd_list)
    except Exception,e:
        logger.error("failed to exec hal-dccm !!: %s" % e)

    return


def ProcessConfig():

    global log_level
    global default_network
    global pppopts

    logger = logging.getLogger("hal-synce-serial")

    config = ConfigParser.ConfigParser()
    try:
        config.read(config_file)
    except Exception,e:
        logger.warning("failed to parse config file %s: %s" % (config_file, e))
        return False

    if config.has_option('general', 'loglevel'):
        loglevel_txt = config.get('general', 'loglevel').lower()
        if loglevel_txt == 'critical':
            log_level = logging.CRITICAL
        elif loglevel_txt == 'error':
            log_level = logging.ERROR
        elif loglevel_txt == 'warning':
            log_level = logging.WARNING
        elif loglevel_txt == 'info':
            log_level = logging.INFO
        elif loglevel_txt == 'debug':
            log_level = logging.DEBUG
        else:
            logger.warning("found invalid loglevel in config file %s: %s" % (config_file, loglevel_txt))

        logger.setLevel(log_level)

    if config.has_option('serial', 'pppopts'):
        extra_opts = config.get('serial', 'pppopts').split()
        for item in extra_opts:
            pppopts.append(item)

    if config.has_option('serial', 'network'):
        new_network = config.get('serial', 'network')
        
        network_re = re.compile('^(\d{1,3}\.\d{1,3}\.\d{1,3}$)')
        network_match = network_re.match(new_network)
        if network_match != None:
            default_network = new_network
        else:
            logger.warning("found invalid network in config file %s: %s" % (config_file, new_network))

    return True


#
# main()
#
if __name__ == '__main__':

    log_facility = logging.handlers.SysLogHandler.LOG_DAEMON

    logging.basicConfig(level=logging.WARNING,
                        format='%(asctime)s %(name)s %(levelname)s : %(message)s')
    logger = logging.getLogger("hal-synce-serial")
    sys_log = logging.handlers.SysLogHandler("/dev/log", log_facility)
    syslog_form = logging.Formatter('%(name)s[%(process)d] %(levelname)s : %(message)s')
    sys_log.setFormatter(syslog_form)
    logger.addHandler(sys_log)

    if os.environ.has_key("HALD_ACTION"):
        hald_action = os.environ["HALD_ACTION"]
    else:
        logger.critical("Hal environment not set")
        sys.exit(1)

    ProcessConfig()

    # deal with 2 port devices
    # we want to ignore the first port

    our_udi = os.environ["HAL_PROP_INFO_UDI"]

    try:
        device_haldev = dbus.Interface(dbus.SystemBus().get_object(DBUS_HAL_BUSNAME, our_udi), DBUS_HAL_DEVICE_IFACE)
    except:
        logger.critical("failed to connect to hal object %s: %s" % (our_udi, e))
        sys.exit(1)

    parent_udi = device_haldev.GetPropertyString("info.parent")

    try:
        hal_manager = dbus.Interface(dbus.SystemBus().get_object(DBUS_HAL_BUSNAME, DBUS_HAL_MANAGER_OBJPATH), DBUS_HAL_MANAGER_IFACE)
    except:
        logger.critical("failed to connect to hal device manager: %s" % e)
        sys.exit(1)

    udilist = hal_manager.GetAllDevices()

    device_udilist = []

    for udi in udilist:
        if parent_udi in udi and parent_udi != udi:
            device_udilist.append(udi)

    if len(device_udilist) > 1:
        device_udilist.sort()
        if our_udi != device_udilist[len(device_udilist) - 1]:
            device_haldev.SetPropertyBoolean("pda.pocketpc.disabled", True)
            sys.exit(0)


    if platform.system() == "FreeBSD":
        try:
            device_file = "/dev/ttyU"+os.environ["HAL_PROP_FREEBSD_UNIT"]
        except Exception,e:
            logger.critical("Hal environment not set: %s" % e)
            sys.exit(1)
    else:
        try:
            device_file = os.environ["HAL_PROP_SERIAL_DEVICE"]
        except Exception,e:
            logger.critical("Hal environment not set: %s" % e)
            sys.exit(1)

    if stat.S_ISCHR(os.stat(device_file).st_mode) == 0:
        logger.critical("unable to find a character device named "+device_file)
        sys.exit(1)

    devnum_re = re.compile('^[^0-9]*([0-9]*)$')
    devnum_match = devnum_re.match(device_file)
    if devnum_match != None:
        devnum = devnum_match.group(1)
    else:
        devnum = 0

    # Add 120 for /dev/ttyS*, to support both standard serial
    # and USB
    serial_re = re.compile('ttyS')
    serial_match = serial_re.match(device_file)
    if serial_match != None:
        devnum = int(devnum) + 120

    # see the "linkname" option in the man page for pppd
    linkname = "synce-device"+devnum

    if hald_action == "addon":
        logger.debug("running as addon for device "+device_file)

        local_ip = default_network+"."+str(int(devnum) + 1)
        remote_ip = default_network+"."+str(int(devnum) + 129)

        if synce_serial_start_device(device_file, local_ip, remote_ip, linkname) == False:
            logger.critical("failed to establish serial connection, aborting...")
            sys.exit(1)

        logger.debug("successfully configured interface")

        RunDccm(remote_ip, local_ip)

        logger.error("aborting ...")
        sys.exit(1)


    if hald_action == "remove":
        logger.debug("running for removal of device "+device_file)
        synce_serial_stop_device(linkname)

