コード例 #1
0
    def __init__(self,
                 manager=None,
                 id=None,
                 command=None,
                 autoclose=False,
                 autoclose_retain=5):
        self.width = 80
        self.height = 25
        self.id = id
        self.manager = manager
        self.autoclose = autoclose
        self.autoclose_retain = autoclose_retain
        self.output = BroadcastQueue()

        env = {}
        env.update(os.environ)
        env['TERM'] = 'linux'
        env['COLUMNS'] = str(self.width)
        env['LINES'] = str(self.height)
        env['LC_ALL'] = 'en_US.UTF8'

        self.command = command
        if not self.command:
            shell = os.environ.get('SHELL', None)
            if not shell:
                for sh in ['zsh', 'bash', 'sh']:
                    try:
                        shell = subprocess.check_output(['which', sh])
                        break
                    except:
                        pass
            self.command = shell

        args = ['sh', '-c', self.command]
        exe = 'sh'

        logging.info('Activating new terminal: %s', self.command)

        self.pid, self.fd = pty.fork()
        if self.pid == 0:
            setproctitle.setproctitle('%s terminal session #%i' %
                                      (sys.argv[0], os.getpid()))
            os.execvpe(exe, args, env)

        logging.info('Subprocess PID %s', self.pid)

        self.dead = False

        fl = fcntl.fcntl(self.fd, fcntl.F_GETFL)
        fcntl.fcntl(self.fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)

        self.stream_in = os.fdopen(self.fd, 'rb', 0)
        self.stream_out = os.fdopen(self.fd, 'wb', 0)
        self.pyte_stream = pyte.Stream()
        self.screen = pyte.DiffScreen(self.width, self.height)
        self.pyte_stream.attach(self.screen)

        self.last_cursor_position = None

        self.reader = gevent.spawn(self.reader_fn)
コード例 #2
0
ファイル: gate.py プロジェクト: youprofit/ajenti
 def __init__(self, session, name=None, log_tag=None, restricted=False,
              initial_identity=None):
     self.session = session
     self.process = None
     self.stream = None
     self.stream_reader = None
     self.worker = None
     self.name = name
     self.log_tag = log_tag
     self.restricted = restricted
     self.initial_identity = initial_identity
     self.q_http_replies = BroadcastQueue()
     self.q_socket_messages = BroadcastQueue()
コード例 #3
0
ファイル: gate.py プロジェクト: tawanda/ajenti
 def __init__(self, session, name=None, log_tag=None, restricted=False,
              initial_identity=None):
     self.session = session
     self.process = None
     self.stream = None
     self.stream_reader = None
     self.worker = None
     self.name = name
     self.log_tag = log_tag
     self.restricted = restricted
     self.initial_identity = initial_identity
     self.q_http_replies = BroadcastQueue()
     self.q_socket_messages = BroadcastQueue()
コード例 #4
0
class Push():
    """
    A service providing push messages to the client.
    """
    def __init__(self, context):
        self.q = BroadcastQueue()

    def register(self):
        return self.q.register()

    def push(self, plugin, msg):
        """
        Sends a push message to the client.

        :param plugin: routing ID
        :param msg: message
        """
        self.q.broadcast((plugin, msg))
コード例 #5
0
ファイル: push.py プロジェクト: Dolphi2/ajenti
class Push(object):
    """
    A service providing push messages to the client.
    """
    def __init__(self, context):
        self.q = BroadcastQueue()

    def register(self):
        return self.q.register()

    def push(self, plugin, msg):
        """
        Sends a push message to the client.

        :param plugin: routing ID
        :param msg: message
        """
        self.q.broadcast((plugin, msg))
コード例 #6
0
ファイル: terminal.py プロジェクト: Dolphi2/ajenti
    def __init__(self, manager=None, id=None, command=None, autoclose=False, autoclose_retain=5):
        self.width = 80
        self.height = 25
        self.id = id
        self.manager = manager
        self.autoclose = autoclose
        self.autoclose_retain = autoclose_retain
        self.output = BroadcastQueue()

        env = {}
        env.update(os.environ)
        env['TERM'] = 'linux'
        env['COLUMNS'] = str(self.width)
        env['LINES'] = str(self.height)
        env['LC_ALL'] = 'en_US.UTF8'

        self.command = command
        if not self.command:
            shell = os.environ.get('SHELL', None)
            if not shell:
                for sh in ['zsh', 'bash', 'sh']:
                    try:
                        shell = subprocess.check_output(['which', sh])
                        break
                    except:
                        pass
            self.command = shell

        args = ['sh', '-c', self.command]
        exe = 'sh'

        logging.info('Activating new terminal: %s', self.command)

        self.pid, self.fd = pty.fork()
        if self.pid == 0:
            setproctitle.setproctitle('%s terminal session #%i' % (sys.argv[0], os.getpid()))
            os.execvpe(exe, args, env)

        logging.info('Subprocess PID %s', self.pid)

        self.dead = False

        fl = fcntl.fcntl(self.fd, fcntl.F_GETFL)
        fcntl.fcntl(self.fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)

        self.stream_in = os.fdopen(self.fd, 'rb', 0)
        self.stream_out = os.fdopen(self.fd, 'wb', 0)
        self.pyte_stream = pyte.Stream()
        self.screen = pyte.DiffScreen(self.width, self.height)
        self.pyte_stream.attach(self.screen)

        self.last_cursor_position = None

        self.reader = gevent.spawn(self.reader_fn)
コード例 #7
0
class WorkerGate(object):
    def __init__(self,
                 session,
                 name=None,
                 log_tag=None,
                 restricted=False,
                 initial_identity=None):
        self.session = session
        self.process = None
        self.stream = None
        self.stream_reader = None
        self.worker = None
        self.name = name
        self.log_tag = log_tag
        self.restricted = restricted
        self.initial_identity = initial_identity
        self.q_http_replies = BroadcastQueue()
        self.q_socket_messages = BroadcastQueue()

    def start(self):
        pipe_parent, pipe_child = gipc.pipe(
            duplex=True,
            encoder=lambda x: pickle.dumps(x, 2),
        )
        self.stream = GateStreamServerEndpoint(pipe_parent)
        stream_child = GateStreamWorkerEndpoint(pipe_child)

        self.process = gipc.start_process(target=self._target,
                                          kwargs={
                                              'stream': stream_child,
                                              '_pipe': pipe_child,
                                          })

        logging.debug('Started child process %s', self.process.pid)

        self.stream_reader = gevent.spawn(self._stream_reader)

    def stop(self):
        logging.debug('Stopping child process %s', self.process.pid)

        try:
            os.killpg(self.process.pid, signal.SIGTERM)
        except OSError as e:
            logging.debug('Child process %s is already dead: %s',
                          self.process.pid, e)
            return

        self.process.terminate()
        self.process.join(0.125)
        try:
            os.kill(self.process.pid, 0)
            logging.debug('Child process %s did not stop, killing',
                          self.process.pid)
            os.kill(self.process.pid, signal.SIGKILL)
            os.killpg(self.process.pid, signal.SIGKILL)

            os.kill(self.process.pid, 0)
            logging.error('Child process %s did not stop after SIGKILL!',
                          self.process.pid)
        except OSError:
            pass

        self.stream_reader.kill(block=False)

    def _stream_reader(self):
        try:
            while True:
                resp = self.stream.buffer_single_response(None)
                if not resp:
                    return
                self.stream.ack_response(resp.id)
                if resp.object['type'] == 'socket':
                    self.q_socket_messages.broadcast(resp)
                if resp.object['type'] == 'http':
                    self.q_http_replies.broadcast(resp)
                if resp.object['type'] == 'terminate':
                    self.session.deactivate()
                if resp.object['type'] == 'restart-master':
                    aj.restart()
                if resp.object['type'] == 'reload-config':
                    aj.config.load()
                    aj.config.ensure_structure()
                    self.stream.send({
                        'type': 'config-data',
                        'data': aj.config.data,
                    })
                if resp.object['type'] == 'log':
                    method = {
                        'info': logging.info,
                        'warn': logging.warn,
                        'debug': logging.debug,
                        'error': logging.error,
                        'critical': logging.critical,
                    }.get(resp.object['method'], None)
                    if method:
                        method(resp.object['message'],
                               extra=resp.object['kwargs'])
        except greenlet.GreenletExit:
            pass

    def _target(self, stream=None, _pipe=None):
        self.worker = Worker(stream, self)
        self.worker.run()
コード例 #8
0
ファイル: tasks.py プロジェクト: youprofit/ajenti
 def __init__(self, context):
     self.context = context
     self.tasks = {}
     self.notifications = BroadcastQueue()
コード例 #9
0
ファイル: terminal.py プロジェクト: Mu-L/ajenti
    def __init__(self,
                 manager=None,
                 id=None,
                 command=None,
                 autoclose=False,
                 autoclose_retain=5,
                 redirect=None):
        """
        Setup the environment for a new terminal.

        :param manager: TerminalManager object
        :type manager: TerminalManager
        :param id: Id fo the terminal
        :type id: hex
        :param command: Command to run
        :type command: string
        :param autoclose: Parameter to close the terminal after the command
        :type autoclose: bool
        :param autoclose_retain: Time to wait before closing in seconds
        :type autoclose_retain: integer
        :param redirect: Default redirect URL after closing
        :type redirect: string
        """

        self.width = 80
        self.height = 25
        self.id = id
        self.manager = manager
        self.autoclose = autoclose
        self.autoclose_retain = autoclose_retain
        if redirect:
            self.redirect = redirect
        else:
            self.redirect = '/view/terminal'
        self.output = BroadcastQueue()

        env = {}
        env.update(os.environ)
        env['TERM'] = 'linux'
        env['COLUMNS'] = str(self.width)
        env['LINES'] = str(self.height)
        env['LC_ALL'] = 'en_US.UTF8'

        self.command = command
        if not self.command:
            shell = os.environ.get('SHELL', None)
            if not shell:
                for sh in ['zsh', 'bash', 'sh']:
                    try:
                        shell = subprocess.check_output(['which', sh])
                        break
                    except Exception as e:
                        pass
            self.command = shell

        args = ['sh', '-c', self.command]
        exe = 'sh'

        logging.info(f'Activating new terminal: {self.command}')

        self.pid, self.fd = pty.fork()
        if self.pid == 0:
            setproctitle.setproctitle(
                f'{sys.argv[0]} terminal session #{os.getpid():d}')
            os.execvpe(exe, args, env)

        logging.info(f'Subprocess PID {self.pid}')

        self.dead = False

        fl = fcntl.fcntl(self.fd, fcntl.F_GETFL)
        fcntl.fcntl(self.fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)

        self.stream_in = os.fdopen(self.fd, 'rb', 0)
        self.stream_out = os.fdopen(self.fd, 'wb', 0)
        self.pyte_stream = pyte.Stream()
        self.screen = pyte.DiffScreen(self.width, self.height)
        self.pyte_stream.attach(self.screen)

        self.last_cursor_position = None

        self.reader = gevent.spawn(self.reader_fn)
コード例 #10
0
ファイル: terminal.py プロジェクト: Mu-L/ajenti
class Terminal():
    """
    Terminal emulator based on pyte.
    """
    def __init__(self,
                 manager=None,
                 id=None,
                 command=None,
                 autoclose=False,
                 autoclose_retain=5,
                 redirect=None):
        """
        Setup the environment for a new terminal.

        :param manager: TerminalManager object
        :type manager: TerminalManager
        :param id: Id fo the terminal
        :type id: hex
        :param command: Command to run
        :type command: string
        :param autoclose: Parameter to close the terminal after the command
        :type autoclose: bool
        :param autoclose_retain: Time to wait before closing in seconds
        :type autoclose_retain: integer
        :param redirect: Default redirect URL after closing
        :type redirect: string
        """

        self.width = 80
        self.height = 25
        self.id = id
        self.manager = manager
        self.autoclose = autoclose
        self.autoclose_retain = autoclose_retain
        if redirect:
            self.redirect = redirect
        else:
            self.redirect = '/view/terminal'
        self.output = BroadcastQueue()

        env = {}
        env.update(os.environ)
        env['TERM'] = 'linux'
        env['COLUMNS'] = str(self.width)
        env['LINES'] = str(self.height)
        env['LC_ALL'] = 'en_US.UTF8'

        self.command = command
        if not self.command:
            shell = os.environ.get('SHELL', None)
            if not shell:
                for sh in ['zsh', 'bash', 'sh']:
                    try:
                        shell = subprocess.check_output(['which', sh])
                        break
                    except Exception as e:
                        pass
            self.command = shell

        args = ['sh', '-c', self.command]
        exe = 'sh'

        logging.info(f'Activating new terminal: {self.command}')

        self.pid, self.fd = pty.fork()
        if self.pid == 0:
            setproctitle.setproctitle(
                f'{sys.argv[0]} terminal session #{os.getpid():d}')
            os.execvpe(exe, args, env)

        logging.info(f'Subprocess PID {self.pid}')

        self.dead = False

        fl = fcntl.fcntl(self.fd, fcntl.F_GETFL)
        fcntl.fcntl(self.fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)

        self.stream_in = os.fdopen(self.fd, 'rb', 0)
        self.stream_out = os.fdopen(self.fd, 'wb', 0)
        self.pyte_stream = pyte.Stream()
        self.screen = pyte.DiffScreen(self.width, self.height)
        self.pyte_stream.attach(self.screen)

        self.last_cursor_position = None

        self.reader = gevent.spawn(self.reader_fn)

    def reader_fn(self):
        """
        Infinite reader of the file descriptor associated with the shell.
        """

        while True:
            wait_read(self.fd)
            self.run_single_read()
            self._check()
            if self.dead:
                return

    def run_single_read(self):
        """
        Propagate the new data read in the file descriptor.
        """

        try:
            data = self.stream_in.read()
        except IOError:
            data = ''

        if data:
            try:
                self.pyte_stream.feed(data.decode('utf-8'))
                self.broadcast_update()
            except UnicodeDecodeError:
                pass

    def broadcast_update(self):
        """
        Send the new formated data to the broadcast queue.
        """

        self.output.broadcast(self.format())

    def _check(self):
        """
        Check if a child process of the shell pid is available.
        """

        try:
            pid, status = os.waitpid(self.pid, os.WNOHANG)
        except OSError:
            self.on_died(code=0)
            return
        if pid:
            self.on_died(code=status)

    def on_died(self, code=0):
        """
        Change terminal status to dead, broadcast new status and autoclose it.

        :param code: Status code of the pid
        :type code: integer
        """

        if self.dead:
            return

        logging.info(f'Terminal {self.id} has died')
        self.dead = True

        if code:
            self.pyte_stream.feed('\n\n * ' +
                                  f'Process has exited with status {code}')
        else:
            self.pyte_stream.feed('\n\n * ' +
                                  'Process has exited successfully')

        self.broadcast_update()

        if self.autoclose:
            gevent.spawn_later(self.autoclose_retain, self.manager.remove,
                               self.id)
        """
        TODO
        if self.callback:
            try:
                self.callback(exitcode=code)
            except TypeError:
                self.callback()
        """

    def has_updates(self):
        """
        Test if the user made some changes.

        :return: Bool
        :rtype: bool
        """

        if self.last_cursor_position != (self.screen.cursor.x,
                                         self.screen.cursor.y):
            return True
        return len(self.screen.dirty) > 0

    def format(self, full=False):
        """
        Prepare the terminal content and attributes to send to the frontend.

        :param full: To send the full buffer
        :type full: bool
        :return: Attributes and content of the terminal
        :rtype: dict
        """
        def compress(line):
            return [[tok or 0 for tok in ch] for _, ch in line.items()]

        l = {}
        self.screen.dirty.add(self.screen.cursor.y)
        if self.last_cursor_position:
            self.screen.dirty.add(self.last_cursor_position[1])
        for k in self.screen.dirty:
            if k < len(self.screen.buffer):
                l[k] = compress(self.screen.buffer[k])
        self.screen.dirty.clear()

        if full:
            l = [compress(x) for _, x in self.screen.buffer.items()]

        r = {
            'lines': l,
            'cx': self.screen.cursor.x,
            'cy': self.screen.cursor.y,
            'cursor': not self.screen.cursor.hidden,
            'h': self.screen.lines,
            'w': self.screen.columns,
        }

        self.last_cursor_position = (self.screen.cursor.x,
                                     self.screen.cursor.y)
        return r

    def feed(self, data):
        """
        Write input data into the file descriptor.

        :param data: New data from the user
        :type data: string
        """

        wait_write(self.fd)
        self.stream_out.write(data.encode('utf-8'))
        self.stream_out.flush()

    def resize(self, w, h):
        """
        Resize terminal to new width and height.

        :param w: Width
        :type w: integer
        :param h: Height
        :type h: integer
        """

        if self.dead:
            return
        if (h, w) == (self.screen.lines, self.screen.columns):
            return
        if w <= 0 or h <= 0:
            return
        self.width = w
        self.height = h
        logging.debug(f'Resizing terminal to {w}x{h}')
        self.screen.resize(h, w)
        winsize = struct.pack("HHHH", h, w, 0, 0)
        fcntl.ioctl(self.fd, termios.TIOCSWINSZ, winsize)

    def kill(self):
        """
        Kill this terminal through killing the process associated with the pid.
        """

        self.reader.kill(block=False)

        logging.info(f'Killing subprocess PID {self.pid}')
        try:
            os.killpg(self.pid, signal.SIGTERM)
        except OSError:
            pass
        try:
            os.kill(self.pid, signal.SIGKILL)
        except OSError:
            pass
コード例 #11
0
class Terminal():
    def __init__(self,
                 manager=None,
                 id=None,
                 command=None,
                 autoclose=False,
                 autoclose_retain=5,
                 redirect=None):
        self.width = 80
        self.height = 25
        self.id = id
        self.manager = manager
        self.autoclose = autoclose
        self.autoclose_retain = autoclose_retain
        if redirect:
            self.redirect = redirect
        else:
            self.redirect = '/view/terminal'
        self.output = BroadcastQueue()

        env = {}
        env.update(os.environ)
        env['TERM'] = 'linux'
        env['COLUMNS'] = str(self.width)
        env['LINES'] = str(self.height)
        env['LC_ALL'] = 'en_US.UTF8'

        self.command = command
        if not self.command:
            shell = os.environ.get('SHELL', None)
            if not shell:
                for sh in ['zsh', 'bash', 'sh']:
                    try:
                        shell = subprocess.check_output(['which', sh])
                        break
                    except Exception as e:
                        pass
            self.command = shell

        args = ['sh', '-c', self.command]
        exe = 'sh'

        logging.info('Activating new terminal: %s', self.command)

        self.pid, self.fd = pty.fork()
        if self.pid == 0:
            setproctitle.setproctitle('%s terminal session #%i' %
                                      (sys.argv[0], os.getpid()))
            os.execvpe(exe, args, env)

        logging.info('Subprocess PID %s', self.pid)

        self.dead = False

        fl = fcntl.fcntl(self.fd, fcntl.F_GETFL)
        fcntl.fcntl(self.fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)

        self.stream_in = os.fdopen(self.fd, 'rb', 0)
        self.stream_out = os.fdopen(self.fd, 'wb', 0)
        self.pyte_stream = pyte.Stream()
        self.screen = pyte.DiffScreen(self.width, self.height)
        self.pyte_stream.attach(self.screen)

        self.last_cursor_position = None

        self.reader = gevent.spawn(self.reader_fn)

    def reader_fn(self):
        while True:
            wait_read(self.fd)
            self.run_single_read()
            self._check()
            if self.dead:
                return

    def run_single_read(self):
        try:
            data = self.stream_in.read()
        except IOError:
            data = ''

        if data:
            try:
                self.pyte_stream.feed(data.decode('utf-8'))
                self.broadcast_update()
            except UnicodeDecodeError:
                pass

    def broadcast_update(self):
        self.output.broadcast(self.format())

    def _check(self):
        try:
            pid, status = os.waitpid(self.pid, os.WNOHANG)
        except OSError:
            self.on_died(code=0)
            return
        if pid:
            self.on_died(code=status)

    def on_died(self, code=0):
        if self.dead:
            return

        logging.info('Terminal %s has died', self.id)
        self.dead = True

        if code:
            self.pyte_stream.feed(u'\n\n * ' +
                                  u'Process has exited with status %i' % code)
        else:
            self.pyte_stream.feed(u'\n\n * ' +
                                  u'Process has exited successfully')

        self.broadcast_update()

        if self.autoclose:
            gevent.spawn_later(self.autoclose_retain, self.manager.remove,
                               self.id)
        """
        TODO
        if self.callback:
            try:
                self.callback(exitcode=code)
            except TypeError:
                self.callback()
        """

    def has_updates(self):
        if self.last_cursor_position != (self.screen.cursor.x,
                                         self.screen.cursor.y):
            return True
        return len(self.screen.dirty) > 0

    def format(self, full=False):
        def compress(line):
            return [[tok or 0 for tok in ch] for _, ch in line.items()]

        l = {}
        self.screen.dirty.add(self.screen.cursor.y)
        if self.last_cursor_position:
            self.screen.dirty.add(self.last_cursor_position[1])
        for k in self.screen.dirty:
            if k < len(self.screen.buffer):
                l[k] = compress(self.screen.buffer[k])
        self.screen.dirty.clear()

        if full:
            l = [compress(x) for _, x in self.screen.buffer.items()]

        r = {
            'lines': l,
            'cx': self.screen.cursor.x,
            'cy': self.screen.cursor.y,
            'cursor': not self.screen.cursor.hidden,
            'h': self.screen.lines,
            'w': self.screen.columns,
        }

        self.last_cursor_position = (self.screen.cursor.x,
                                     self.screen.cursor.y)
        return r

    def feed(self, data):
        wait_write(self.fd)
        self.stream_out.write(data.encode('utf-8'))
        self.stream_out.flush()

    def resize(self, w, h):
        if self.dead:
            return
        if (h, w) == (self.screen.lines, self.screen.columns):
            return
        if w <= 0 or h <= 0:
            return
        self.width = w
        self.height = h
        logging.debug('Resizing terminal to %sx%s', w, h)
        self.screen.resize(h, w)
        winsize = struct.pack("HHHH", h, w, 0, 0)
        fcntl.ioctl(self.fd, termios.TIOCSWINSZ, winsize)

    def kill(self):
        self.reader.kill(block=False)

        logging.info('Killing subprocess PID %s', self.pid)
        try:
            os.killpg(self.pid, signal.SIGTERM)
        except OSError:
            pass
        try:
            os.kill(self.pid, signal.SIGKILL)
        except OSError:
            pass
コード例 #12
0
class WorkerGate(object):
    def __init__(self,
                 session,
                 name=None,
                 log_tag=None,
                 restricted=False,
                 initial_identity=None):
        self.session = session
        self.process = None
        self.stream = None
        self.stream_reader = None
        self.worker = None
        self.name = name
        self.log_tag = log_tag
        self.restricted = restricted
        self.initial_identity = initial_identity
        self.q_http_replies = BroadcastQueue()
        self.q_socket_messages = BroadcastQueue()

    def start(self):
        pipe_parent, pipe_child = gipc.pipe(duplex=True)
        self.stream = GateStreamServerEndpoint(pipe_parent)
        stream_child = GateStreamWorkerEndpoint(pipe_child)

        self.process = gipc.start_process(target=self._target,
                                          kwargs={
                                              'stream': stream_child,
                                              '_pipe': pipe_child,
                                          })

        logging.debug('Started child process %s', self.process.pid)

        self.stream_reader = gevent.spawn(self._stream_reader)

    def stop(self):
        logging.debug('Stopping child process %s', self.process.pid)

        try:
            os.killpg(self.process.pid, signal.SIGTERM)
        except OSError as e:
            logging.debug('Child process %s is already dead: %s',
                          self.process.pid, e)
            return

        self.process.terminate()
        self.process.join(0.125)
        try:
            os.kill(self.process.pid, 0)
            logging.debug('Child process %s did not stop, killing',
                          self.process.pid)
            os.kill(self.process.pid, signal.SIGKILL)
            os.killpg(self.process.pid, signal.SIGKILL)

            os.kill(self.process.pid, 0)
            logging.error('Child process %s did not stop after SIGKILL!',
                          self.process.pid)
        except OSError:
            pass

        self.stream_reader.kill(block=False)

    def _stream_reader(self):
        try:
            while True:
                resp = self.stream.buffer_single_response(None)
                if not resp:
                    return
                self.stream.ack_response(resp.id)
                if resp.object['type'] == 'socket':
                    self.q_socket_messages.broadcast(resp)
                if resp.object['type'] == 'http':
                    self.q_http_replies.broadcast(resp)
                if resp.object['type'] == 'terminate':
                    self.session.deactivate()
                if resp.object['type'] == 'restart-master':
                    aj.restart()
        except greenlet.GreenletExit:
            pass

    def _target(self, stream=None, _pipe=None):
        self.worker = Worker(stream, self)
        self.worker.run()
コード例 #13
0
ファイル: push.py プロジェクト: Dolphi2/ajenti
 def __init__(self, context):
     self.q = BroadcastQueue()
コード例 #14
0
 def __init__(self, context):
     self.q = BroadcastQueue()
コード例 #15
0
ファイル: terminal.py プロジェクト: Dolphi2/ajenti
class Terminal(object):
    def __init__(self, manager=None, id=None, command=None, autoclose=False, autoclose_retain=5):
        self.width = 80
        self.height = 25
        self.id = id
        self.manager = manager
        self.autoclose = autoclose
        self.autoclose_retain = autoclose_retain
        self.output = BroadcastQueue()

        env = {}
        env.update(os.environ)
        env['TERM'] = 'linux'
        env['COLUMNS'] = str(self.width)
        env['LINES'] = str(self.height)
        env['LC_ALL'] = 'en_US.UTF8'

        self.command = command
        if not self.command:
            shell = os.environ.get('SHELL', None)
            if not shell:
                for sh in ['zsh', 'bash', 'sh']:
                    try:
                        shell = subprocess.check_output(['which', sh])
                        break
                    except:
                        pass
            self.command = shell

        args = ['sh', '-c', self.command]
        exe = 'sh'

        logging.info('Activating new terminal: %s', self.command)

        self.pid, self.fd = pty.fork()
        if self.pid == 0:
            setproctitle.setproctitle('%s terminal session #%i' % (sys.argv[0], os.getpid()))
            os.execvpe(exe, args, env)

        logging.info('Subprocess PID %s', self.pid)

        self.dead = False

        fl = fcntl.fcntl(self.fd, fcntl.F_GETFL)
        fcntl.fcntl(self.fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)

        self.stream_in = os.fdopen(self.fd, 'rb', 0)
        self.stream_out = os.fdopen(self.fd, 'wb', 0)
        self.pyte_stream = pyte.Stream()
        self.screen = pyte.DiffScreen(self.width, self.height)
        self.pyte_stream.attach(self.screen)

        self.last_cursor_position = None

        self.reader = gevent.spawn(self.reader_fn)

    def reader_fn(self):
        while True:
            wait_read(self.fd)
            self.run_single_read()
            self._check()
            if self.dead:
                return

    def run_single_read(self):
        try:
            data = self.stream_in.read()
        except IOError:
            data = ''

        if data:
            try:
                self.pyte_stream.feed(data.decode('utf-8'))
                self.broadcast_update()
            except UnicodeDecodeError:
                pass

    def broadcast_update(self):
        self.output.broadcast(self.format())

    def _check(self):
        try:
            pid, status = os.waitpid(self.pid, os.WNOHANG)
        except OSError:
            self.on_died(code=0)
            return
        if pid:
            self.on_died(code=status)

    def on_died(self, code=0):
        if self.dead:
            return

        logging.info('Terminal %s has died', self.id)
        self.dead = True

        if code:
            self.pyte_stream.feed(u'\n\n * ' + u'Process has exited with status %i' % code)
        else:
            self.pyte_stream.feed(u'\n\n * ' + u'Process has exited successfully')

        self.broadcast_update()

        if self.autoclose:
            gevent.spawn_later(self.autoclose_retain, self.manager.remove, self.id)

        """
        TODO
        if self.callback:
            try:
                self.callback(exitcode=code)
            except TypeError:
                self.callback()
        """

    def has_updates(self):
        if self.last_cursor_position != (self.screen.cursor.x, self.screen.cursor.y):
            return True
        return len(self.screen.dirty) > 0

    def format(self, full=False):
        def compress(line):
            return [[tok or 0 for tok in ch] for ch in line]

        l = {}
        self.screen.dirty.add(self.screen.cursor.y)
        if self.last_cursor_position:
            self.screen.dirty.add(self.last_cursor_position[1])
        for k in self.screen.dirty:
            if k < len(self.screen.buffer):
                l[k] = compress(self.screen.buffer[k])
        self.screen.dirty.clear()

        if full:
            l = [compress(x) for x in self.screen.buffer]

        r = {
            'lines': l,
            'cx': self.screen.cursor.x,
            'cy': self.screen.cursor.y,
            'cursor': not self.screen.cursor.hidden,
            'h': self.screen.lines,
            'w': self.screen.columns,
        }

        self.last_cursor_position = (self.screen.cursor.x, self.screen.cursor.y)
        return r

    def feed(self, data):
        wait_write(self.fd)
        self.stream_out.write(data.encode('utf-8'))
        self.stream_out.flush()

    def resize(self, w, h):
        if self.dead:
            return
        if (h, w) == (self.screen.lines, self.screen.columns):
            return
        if w <= 0 or h <= 0:
            return
        self.width = w
        self.height = h
        logging.debug('Resizing terminal to %sx%s', w, h)
        self.screen.resize(h, w)
        winsize = struct.pack("HHHH", h, w, 0, 0)
        fcntl.ioctl(self.fd, termios.TIOCSWINSZ, winsize)

    def kill(self):
        self.reader.kill(block=False)

        logging.info('Killing subprocess PID %s', self.pid)
        try:
            os.killpg(self.pid, signal.SIGTERM)
        except OSError:
            pass
        try:
            os.kill(self.pid, signal.SIGKILL)
        except OSError:
            pass
コード例 #16
0
ファイル: gate.py プロジェクト: tawanda/ajenti
class WorkerGate(object):
    def __init__(self, session, name=None, log_tag=None, restricted=False,
                 initial_identity=None):
        self.session = session
        self.process = None
        self.stream = None
        self.stream_reader = None
        self.worker = None
        self.name = name
        self.log_tag = log_tag
        self.restricted = restricted
        self.initial_identity = initial_identity
        self.q_http_replies = BroadcastQueue()
        self.q_socket_messages = BroadcastQueue()

    def start(self):
        pipe_parent, pipe_child = gipc.pipe(duplex=True)
        self.stream = GateStreamServerEndpoint(pipe_parent)
        stream_child = GateStreamWorkerEndpoint(pipe_child)

        self.process = gipc.start_process(
            target=self._target,
            kwargs={
                'stream': stream_child,
                '_pipe': pipe_child,
            }
        )

        logging.debug('Started child process %s', self.process.pid)

        self.stream_reader = gevent.spawn(self._stream_reader)

    def stop(self):
        logging.debug('Stopping child process %s', self.process.pid)

        try:
            os.killpg(self.process.pid, signal.SIGTERM)
        except OSError as e:
            logging.debug('Child process %s is already dead: %s', self.process.pid, e)
            return

        self.process.terminate()
        self.process.join(0.125)
        try:
            os.kill(self.process.pid, 0)
            logging.debug('Child process %s did not stop, killing', self.process.pid)
            os.kill(self.process.pid, signal.SIGKILL)
            os.killpg(self.process.pid, signal.SIGKILL)

            os.kill(self.process.pid, 0)
            logging.error('Child process %s did not stop after SIGKILL!', self.process.pid)
        except OSError:
            pass

        self.stream_reader.kill(block=False)

    def _stream_reader(self):
        try:
            while True:
                resp = self.stream.buffer_single_response(None)
                if not resp:
                    return
                self.stream.ack_response(resp.id)
                if resp.object['type'] == 'socket':
                    self.q_socket_messages.broadcast(resp)
                if resp.object['type'] == 'http':
                    self.q_http_replies.broadcast(resp)
                if resp.object['type'] == 'terminate':
                    self.session.deactivate()
                if resp.object['type'] == 'restart-master':
                    aj.restart()
                if resp.object['type'] == 'reload-config':
                    aj.config.load()
                    aj.config.ensure_structure()
                    self.stream.send({
                        'type': 'config-data',
                        'data': aj.config.data,
                    })
        except greenlet.GreenletExit:
            pass

    def _target(self, stream=None, _pipe=None):
        self.worker = Worker(stream, self)
        self.worker.run()
コード例 #17
0
class WorkerGate():
    def __init__(self,
                 session,
                 gateway_middleware,
                 name=None,
                 log_tag=None,
                 restricted=False,
                 initial_identity=None):
        self.session = session
        self.process = None
        self.stream = None
        self.stream_reader = None
        self.worker = None
        self.name = name
        self.log_tag = log_tag
        self.restricted = restricted
        self.gateway_middleware = gateway_middleware
        self.initial_identity = initial_identity
        self.q_http_replies = BroadcastQueue()
        self.q_socket_messages = BroadcastQueue()

    def start(self):
        pipe_parent, pipe_child = gipc.pipe(
            duplex=True,
            encoder=lambda x: pickle.dumps(x, 2),
        )
        self.stream = GateStreamServerEndpoint(pipe_parent)
        stream_child = GateStreamWorkerEndpoint(pipe_child)

        self.process = gipc.start_process(target=self._target,
                                          kwargs={
                                              'stream': stream_child,
                                              '_pipe': pipe_child,
                                          })

        logging.debug(f'Started child process {self.process.pid}')

        self.stream_reader = gevent.spawn(self._stream_reader)

    def stop(self):
        logging.debug(f'Stopping child process {self.process.pid}')

        try:
            os.killpg(self.process.pid, signal.SIGTERM)
        except OSError as e:
            logging.debug(
                f'Child process {self.process.pid} is already dead: {e}')
            return

        self.process.terminate()
        self.process.join(0.125)
        try:
            os.kill(self.process.pid, 0)
            logging.debug(
                f'Child process {self.process.pid} did not stop, killing')
            os.kill(self.process.pid, signal.SIGKILL)
            os.killpg(self.process.pid, signal.SIGKILL)

            os.kill(self.process.pid, 0)
            logging.error(
                f'Child process {self.process.pid} did not stop after SIGKILL!'
            )
        except OSError:
            pass

        self.stream.destroy()
        self.stream_reader.kill(block=False)

    def send_config_data(self):
        logging.debug(f'Sending a config update to {self.name}')
        self.stream.send({
            'type': 'config-data',
            'data': {
                'config': aj.config.data,
                'users': aj.users.data
            }
        })

    def send_sessionlist(self):
        logging.debug(f'Sending a session list update to {self.name}')
        self.stream.send({
            'type': 'session-list',
            'data': {
                key: session.serialize()
                for key, session in self.gateway_middleware.sessions.items()
            },
        })

    def _stream_reader(self):
        try:
            while True:
                resp = self.stream.buffer_single_response(None)
                if not resp:
                    return
                self.stream.ack_response(resp.id)
                if resp.object['type'] == 'socket':
                    self.q_socket_messages.broadcast(resp)
                if resp.object['type'] == 'http':
                    self.q_http_replies.broadcast(resp)
                if resp.object['type'] == 'terminate':
                    if self.session != self.gateway_middleware:
                        # Not the restricted session, we can disable it
                        self.session.deactivate()
                if resp.object['type'] == 'restart-master':
                    aj.restart()
                if resp.object['type'] == 'update-sessionlist':
                    self.gateway_middleware.broadcast_sessionlist()
                if resp.object['type'] == 'reload-config':
                    aj.config.load()
                    aj.users.load()
                    aj.config.ensure_structure()
                    self.gateway_middleware.broadcast_config_data()
                if resp.object['type'] == 'log':
                    method = {
                        'info': logging.info,
                        'warn': logging.warning,
                        'warning': logging.warning,
                        'debug': logging.debug,
                        'error': logging.error,
                        'critical': logging.critical,
                    }.get(resp.object['method'], None)
                    if method:
                        method(f"{resp.object['message']}",
                               extra=resp.object['kwargs'])
        except greenlet.GreenletExit:
            pass

    def _target(self, stream=None, _pipe=None):
        self.worker = Worker(stream, self)
        self.worker.run()