Example #1
0
File: engine.py Project: hick/x84
def _loop(servers):
    """
    Main event loop. Never returns.
    """
    # pylint: disable=R0912,R0914,R0915
    #         Too many local variables (24/15)
    import logging
    import select
    import sys
    from x84.terminal import get_terminals, kill_session
    from x84.bbs.ini import CFG
    from x84.fail2ban import get_fail2ban_function

    SELECT_POLL = 0.02 # polling time is 20ms

    # WIN32 has no session_fds (multiprocess queues are not polled using
    # select), use a persistently empty set; for WIN32, sessions are always
    # polled for data at every loop.
    WIN32 = sys.platform.lower().startswith('win32')
    session_fds = set()

    log = logging.getLogger('x84.engine')

    if not len(servers):
        raise ValueError("No servers configured for event loop! (ssh, telnet)")

    tap_events = CFG.getboolean('session', 'tap_events')
    check_ban = get_fail2ban_function()
    locks = dict()

    while True:
        # shutdown, close & delete inactive clients,
        for server in servers:
            # bbs sessions that are no longer active on the socket
            # level -- send them a 'kill signal'
            for key, client in server.clients.items()[:]:
                if not client.is_active():
                    kill_session(client, 'socket shutdown')
                    del server.clients[key]
            # on-connect negotiations that have completed or failed.
            # delete their thread instance from further evaluation
            for thread in [_thread for _thread in server.threads
                           if _thread.stopped][:]:
                server.threads.remove(thread)

        server_fds = [server.server_socket.fileno() for server in servers]
        client_fds = [fd for fd in server.client_fds() for server in servers]
        check_r = server_fds + client_fds
        if not WIN32:
            session_fds = get_session_output_fds(servers)
            check_r += session_fds

        # We'd like to use timeout 'None', but the registration of
        # a new client in terminal.start_process surprises us with new
        # file descriptors for the session i/o. unless we loop for
        # additional `session_fds', a connecting client would block.
        ready_r, _, _ = select.select(check_r, [], [], SELECT_POLL)

        for fd in ready_r:
            # see if any new tcp connections were made
            server = find_server(servers, fd)
            if server is not None:
                accept(log, server, check_ban)

        # receive new data from tcp clients.
        client_recv(servers, log)
        terms = get_terminals()

        # receive new data from session terminals
        if WIN32 or set(session_fds) & set(ready_r):
            try:
                session_recv(locks, terms, log, tap_events)
            except IOError, err:
                # if the ipc closes while we poll, warn and continue
                log.warn(err)

        # send tcp data to clients
        client_send(terms, log)

        # send session data, poll for user-timeout and disconnect them
        session_send(terms)
Example #2
0
File: engine.py Project: hick/x84
def handle_lock(locks, tty, event, data, tap_events, log):
    """
    handle locking event of (lock-key, (method, stale))
    """
    import time
    from x84.terminal import get_terminals
    method, stale = data
    if method == 'acquire':
        # this lock is already held,
        if event in locks:
            # check if lock held by an active session,
            holder = locks[event][1]
            for _sid, _tty in get_terminals():
                if _sid == holder and _sid != tty.sid:
                    # acquire the lock from a now-deceased session.
                    log.debug('[{tty.sid}] {event} not acquired, '
                              'held by active session: {holder}'
                              .format(tty=tty, event=event, holder=holder))
                    break
                elif _sid == holder and _sid == tty.sid:
                    # acquire the lock from ourselves!  We'll allow it
                    # (this is termed, "re-entrant locking").
                    log.debug('{tty.sid}] {event} is re-acquired!'
                              .format(tty=tty, event=event))
            else:
                # lock is held by a now-defunct session, re-acquired.
                log.debug('[{tty.sid}] {event} re-acquiring stale lock, '
                          'previously held by session no longer active: '
                          '{holder}'
                          .format(tty=tty, event=event, holder=holder))
                del locks[event]

        # lock is not held, or release by previous block
        if event not in locks:
            # acknowledge its requirement,
            locks[event] = (time.time(), tty.sid)
            tty.master_write.send((event, True,))
            if tap_events:
                log.debug('[{tty.sid}] {event} granted lock.'
                          .format(tty=tty, event=event))

        # lock cannot be acquired,
        else:
            holder = locks[event][1]
            elapsed = time.time() - locks[event][0]
            if (stale is not None and elapsed > stale):
                # caller has decreed that this lock may be acquired even if
                # it already held, if it has been held longer than length of
                # time `stale`.  This is simply to prevent a global freeze
                # when the programmer knows the holder may fail to release,
                # though this is not currently used in the demonstration
                # system.
                locks[event] = (time.time(), tty.sid)
                tty.master_write.send((event, True,))
                log.warn('[{tty.sid}] {event} re-acquiring stale lock, '
                         'previously held active session {holder} after '
                         '{elapsed}s elapsed (stale={stale})'
                         .format(tty=tty, event=event, holder=holder,
                                 elapsed=elapsed, stale=stale))

            # signal busy with matching event, data=False
            else:
                tty.master_write.send((event, False,))
                log.warn('[{tty.sid}] {event} lock rejected; already held '
                         'by active session {holder} for {elapsed} seconds '
                         '(stale={stale})'
                         .format(tty=tty, event=event, holder=holder,
                                 elapsed=elapsed, stale=stale))

    elif method == 'release':
        if event not in locks:
            log.error('[{tty.sid}] {event} lock failed to release, '
                      'not acquired.'.format(tty=tty, event=event))
        else:
            del locks[event]
            if tap_events:
                log.debug('[{tty.sid}] {event} released lock.'
                          .format(tty=tty, event=event))