2018-04-03 12:49:14 +00:00
|
|
|
# Copyright (C) 2018 Avatao.com Innovative Learning Kft.
|
|
|
|
# All Rights Reserved. See LICENSE file for details.
|
|
|
|
|
2019-06-27 12:12:01 +00:00
|
|
|
from pwd import getpwnam
|
|
|
|
from grp import getgrnam
|
|
|
|
from pathlib import Path
|
|
|
|
from os import chown
|
2018-03-25 13:43:59 +00:00
|
|
|
from os.path import dirname
|
2018-03-30 15:50:20 +00:00
|
|
|
from re import findall
|
|
|
|
from re import compile as compileregex
|
2018-03-29 09:23:38 +00:00
|
|
|
from abc import ABC, abstractmethod
|
2018-03-25 13:43:59 +00:00
|
|
|
|
2019-06-11 15:25:35 +00:00
|
|
|
from tfw.components.inotify import InotifyObserver
|
|
|
|
from tfw.event_handlers import TFWServerUplinkConnector
|
2018-03-03 16:15:21 +00:00
|
|
|
|
2018-03-07 09:12:58 +00:00
|
|
|
|
2019-06-11 15:25:35 +00:00
|
|
|
class HistoryMonitor(ABC, InotifyObserver):
|
2018-04-18 17:44:26 +00:00
|
|
|
"""
|
|
|
|
Abstract class capable of monitoring and parsing a history file such as
|
|
|
|
bash HISTFILEs. Monitoring means detecting when the file was changed and
|
|
|
|
notifying subscribers about new content in the file.
|
|
|
|
|
|
|
|
This is useful for monitoring CLI sessions.
|
|
|
|
|
|
|
|
To specify a custom HistoryMonitor inherit from this class and override the
|
|
|
|
command pattern property and optionally the sanitize_command method.
|
|
|
|
See examples below.
|
|
|
|
"""
|
2019-06-27 12:12:01 +00:00
|
|
|
def __init__(self, uplink, histfile):
|
|
|
|
self._domain = ''
|
2018-03-03 16:15:21 +00:00
|
|
|
self.histfile = histfile
|
2019-06-27 12:12:01 +00:00
|
|
|
self.history = []
|
|
|
|
self._last_length = len(self.history)
|
|
|
|
self.uplink = uplink
|
|
|
|
uid = getpwnam('user').pw_uid
|
|
|
|
gid = getgrnam('users').gr_gid
|
|
|
|
path = Path(self.histfile)
|
|
|
|
path.touch()
|
|
|
|
chown(self.histfile, uid, gid)
|
|
|
|
super().__init__(self.histfile)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def domain(self):
|
|
|
|
return self._domain
|
|
|
|
|
|
|
|
@domain.setter
|
|
|
|
def domain(self, domain):
|
|
|
|
self._domain = domain
|
2019-06-11 15:25:35 +00:00
|
|
|
|
|
|
|
def on_modified(self, event):
|
|
|
|
self._fetch_history()
|
2019-06-27 12:12:01 +00:00
|
|
|
if self._last_length < len(self.history):
|
|
|
|
for command in self.history[self._last_length:]:
|
2019-06-11 15:25:35 +00:00
|
|
|
self.send_message(command)
|
2018-03-03 16:15:21 +00:00
|
|
|
|
|
|
|
def _fetch_history(self):
|
2019-06-27 12:12:01 +00:00
|
|
|
self._last_length = len(self.history)
|
2018-03-03 16:15:21 +00:00
|
|
|
with open(self.histfile, 'r') as ifile:
|
2018-03-30 15:50:20 +00:00
|
|
|
pattern = compileregex(self.command_pattern)
|
2018-03-29 09:23:38 +00:00
|
|
|
data = ifile.read()
|
2019-06-27 12:12:01 +00:00
|
|
|
self.history = [
|
2018-06-04 20:16:44 +00:00
|
|
|
self.sanitize_command(command)
|
|
|
|
for command in findall(pattern, data)
|
|
|
|
]
|
2018-03-29 09:23:38 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
@abstractmethod
|
|
|
|
def command_pattern(self):
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
def sanitize_command(self, command):
|
2018-03-30 16:11:38 +00:00
|
|
|
# pylint: disable=no-self-use
|
2018-03-29 09:23:38 +00:00
|
|
|
return command
|
2018-03-03 16:15:21 +00:00
|
|
|
|
2019-06-11 15:25:35 +00:00
|
|
|
def send_message(self, command):
|
|
|
|
self.uplink.send_message({
|
|
|
|
'key': f'history.{self.domain}',
|
|
|
|
'value': command
|
|
|
|
})
|
2018-03-05 15:23:01 +00:00
|
|
|
|
2018-03-29 09:23:38 +00:00
|
|
|
|
|
|
|
class BashMonitor(HistoryMonitor):
|
2018-04-18 17:44:26 +00:00
|
|
|
"""
|
|
|
|
HistoryMonitor for monitoring bash CLI sessions.
|
|
|
|
This requires the following to be set in bash
|
|
|
|
(note that this is done automatically by TFW):
|
2018-06-01 14:20:20 +00:00
|
|
|
PROMPT_COMMAND="history -a"
|
|
|
|
shopt -s cmdhist
|
|
|
|
shopt -s histappend
|
|
|
|
unset HISTCONTROL
|
2018-04-18 17:44:26 +00:00
|
|
|
"""
|
2019-06-27 12:12:01 +00:00
|
|
|
def __init__(self, uplink, histfile):
|
|
|
|
super().__init__(uplink, histfile)
|
|
|
|
self.domain = 'bash'
|
2019-06-11 15:25:35 +00:00
|
|
|
|
2018-03-29 09:23:38 +00:00
|
|
|
@property
|
|
|
|
def command_pattern(self):
|
|
|
|
return r'.+'
|
|
|
|
|
|
|
|
def sanitize_command(self, command):
|
|
|
|
return command.strip()
|
2018-03-29 09:37:25 +00:00
|
|
|
|
|
|
|
|
|
|
|
class GDBMonitor(HistoryMonitor):
|
2018-04-18 17:44:26 +00:00
|
|
|
"""
|
|
|
|
HistoryMonitor to monitor GDB sessions.
|
|
|
|
For this to work "set trace-commands on" must be set in GDB.
|
|
|
|
"""
|
2019-06-11 15:25:35 +00:00
|
|
|
def __init__(self, histfile):
|
2019-06-27 12:12:01 +00:00
|
|
|
super().__init__(histfile)
|
|
|
|
self.domain = 'gdb'
|
2019-06-11 15:25:35 +00:00
|
|
|
|
2018-03-29 09:37:25 +00:00
|
|
|
@property
|
|
|
|
def command_pattern(self):
|
|
|
|
return r'(?<=\n)\+(.+)\n'
|