diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..cd4ce96 --- /dev/null +++ b/Makefile @@ -0,0 +1,62 @@ +NAME=sysinfo +IMAGE_NAME=sysinfo + +ifndef VERSION +VERSION := $(shell python3 -c "from setup import find_version;find_version("src/sysinfo/__init__.py")" || echo 0.0.0) +endif + +ifndef BRANCH +BRANCH := $(shell git branch --show-current) +endif + +ifndef COMMIT +COMMIT := $(shell git log -n1 --format="%h") +endif + +ifndef SRC +SRC := src/sysinfo/*.py +endif + +ifndef TESTS +TESTS := src/tests +endif + +BUILD_RUN=docker run --rm "$(IMAGE_NAME):$(COMMIT)" + +.PHONY: git black lint build test coverage security + +git: + @echo $(branch: [$(BRANCH)] commit: [$(COMMIT)]) + +black: + isort $(SRC) + black $(SRC) + autoflake --remove-all-unused-imports --remove-duplicate-keys --expand-star-imports --recursive --in-place $(SRC) + +lint: + flake8 --max-line-length=120 --max-complexity 8 $(SRC) + interrogate $(SRC) + mypy $(SRC) + pylint -d C0301 -d R0902 $(SRC) + +build: + python setup.py build + +install: + python setup.py install + +build_docker: + docker build -t $(IMAGE_NAME):$(COMMIT) . -f docker/build.Dockerfile + +test_docker: + docker build -t $(IMAGE_NAME):$(COMMIT) . -f docker/test.Dockerfile + +test: + pytest $(TESTS) + +coverage: + pytest --cov-report term-missing --cov=sysinfo $(TESTS) + +security: + safety check + bandit -r $(SRC) diff --git a/README.md b/README.md index 9dea176..072fe82 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ positional arguments: optional arguments: -h, --help Show this help message and exit --all, -a Execute all commands. + --camel-case, -c Convert keys to CamelCase. --error, -e Show only error outputs from commands. --export-only Export output from commands without processing. --export-dir PATH Path to the directory for saving output from commands. @@ -32,13 +33,14 @@ optional arguments: --list, -l List all commands. --output OUTPUT, -o OUTPUT Path to the output file. --pool POOL, -p POOL Pool size for parallel execution of commands. (default value is 5) + --system SYSTEM, -s SYSTEM Execute or parse commands for selected system [linux, darwin, java, windows]. --verbose, -v Add more info to output - options, commands, raw command result. ``` ## Examples ### Standart JSON output ``` -python2 sysinfo.py lscpu +python sysinfo.py lscpu ``` ```json { @@ -66,7 +68,7 @@ python2 sysinfo.py lscpu ### Get single value ``` -python2 sysinfo.py lscpu | jq -r ".lscpu.output.modelName" +python sysinfo.py lscpu | jq -r ".lscpu.output.modelName" ``` ``` ARM1176 @@ -74,7 +76,7 @@ ARM1176 ### Output in CSV format ``` -python2 sysinfo.py lsblk | jq -r ".lsblk.output[] | [.name, .label, .size, +python sysinfo.py lsblk | jq -r ".lsblk.output[] | [.name, .label, .size, .mountpoint] | @csv" ``` @@ -101,12 +103,21 @@ sudo python sysinfo.py --import-dir ./out blkid * [jq](https://stedolan.github.io/jq/) ## Available commands + +### Linux + ``` +arp - System ARP cache blkid - Block device attributes blockdev - Block device ioctls +blockdev_detail - Block device ioctls details busctl - Introspect the bus +busctl_status - Process information and credentials of a bus service +busctl_tree - Object tree for services +chage - Users password expiration information chrt - Scheduling attributes of all the tasks (threads) dev_disk - Disk devices mapping +dev_input - Input devices mapping df - Report file system disk space usage dmidecode - Dumping all information from DMI (SMBIOS) dmidecode_baseboard - Dumping BASEBOARD information from DMI (SMBIOS) @@ -136,14 +147,14 @@ fbset_info - Show frame buffer device information findmnt - List all mounted filesytems free - Amount of free and used memory in the system getconf - Configuration variables for the current system and their values -groups - Group names -hardware_platform - Hardware platform +groups - Group names (compgen) +hardware_platform - Hardware platform (uname) hostnamectl - Current system hostname and related information ifconfig - List all interfaces which are currently available, even if down -jobs - Job names, if job control is active -kernel_name - Kernel name -kernel_release - Kernel release -kernel_version - Kernel version +jobs - Job names, if job control is active (compgen) +kernel_name - Kernel name (uname) +kernel_release - Kernel release (uname) +kernel_version - Kernel version (uname) lsblk - Lists information about all block devices lscpu - Information about the CPU architecture lsmod - Show the status of modules in the Linux Kernel @@ -151,11 +162,14 @@ lsns - Block device ioctls lsof - Information about files opened by processes lspci - List all PCI devices lsusb - List USB devices -machine - Machine hardware name +machine - Machine hardware name (uname) modinfo - Information about a Linux Kernel modules -nodename - Network node hostname -operating_system - Operating system +nodename - Network node hostname (uname) +operating_system - Operating system (uname) parted - Lists partition layout on all block devices +proc_buddyinfo - Memory fragmentation +proc_bus_input - Input devices +proc_cgroups - Control groups proc_cmdline - Parameters passed to the kernel at the time it is started proc_consoles - Information about current consoles including tty proc_cpuinfo - Type of processor used by your system @@ -172,33 +186,47 @@ proc_locks - Files currently locked by the kernel proc_meminfo - Reports a large amount of valuable information about the systems RAM usage proc_modules - List of all modules loaded into the kernel proc_mounts - List mounted filesystems (info provides from kernel) +proc_net_arp - ARP +proc_net_ax25_route - AX25 routing information +proc_net_ipx_route - IPX routing information +proc_net_route - IP routing information +proc_net_tcp - TCP socket table +proc_net_tcp6 - TCP6 socket table +proc_net_udp - UDP socket table +proc_net_udp6 - UDP6 socket table proc_partitions - Partition block allocation information proc_scsi - List of every recognized SCSI device +proc_slabinfo - Kernel caches informations +proc_stat - Kernel/system statistics proc_swaps - Measures swap space and its utilization proc_sys - Information about the system and kernel features proc_uptime - Information detailing how long the system has been on since its last restart proc_version - Version of the Linux kernel, the version of gcc used to compile the kernel, and the time of kernel compilation +proc_version_signature - OS version signature proc_vmstat - Detailed virtual memory statistics from the kernel -processor - Processor type +processor - Processor type (uname) prtstat - Print statistics of a processes ps - Report a snapshot of the current processes python_pip_packages - List available python modules python_platform - Probe the underlying platform's hardware, operating system, and Python interpreter version information +route - IP routing table rpm - Querying all RPM packages -services - Service names +services - Service names (compgen) services_list - Displays services with status -services_params - Displays services with status -shell_alias - Shell alias names -shell_all_commands - Shell command names -shell_builtins - Names of shell builtin commands -shell_exported_variables - Names of exported shell variables -shell_variables - Names of all shell variables +services_params - Displays services with params +shell_alias - Shell alias names (compgen) +shell_all_commands - Shell command names (compgen) +shell_builtins - Names of shell builtin commands (compgen) +shell_exported_variables - Names of exported shell variables (compgen) +shell_variables - Names of all shell variables (compgen) sysctl - Runtime kernel parameters sysctl_system - Runtime kernel parameters from all system configuration files timedatectl - System time and date +timedatectl_timesync - Status of systemd-timesyncd.service udevadm - Queries the udev database for device information stored in the udev database udevadm_block_devices - Queries the udev database for block device information stored in the udev database -users - User names +update_alternatives - Symbolic links determining default commands +users - User names (compgen) vmstat_disk - Report disk statistics vmstat_disk_sum - Report some summary statistics about disk activity vmstat_forks - Displays the number of forks since boot @@ -206,3 +234,16 @@ vmstat_stats - Displays a table of various event counters and memor yum_installed - YUM - list installed packages yum_repolist - YUM - defined repositories ``` + +### Windows + +``` +arp - System ARP cache +assoc - File associations +driverquery - List of installed device drivers. +driverquery_signed - List of installed device signed drivers. +tasklist - Get currently running processes +tasklist_apps - Get services hosted in each process +tasklist_modules - Get modules loaded in each process +tasklist_services - Get services hosted in each process +``` diff --git a/docker/build.Dockerfile b/docker/build.Dockerfile new file mode 100644 index 0000000..8f468d1 --- /dev/null +++ b/docker/build.Dockerfile @@ -0,0 +1,14 @@ +FROM python:3.10-alpine +# LABEL instruction creates labels. +LABEL "maintainer"="Petr Vavrin" "appname"="sysinfo" + +ENV applocation /usr/src +COPY src/sysinfo $applocation/sysinfo +ENV app $applocation/sysinfo +WORKDIR $app/ + +RUN apk add --update python py-pip +RUN pip install --upgrade pip +RUN pip install -r requirements.txt +#EXPOSE 5000 +CMD ["python", "sysinfo.py"] diff --git a/docker/test.Dockerfile b/docker/test.Dockerfile new file mode 100644 index 0000000..40155e4 --- /dev/null +++ b/docker/test.Dockerfile @@ -0,0 +1,28 @@ +FROM python:3.10-alpine +# LABEL instruction creates labels. +LABEL "maintainer"="Petr Vavrin" "appname"="sysinfo" + +ENV applocation /usr/src +COPY src/sysinfo $applocation/sysinfo +COPY src/tests $applocation/tests +ENV app $applocation/sysinfo +ENV tests $applocation/tests +WORKDIR $app/ + +RUN apk add --update python py-pip && \ + pip install --upgrade pip && \ + pip install -r requirements-test.txt && \ + isort $app && \ + black $app && \ + autoflake --remove-all-unused-imports --remove-duplicate-keys --expand-star-imports --recursive --in-place $app && \ + flake8 --max-line-length=120 --max-complexity 8 $app && \ + interrogate $app && \ + mypy $app && \ + pylint -d C0301 -d R0902 $app && \ + pytest $tests && \ + pytest --cov-report term-missing --cov=sysinfo $tests && \ + safety check && \ + bandit -r $app + +#EXPOSE 5000 +CMD ["python", "sysinfo.py"] diff --git a/modules/blkid.py b/modules/blkid.py deleted file mode 100644 index 349a59e..0000000 --- a/modules/blkid.py +++ /dev/null @@ -1,33 +0,0 @@ - -import re -from sysinfo_lib import camelCase - - -def parser(stdout, stderr): - output = {} - ignoredLines = [] - if stdout: - device = '' - for line in stdout.splitlines(): - dev = re.search(r'^>>> Device: (\S+)', line) - kv = re.search(r'^(\w[^=]+)=(.*)$', line) - if dev: - device = dev.group(1) - output[device] = {} - elif kv: - output[device][camelCase(kv.group(1))] = kv.group(2) - else: - ignoredLines.append(line) - pass - - return { - 'output': output, - 'ignored': ignoredLines - } - -def register(main): - main['blkid'] = { - 'cmd': """blkid -o device | xargs -n 1 -I {} sh -c "echo '>>> Device: {}'; blkid -o export -p {}; blkid -o export -i {}" """, - 'description': 'Block device attributes', - 'parser': parser - } \ No newline at end of file diff --git a/modules/blockdev.py b/modules/blockdev.py deleted file mode 100644 index 8768ee3..0000000 --- a/modules/blockdev.py +++ /dev/null @@ -1,16 +0,0 @@ - -from sysinfo_lib import parseTable - -def parser(stdout, stderr): - output = {} - if stdout: - output = parseTable(stdout) - - return {'output': output} - -def register(main): - main['blockdev'] = { - 'cmd': 'blockdev --report | column -t', - 'description': 'Block device ioctls', - 'parser': parser - } \ No newline at end of file diff --git a/modules/busctl.py b/modules/busctl.py deleted file mode 100644 index 4258610..0000000 --- a/modules/busctl.py +++ /dev/null @@ -1,16 +0,0 @@ - -from sysinfo_lib import parseTable - -def parser(stdout, stderr): - output = {} - if stdout: - output = parseTable(stdout) - - return {'output': output} - -def register(main): - main['busctl'] = { - 'cmd': 'busctl --no-pager | column -t', - 'description': 'Introspect the bus', - 'parser': parser - } diff --git a/modules/chrt.py b/modules/chrt.py deleted file mode 100644 index 1d2e306..0000000 --- a/modules/chrt.py +++ /dev/null @@ -1,29 +0,0 @@ - -import re - -def parser(stdout, stderr): - output = {} - pid = None - if stdout: - for line in stdout.splitlines(): - pidSearch = re.search(r'^>>>\s+PID:\s+(\S+)', line) - if pidSearch: - pid = pidSearch.group(1) - output[pid] = {'pid': pid, 'scheduling': {}, 'current': {}} - - if pid: - sched = re.search(r'^SCHED_(\S+)[^:]+:\s*(\S+)', line) - if sched: - output[pid]['scheduling'][sched.group(1).strip()] = sched.group(2).strip() - - current = re.search(r'current scheduling (\S+).*:\s*(\S+)', line) - if current: - output[pid]['current'][current.group(1).strip()] = current.group(2).strip() - return {'output': output} - -def register(main): - main['chrt'] = { - 'cmd': """ps -eo pid | grep -vi pid | xargs -I {} sh -c "echo '>>> PID: {}'; chrt -a --pid {}; echo '----'; chrt -m --pid {};" """, - 'description': 'Scheduling attributes of all the tasks (threads)', - 'parser': parser - } diff --git a/modules/compgen.py b/modules/compgen.py deleted file mode 100644 index f3f92fa..0000000 --- a/modules/compgen.py +++ /dev/null @@ -1,63 +0,0 @@ - -from sysinfo_lib import sortedList - -def parser(stdout, stderr): - if stdout: - return {'output': sortedList(stdout)} - else: - return {} - -def register(main): - main['shell_alias'] = { - 'cmd': '$(which bash) -c "compgen -a"', - 'description': 'Shell alias names', - 'parser': parser - } - - main['shell_builtins'] = { - 'cmd': '$(which bash) -c "compgen -b"', - 'description': 'Names of shell builtin commands', - 'parser': parser - } - - main['shell_all_commands'] = { - 'cmd': '$(which bash) -c "compgen -c"', - 'description': 'Shell command names', - 'parser': parser - } - - main['shell_exported_variables'] = { - 'cmd': '$(which bash) -c "compgen -e"', - 'description': 'Names of exported shell variables', - 'parser': parser - } - - main['groups'] = { - 'cmd': '$(which bash) -c "compgen -g"', - 'description': 'Group names', - 'parser': parser - } - - main['jobs'] = { - 'cmd': '$(which bash) -c "compgen -j"', - 'description': 'Job names, if job control is active', - 'parser': parser - } - - main['services'] = { - 'cmd': '$(which bash) -c "compgen -s"', - 'description': 'Service names', - 'parser': parser - } - - main['users'] = { - 'cmd': '$(which bash) -c "compgen -u"', - 'description': 'User names', - 'parser': parser - } - - main['shell_variables'] = { - 'cmd': '$(which bash) -c "compgen -v"', - 'description': 'Names of all shell variables', - 'parser': parser - } diff --git a/modules/dev_disk.py b/modules/dev_disk.py deleted file mode 100644 index be13a0f..0000000 --- a/modules/dev_disk.py +++ /dev/null @@ -1,32 +0,0 @@ - -import re - -def parser(stdout, stderr): - output = {'all': {}} - if stdout: - typeName = '' - for line in stdout.splitlines(): - matchType = re.search(r'^\/dev\/disk\/by-(.*):\s*$', line) - if matchType: - typeName = matchType.group(1) - output[typeName] = {} - - matchEntry = re.search(r'\s(\S+)\s+->\s+[\.\/]+(.*)$', line) - if matchEntry and typeName: - key = matchEntry.group(1).strip() - value = matchEntry.group(2).strip() - output[typeName][key] = value - - if not value in output['all']: - output['all'][value] = {} - - output['all'][value][typeName] = key - - return {'output': output} - -def register(main): - main['dev_disk'] = { - 'cmd': 'ls -l /dev/disk/by-*', - 'description': 'Disk devices mapping', - 'parser': parser - } diff --git a/modules/df.py b/modules/df.py deleted file mode 100644 index 7ad9fa4..0000000 --- a/modules/df.py +++ /dev/null @@ -1,38 +0,0 @@ -import re - -def parser(stdout, stderr): - output = {} - ignoredLines = [] - if stdout: - reSplit = re.compile(r'\s+') - for line in stdout.splitlines(): - cols = reSplit.split(line) - if len(cols) > 11 and cols[11].lower() != 'mounted': - output[cols[11]] = { - 'source': cols[0], - 'fstype': cols[1], - 'itotal': cols[2], - 'iused': cols[3], - 'iavail': cols[4], - 'ipcent': cols[5], - 'size': cols[6], - 'used': cols[7], - 'avail': cols[8], - 'pcent': cols[9], - 'file': cols[10], - 'target': cols[11] - } - else: - ignoredLines.append(line) - - return { - 'output': output, - 'ignored': ignoredLines - } - -def register(main): - main['df'] = { - 'cmd': 'df -a --output=source,fstype,itotal,iused,iavail,ipcent,size,used,avail,pcent,file,target', - 'description': 'Report file system disk space usage', - 'parser': parser - } \ No newline at end of file diff --git a/modules/dmidecode.py b/modules/dmidecode.py deleted file mode 100644 index 8ebff83..0000000 --- a/modules/dmidecode.py +++ /dev/null @@ -1,132 +0,0 @@ - -import re -from sysinfo_lib import camelCase - -def parser(stdout, stderr): - dmiSections = { - '0': 'BIOS', - '1': 'System', - '2': 'Base Board', - '3': 'Chassis', - '4': 'Processor', - '5': 'Memory Controller', - '6': 'Memory Module', - '7': 'Cache', - '8': 'Port Connector', - '9': 'System Slots', - '10': 'On Board Devices', - '11': 'OEM Strings', - '12': 'System Configuration Options', - '13': 'BIOS Language', - '14': 'Group Associations', - '15': 'System Event Log', - '16': 'Physical Memory Array', - '17': 'Memory Device', - '18': '32-bit Memory Error', - '19': 'Memory Array Mapped Address', - '20': 'Memory Device Mapped Address', - '21': 'Built-in Pointing Device', - '22': 'Portable Battery', - '23': 'System Reset', - '24': 'Hardware Security', - '25': 'System Power Controls', - '26': 'Voltage Probe', - '27': 'Cooling Device', - '28': 'Temperature Probe', - '29': 'Electrical Current Probe', - '30': 'Out-of-band Remote Access', - '31': 'Boot Integrity Services', - '32': 'System Boot', - '33': '64-bit Memory Error', - '34': 'Management Device', - '35': 'Management Device Component', - '36': 'Management Device Threshold Data', - '37': 'Memory Channel', - '38': 'IPMI Device', - '39': 'Power Supply' - } - section = None - output = {} - - if stdout: - for line in stdout.splitlines(): - if line.strip() == '': - section = None - - handleSearch = re.search(r'^Handle\s+([^,]+),\s+DMI type\s+([^,]+),', line, re.IGNORECASE) - if handleSearch: - handle = handleSearch.group(1) - dmiType = handleSearch.group(2) - if dmiType in dmiSections: - section = camelCase(dmiSections[dmiType]) - output[section] = { - '__handle': handle, - '__dmiType': dmiType - } - - entry = re.search(r'^\s+([^:]+):\s+(.*)$', line) - if section and entry: - output[section][camelCase(entry.group(1))] = entry.group(2).strip() - - return {'output': output} - -def register(main): - main['dmidecode'] = { - 'cmd': 'dmidecode', - 'description': 'Dumping all information from DMI (SMBIOS)', - 'parser': parser - } - - main['dmidecode_bios'] = { - 'cmd': 'dmidecode -t bios', - 'description': 'Dumping BIOS information from DMI (SMBIOS)', - 'parser': parser - } - - main['dmidecode_system'] = { - 'cmd': 'dmidecode -t system', - 'description': 'Dumping SYSTEM information from DMI (SMBIOS)', - 'parser': parser - } - - main['dmidecode_baseboard'] = { - 'cmd': 'dmidecode -t baseboard', - 'description': 'Dumping BASEBOARD information from DMI (SMBIOS)', - 'parser': parser - } - - main['dmidecode_chassis'] = { - 'cmd': 'dmidecode -t chassis', - 'description': 'Dumping CHASSIS information from DMI (SMBIOS)', - 'parser': parser - } - - main['dmidecode_processor'] = { - 'cmd': 'dmidecode -t processor', - 'description': 'Dumping CHASSIS information from DMI (SMBIOS)', - 'parser': parser - } - - main['dmidecode_memory'] = { - 'cmd': 'dmidecode -t memory', - 'description': 'Dumping MEMORY information from DMI (SMBIOS)', - 'parser': parser - } - - main['dmidecode_cache'] = { - 'cmd': 'dmidecode -t cache', - 'description': 'Dumping CACHE information from DMI (SMBIOS)', - 'parser': parser - } - - main['dmidecode_connector'] = { - 'cmd': 'dmidecode -t connector', - 'description': 'Dumping CONNECTOR information from DMI (SMBIOS)', - 'parser': parser - } - - main['dmidecode_slot'] = { - 'cmd': 'dmidecode -t slot', - 'description': 'Dumping SLOT information from DMI (SMBIOS)', - 'parser': parser - } diff --git a/modules/dnf.py b/modules/dnf.py deleted file mode 100644 index 7b72a0e..0000000 --- a/modules/dnf.py +++ /dev/null @@ -1,72 +0,0 @@ - -import re - -def parser_repolist(stdout, stderr): - output = {'repos': [], 'errors': []} - insideTable = False - col1 = None - col2 = None - if stdout: - for line in stdout.splitlines(): - tableHeader = re.search(r'^(repo id\s+)(repo name\s+)status', line, re.IGNORECASE) - - if col1 and col2: - tableRow = re.search(r'^(.{%s})(.{%s})(.*)$' % (col1, col2), line) - - if insideTable and tableRow: - output['repos'].append({ - 'repo': tableRow.group(1).strip(), - 'repo_name': tableRow.group(2).strip(), - 'status': tableRow.group(3).strip() - }) - continue - - if tableHeader: - insideTable = True - col1 = len(tableHeader.group(1)) - col2 = len(tableHeader.group(2)) - - else: - insideTable = False - - if stderr: - for line in stderr.splitlines(): - if re.search(r'http.*error .*', line, re.IGNORECASE): - if not line in output['errors']: - output['errors'].append(line) - - return {'output': output} - -def parser_installed(stdout, stderr): - output = {'packages': [], 'errors': []} - if stdout: - for line in stdout.splitlines(): - package = re.search(r'^([\S\.]+)\.(\S+)\s+(\S+\.\S+)\s+(\S+.*)$', line) - if package: - output['packages'].append({ - 'name': package.group(1).strip(), - 'arch': package.group(2).strip(), - 'version': package.group(3).strip(), - 'status': package.group(4).strip() - }) - - if stderr: - for line in stderr.splitlines(): - if re.search(r'http.*error .*', line, re.IGNORECASE): - if not line in output['errors']: - output['errors'].append(line) - - return {'output': output} - -def register(main): - main['dnf_repolist'] = { - 'cmd': 'dnf repolist --all', - 'description': 'DNF - defined repositories', - 'parser': parser_repolist - } - - main['dnf_installed'] = { - 'cmd': 'dnf list installed', - 'description': 'DNF - list installed packages', - 'parser': parser_installed - } diff --git a/modules/env.py b/modules/env.py deleted file mode 100644 index d6fc230..0000000 --- a/modules/env.py +++ /dev/null @@ -1,19 +0,0 @@ - -import re - -def parser(stdout, stderr): - output = {} - if stdout: - for line in stdout.splitlines(): - lineMatch = re.search(r'^([^=]+)=(.*)$', line) - if lineMatch: - output[lineMatch.group(1)] = lineMatch.group(2) - - return {'output': output} - -def register(main): - main['env'] = { - 'cmd': 'env', - 'description': 'Environment variables', - 'parser': parser - } \ No newline at end of file diff --git a/modules/etc_default.py b/modules/etc_default.py deleted file mode 100644 index b0912f0..0000000 --- a/modules/etc_default.py +++ /dev/null @@ -1,41 +0,0 @@ - -import re - -def setPathValue(data, path, value): - pathRest = None - pathParts = re.search(r'^([^\/]+)\/?(.*)$', path) - if pathParts: - path = pathParts.group(1) - pathRest = pathParts.group(2) - - if not path in data: - data[path] = {} - - if pathRest: - setPathValue(data[path], pathRest, value) - else: - if isinstance(data[path], dict): - data[path] = [] - data[path].append(value) - -def parser(stdout, stderr): - output = {} - if stdout: - for line in stdout.splitlines(): - pathValue = re.search(r'^\/etc\/default\/([^:]+):(.*)$', line) - if pathValue: - path = pathValue.group(1) - value = pathValue.group(2) - if value.strip() == '': - continue - if not re.search(r'^\s*#', value): - setPathValue(output, path, value) - - return {'output': output} - -def register(main): - main['etc_default'] = { - 'cmd': 'find /etc/default -type f -follow -print | xargs grep ""', - 'description': 'Default configuration for programs', - 'parser': parser - } \ No newline at end of file diff --git a/modules/etc_fstab.py b/modules/etc_fstab.py deleted file mode 100644 index a43c3c4..0000000 --- a/modules/etc_fstab.py +++ /dev/null @@ -1,28 +0,0 @@ - -import re - -def parser(stdout, stderr): - output = {} - if stdout: - for line in stdout.splitlines(): - if re.match(r'^\s*#', line): - continue - lineMatch = re.split(r'\s+', line) - if lineMatch and len(lineMatch) > 5: - output[lineMatch[0]] = { - 'location': lineMatch[0], - 'mountPoint': lineMatch[1], - 'type': lineMatch[2], - 'security': lineMatch[3], - 'dump': lineMatch[4], - 'fsckOrder': lineMatch[5] - } - - return {'output': output} - -def register(main): - main['etc_fstab'] = { - 'cmd': 'cat /etc/fstab', - 'description': 'Filesystems mounted on boot', - 'parser': parser - } \ No newline at end of file diff --git a/modules/etc_group.py b/modules/etc_group.py deleted file mode 100644 index 099cc8b..0000000 --- a/modules/etc_group.py +++ /dev/null @@ -1,16 +0,0 @@ - -from sysinfo_lib import parseCharDelimitedTable, tableToDict - -def parser(stdout, stderr): - columnsNames = ['groupName', 'password', 'gid', 'groupList'] - output = parseCharDelimitedTable(stdout, ':', columnsNames) - output = tableToDict(output, 'groupName') - return {'output': output} - -def register(main): - main['etc_group'] = { - 'cmd': 'cat /etc/group', - 'description': 'Groups essential information', - 'parser': parser - } - diff --git a/modules/etc_hosts.py b/modules/etc_hosts.py deleted file mode 100644 index c2c86e9..0000000 --- a/modules/etc_hosts.py +++ /dev/null @@ -1,25 +0,0 @@ - -import re - -def parser(stdout, stderr): - output = {} - if stdout: - for line in stdout.splitlines(): - values = re.search(r'^\/etc\/([^\:]+):\s*(.*)$', line) - if not values: - continue - group = values.group(1) - value = re.sub(r'#.*$', '', values.group(2)).strip() - if group not in output: - output[group] = [] - if value != '': - output[group].append(value.split('\t')) - - return {'output': output} - -def register(main): - main['etc_hosts'] = { - 'cmd': """grep "" /etc/hosts*""", - 'description': 'Maps hostnames to IP addresses', - 'parser': parser - } diff --git a/modules/etc_locale_gen.py b/modules/etc_locale_gen.py deleted file mode 100644 index 1a7c09b..0000000 --- a/modules/etc_locale_gen.py +++ /dev/null @@ -1,22 +0,0 @@ - -import re - -def parser(stdout, stderr): - output = [] - if stdout: - for line in stdout.splitlines(): - values = re.search(r'^\s*([^#]+)', line) - if not values: - continue - value = values.group(1).strip() - if value != '': - output.append(value) - - return {'output': output} - -def register(main): - main['etc_locale_gen'] = { - 'cmd': 'cat /etc/locale.gen', - 'description': 'Configuration file for locale-gen', - 'parser': parser - } diff --git a/modules/etc_mtab.py b/modules/etc_mtab.py deleted file mode 100644 index 0b8ae00..0000000 --- a/modules/etc_mtab.py +++ /dev/null @@ -1,26 +0,0 @@ - -import re - -def parser(stdout, stderr): - output = {} - if stdout: - for line in stdout.splitlines(): - values = re.split(r'\s+', line) - if len(values) > 5: - output[values[1]] = { - 'partition': values[0], - 'mountPoint': values[1], - 'fileSystem': values[2], - 'mountOptions': re.split(r',', values[3]), - 'dump': values[4], - 'fsckOrder': values[5] - } - - return {'output': output} - -def register(main): - main['etc_mtab'] = { - 'cmd': 'cat /etc/mtab', - 'description': 'Currently mounted filesystems', - 'parser': parser - } diff --git a/modules/etc_passwd.py b/modules/etc_passwd.py deleted file mode 100644 index 6ca092c..0000000 --- a/modules/etc_passwd.py +++ /dev/null @@ -1,15 +0,0 @@ - -from sysinfo_lib import parseCharDelimitedTable, tableToDict - -def parser(stdout, stderr): - columnsNames = ['username', 'password', 'uid', 'gid', 'idInfo', 'homeDir', 'shell'] - output = parseCharDelimitedTable(stdout, ':', columnsNames) - output = tableToDict(output, 'username') - return {'output': output} - -def register(main): - main['etc_passwd'] = { - 'cmd': 'cat /etc/passwd', - 'description': 'Attributes of each user or account on a computer', - 'parser': parser - } diff --git a/modules/etc_release.py b/modules/etc_release.py deleted file mode 100644 index 70f1b90..0000000 --- a/modules/etc_release.py +++ /dev/null @@ -1,19 +0,0 @@ - -import re - -def parser(stdout, stderr): - output = {} - if stdout: - for line in stdout.splitlines(): - values = re.search(r'^([^=]+)=(.*)$', line) - if values: - output[values.group(1)] = values.group(2).strip().strip('"') - - return {'output': output} - -def register(main): - main['etc_release'] = { - 'cmd': 'cat /etc/*release', - 'description': 'OS release info', - 'parser': parser - } diff --git a/modules/etc_shadow.py b/modules/etc_shadow.py deleted file mode 100644 index 767b9f3..0000000 --- a/modules/etc_shadow.py +++ /dev/null @@ -1,15 +0,0 @@ - -from sysinfo_lib import parseCharDelimitedTable, tableToDict - -def parser(stdout, stderr): - columnsNames = ['username', 'password', 'lastPasswordChange', 'minimum', 'maximum', 'warn', 'inactive', 'expire'] - output = parseCharDelimitedTable(stdout, ':', columnsNames) - output = tableToDict(output, 'username') - return {'output': output} - -def register(main): - main['etc_shadow'] = { - 'cmd': 'cat /etc/shadow', - 'description': 'Shadow database of the passwd file', - 'parser': parser - } diff --git a/modules/etc_timezone.py b/modules/etc_timezone.py deleted file mode 100644 index 10cf6ca..0000000 --- a/modules/etc_timezone.py +++ /dev/null @@ -1,13 +0,0 @@ - -def parser(stdout, stderr): - output = '' - if stdout: - output = stdout.strip() - return {'output': output} - -def register(main): - main['etc_timezone'] = { - 'cmd': 'cat /etc/timezone', - 'description': 'Timezone settings', - 'parser': parser - } diff --git a/modules/fbset.py b/modules/fbset.py deleted file mode 100644 index 5d28910..0000000 --- a/modules/fbset.py +++ /dev/null @@ -1,93 +0,0 @@ - -import re - -def parser(stdout, stderr): - output = {} - modeName = '' - if stdout: - for line in stdout.splitlines(): - mode = re.search(r'^\s*mode "([^"]+)\s*"$', line) - if mode: - modeName = mode.group(1) - output[modeName] = {} - - endmode = re.search(r'^\s*endmode', line) - if endmode: - modeName = '' - - if modeName: - geometry = re.search(r'^\s*geometry\s*(\d+)\s*(\d+)\s*(\d+)\s*(\d+)\s*(\d+)\s*$', line) - if geometry: - output[modeName]['geometry'] = { - 'xres': geometry.group(1), - 'yres': geometry.group(2), - 'xresVirtual': geometry.group(3), - 'yresVirtual': geometry.group(4), - 'bitsPerPixel': geometry.group(5) - } - - timings = re.search(r'^\s*timings\s*(\d+)\s*(\d+)\s*(\d+)\s*(\d+)\s*(\d+)\s*(\d+)\s*(\d+)\s*$', line) - if timings: - output[modeName]['timings'] = { - 'pixclock': timings.group(1), - 'leftMargin': timings.group(2), - 'rightMargin': timings.group(3), - 'upperMargin': timings.group(4), - 'lowerMargin': timings.group(5), - 'hsyncLen': timings.group(6), - 'vsyncLen': timings.group(7) - } - - rgba = re.search(r'^\s*rgba\s*(\d+)\/(\d+),(\d+)\/(\d+),(\d+)\/(\d+),(\d+)\/(\d+)\s*$', line) - if rgba: - output[modeName]['rgba'] = { - 'redLength': rgba.group(1), - 'redOffset': rgba.group(2), - 'greenLength': rgba.group(3), - 'greenOffset': rgba.group(4), - 'blueLength': rgba.group(5), - 'blueOffset': rgba.group(6), - 'transpLength': rgba.group(7), - 'transpOffset': rgba.group(8) - } - - state = re.search(r'^\s*(interlaced|double|vsync|hsync|csync|extsync)\s+(\S+)\s*$', line) - if state: - output[modeName][state.group(1)] = state.group(2) - - return { - 'output': { - 'mode': output - } - } - -def parserInfo(stdout, stderr): - output = parser(stdout, stderr) - output['output']['info'] = {} - - informationBlock = False - if stdout: - for line in stdout.splitlines(): - info = re.search(r'^\s*Frame buffer device information:\s*$', line) - if info: - informationBlock = True - - if informationBlock: - keyValue = re.search(r'^\s+(\S+)\s*:\s+(.*)\s*$', line) - if keyValue: - output['output']['info'][keyValue.group(1)] = keyValue.group(2) - - return output - -def register(main): - main['fbset'] = { - 'cmd': 'fbset -a', - 'description': 'Show frame buffer device settings', - 'parser': parser - } - - main['fbset_info'] = { - 'cmd': 'fbset -i', - 'description': 'Show frame buffer device information', - 'parser': parserInfo - } \ No newline at end of file diff --git a/modules/findmnt.py b/modules/findmnt.py deleted file mode 100644 index ce9f9af..0000000 --- a/modules/findmnt.py +++ /dev/null @@ -1,16 +0,0 @@ - -from sysinfo_lib import parseTable - -def parser(stdout, stderr): - output = {} - if stdout: - output = parseTable(stdout) - - return {'output': output} - -def register(main): - main['findmnt'] = { - 'cmd': 'findmnt -Al | column -t', - 'description': 'List all mounted filesytems', - 'parser': parser - } \ No newline at end of file diff --git a/modules/free.py b/modules/free.py deleted file mode 100644 index 5edf159..0000000 --- a/modules/free.py +++ /dev/null @@ -1,29 +0,0 @@ - -import re -from sysinfo_lib import camelCase - -def parser(stdout, stderr): - output = {} - columns = None - if stdout: - typeName = '' - for line in stdout.splitlines(): - if re.search(r'total\s+used', line, re.IGNORECASE): - columns = re.split(r'\s+', line.strip()) - - entrySearch = re.search(r'^([^:]+):\s+(.*)$', line) - if columns and entrySearch: - type = camelCase(entrySearch.group(1)) - output[type] = {} - for idx, value in enumerate(re.split(r'\s+', entrySearch.group(2).strip())): - if idx < len(columns): - output[type][columns[idx]] = value - - return {'output': output} - -def register(main): - main['free'] = { - 'cmd': 'free -b -l -w', - 'description': 'Amount of free and used memory in the system', - 'parser': parser - } diff --git a/modules/getconf.py b/modules/getconf.py deleted file mode 100644 index 03a97e9..0000000 --- a/modules/getconf.py +++ /dev/null @@ -1,21 +0,0 @@ - -import re - -def parser(stdout, stderr): - output = {} - if stdout: - for line in stdout.splitlines(): - kv = re.search(r'^(\S+)\s*(.*)$', line) - if kv: - output[kv.group(1)] = kv.group(2).strip() - - return { - 'output': output, - } - -def register(main): - main['getconf'] = { - 'cmd': 'getconf -a', - 'description': 'Configuration variables for the current system and their values', - 'parser': parser - } \ No newline at end of file diff --git a/modules/hostnamectl.py b/modules/hostnamectl.py deleted file mode 100644 index 3403da7..0000000 --- a/modules/hostnamectl.py +++ /dev/null @@ -1,20 +0,0 @@ - -import re -from sysinfo_lib import camelCase - -def parser(stdout, stderr): - output = {} - if stdout: - for line in stdout.splitlines(): - lineMatch = re.search(r'^([^:]+):\s*(.*)', line) - if lineMatch: - output[camelCase(lineMatch.group(1))] = lineMatch.group(2) - - return {'output': output} - -def register(main): - main['hostnamectl'] = { - 'cmd': 'hostnamectl status', - 'description': 'Current system hostname and related information', - 'parser': parser - } \ No newline at end of file diff --git a/modules/ifconfig.py b/modules/ifconfig.py deleted file mode 100644 index ba26809..0000000 --- a/modules/ifconfig.py +++ /dev/null @@ -1,74 +0,0 @@ - -import re -from sysinfo_lib import camelCase - -def extractValue(data, line, key, regExp): - search = re.search(regExp, line, re.IGNORECASE) - if search: - data[key] = search.group(1) - return data - -def extractValues(data, line): - for pair in re.split(r'\s\s+', line): - desc = re.search(r'^(.*)\((.*)\)$', pair.strip()) - if desc: - data['description'] = desc.group(2) - if desc.group(1): - pair = desc.group(1).strip() - else: - continue - - kv = re.search(r'^([^\s]+)\s(\S.*)$', pair) - if kv: - value = kv.group(2) - valueFix = re.search(r'^(\S+)\s+(\S+)\s(\S+)$', value) - if valueFix: - data[kv.group(1)] = valueFix.group(1) - data[valueFix.group(2)] = valueFix.group(3) - else: - data[kv.group(1)] = value - - return extractValues - -def parser(stdout, stderr): - output = {} - blockData = {} - if stdout: - for block in re.split(r'\r\r|\n\n|\r\n\r\n', stdout): - blockData = {'entries': [], 'rx': {}, 'tx': {}} - for line in block.splitlines(): - header = re.search(r'^(\S[^:]+):\s*(.*)$', line) - if header: - name = header.group(1) - blockData['name'] = name - extractValue(blockData, line, 'flags', r'flags=(\S+)') - extractValues(blockData, header.group(2)) - output[name] = blockData - - rxTx = re.search(r'^\s+([rt]x)\s+(.*)$', line, re.IGNORECASE) - if rxTx: - type = rxTx.group(1).lower() - extractValues(blockData[type], rxTx.group(2)) - continue - - sub = re.search(r'^\s+(\S+)\s\s(.*)$', line, re.IGNORECASE) - if sub: - subData = {'type': sub.group(1)} - extractValues(subData, sub.group(2)) - blockData['entries'].append(subData) - continue - - sub = re.search(r'^\s+(\S+)\s(\S+)\s\s(.*)$', line, re.IGNORECASE) - if sub: - subData = {'type': sub.group(1), 'value': sub.group(2)} - extractValues(subData, sub.group(3)) - blockData['entries'].append(subData) - - return {'output': output} - -def register(main): - main['ifconfig'] = { - 'cmd': 'ifconfig -a -v', - 'description': 'List all interfaces which are currently available, even if down', - 'parser': parser - } diff --git a/modules/lsblk.py b/modules/lsblk.py deleted file mode 100644 index ab06997..0000000 --- a/modules/lsblk.py +++ /dev/null @@ -1,25 +0,0 @@ - -import re -from sysinfo_lib import camelCase - -def parser(stdout, stderr): - output = {} - if stdout: - for line in stdout.splitlines(): - entry = {} - kv = re.findall(r'(\S[^=]+)=\"([^"]*)\"', line) - if kv: - for pair in kv: - entry[camelCase(pair[0])] = pair[1].strip() - - if 'name' in entry: - output[entry['name']] = entry - - return {'output': output} - -def register(main): - main['lsblk'] = { - 'cmd': 'lsblk -P -o NAME,KNAME,MAJ:MIN,FSTYPE,MOUNTPOINT,LABEL,UUID,PARTLABEL,PARTUUID,RA,RO,RM,MODEL,SERIAL,SIZE,STATE,OWNER,GROUP,MODE,ALIGNMENT,MIN-IO,OPT-IO,PHY-SEC,LOG-SEC,ROTA,SCHED,RQ-SIZE,TYPE,DISC-ALN,DISC-GRAN,DISC-MAX,DISC-ZERO,WSAME,WWN,RAND,PKNAME,HCTL,TRAN,REV,VENDOR', - 'description': 'Lists information about all block devices', - 'parser': parser - } \ No newline at end of file diff --git a/modules/lscpu.py b/modules/lscpu.py deleted file mode 100644 index 4c40546..0000000 --- a/modules/lscpu.py +++ /dev/null @@ -1,21 +0,0 @@ - -import re -from sysinfo_lib import camelCase - -def parser(stdout, stderr): - output = {} - if stdout: - for line in stdout.splitlines(): - line = re.sub(r'\(s\)', 's', line) - lineMatch = re.search(r'^([^:]+):\s*(.*)', line) - if lineMatch: - output[camelCase(lineMatch.group(1))] = lineMatch.group(2) - - return {'output': output} - -def register(main): - main['lscpu'] = { - 'cmd': 'lscpu', - 'description': 'Information about the CPU architecture', - 'parser': parser - } \ No newline at end of file diff --git a/modules/lsmod.py b/modules/lsmod.py deleted file mode 100644 index 9e1e131..0000000 --- a/modules/lsmod.py +++ /dev/null @@ -1,24 +0,0 @@ - -import re - -def parser(stdout, stderr): - output = {} - if stdout: - for line in stdout.splitlines(): - lineMatch = re.search(r'^(^\S+)\s*(\d+)\s*(\d+)\s*(.*)$', line) - if lineMatch: - output[lineMatch.group(1)] = { - 'module': lineMatch.group(1), - 'size': lineMatch.group(2), - 'usedNumber': lineMatch.group(3), - 'usedBy': lineMatch.group(4) - } - - return {'output': output} - -def register(main): - main['lsmod'] = { - 'cmd': 'lsmod', - 'description': 'Show the status of modules in the Linux Kernel', - 'parser': parser - } \ No newline at end of file diff --git a/modules/lsns.py b/modules/lsns.py deleted file mode 100644 index cc4339e..0000000 --- a/modules/lsns.py +++ /dev/null @@ -1,16 +0,0 @@ - -from sysinfo_lib import parseTable - -def parser(stdout, stderr): - output = {} - if stdout: - output = parseTable(stdout, r'^(\s*NS)(\sTYPE\s+)(\sPATH\s*)(\s\s*NPROCS)(\s*\sPID)(\s*\sPPID)(\s*\sUID)(\sUSER\s*)(\sCOMMAND\s*)') - - return {'output': output} - -def register(main): - main['lsns'] = { - 'cmd': 'lsns -o NS,TYPE,PATH,NPROCS,PID,PPID,UID,USER,COMMAND', - 'description': 'Block device ioctls', - 'parser': parser - } \ No newline at end of file diff --git a/modules/lsof.py b/modules/lsof.py deleted file mode 100644 index 6118248..0000000 --- a/modules/lsof.py +++ /dev/null @@ -1,98 +0,0 @@ - -import re - -opField = { - 'a': 'accessMode', - 'c': 'commandName', - 'C': 'structureShareCount', - 'd': 'deviceCharacterCode', - 'D': 'majorMinorDeviceNumber', - 'f': 'fileDescriptor', - 'F': 'structureAddress', - 'g': 'processGroupId', - 'G': 'flags', - 'i': 'inodeNumber', - 'k': 'linkCount', - 'K': 'taskId', - 'l': 'lockStatus', - 'L': 'loginName', - 'm': 'markerBetweenRepeatedOutput', - 'n': 'name', - 'N': 'nodeIdentifier', - 'o': 'fileOffset', - 'p': 'processId', - 'P': 'protocolName', - 'r': 'rawDeviceNumber', - 'R': 'parentPid', - 's': 'fileSize', - 'S': 'streamModuleAndDeviceNames', - 't': 'fileType', - 'T': 'tcpTpiInfo', - 'u': 'userId', - 'z': 'zoneName', - 'Z': 'selinuxSecurityContext' -} - -tcptpiField = { - 'QR': 'readQueueSize', - 'QS': 'sendQueueSize', - 'SO': 'socketOptionsAndValues', - 'SS': 'socketStates', - 'ST': 'connectionState', - 'TF': 'tcpFlagsAndValues', - 'WR': 'windowReadSize', - 'WW': 'windowWriteSize' -} - -def parseElements(elements): - global opField - global tcptpiField - output = {} - for el in elements: - ident = el[0:1] - content = el[1:] - identType = opField.get(ident, ident) - if identType: - if not identType in output: - output[identType] = {} - if ident == 'T': - fifc = (content + '=').split('=') - fifcType = tcptpiField.get(fifc[0], fifc[0]) - output[identType][fifcType] = fifc[1] - - else: - output[identType] = content - - return output - -def parser(stdout, stderr): - output = {} - pid = None - if stdout: - for line in re.split(r'\x00\n', stdout): - line = re.sub(r'^[\s\x00]*', '', line) - elements = re.split(r'\x00', line) - if not elements: - continue - - first = elements.pop(0) - if first: - ident = first[0:1] - content = first[1:] - if ident == 'p': - pid = content - output[pid] = parseElements(elements) - output[pid]['pid'] = pid - output[pid]['files'] = [] - - if pid and ident == 'f': - output[pid]['files'].append(parseElements(elements)) - - return {'output': output} - -def register(main): - main['lsof'] = { - 'cmd': 'lsof -F0', - 'description': 'Information about files opened by processes', - 'parser': parser - } diff --git a/modules/lspci.py b/modules/lspci.py deleted file mode 100644 index ddbed32..0000000 --- a/modules/lspci.py +++ /dev/null @@ -1,26 +0,0 @@ - -import re -from sysinfo_lib import camelCase - -def parser(stdout, stderr): - output = {} - slot = None - if stdout: - for line in stdout.splitlines(): - slotSearch = re.search(r'^Slot:\s+(.*)$', line, re.IGNORECASE) - if slotSearch: - slot = slotSearch.group(1).strip() - output[slot] = {} - - keyValueSearch = re.search(r'^(\S[^:]+):\s+(.*)$', line) - if slot and keyValueSearch: - output[slot][camelCase(keyValueSearch.group(1))] = keyValueSearch.group(2).strip() - - return {'output': output} - -def register(main): - main['lspci'] = { - 'cmd': 'lspci -mm -vvv', - 'description': 'List all PCI devices', - 'parser': parser - } diff --git a/modules/lsusb.py b/modules/lsusb.py deleted file mode 100644 index d70afab..0000000 --- a/modules/lsusb.py +++ /dev/null @@ -1,93 +0,0 @@ - -import re -from sysinfo_lib import camelCase - -def extractDescriptors(data, lineNumber, offset): - output = {} - ln = lineNumber - descOffset = 0 - while ln < len(data): - line = data[ln] - searchDesc = re.search(r'^(\s*)(.*)Descriptors?:\s*$', line, re.IGNORECASE) - searchStatus = re.search(r'^(\s*)(.*Status)?:\s*(.*)$', line, re.IGNORECASE) - searchKeyValue = re.search(r'^\s*(\S+)\s+([0-9].*)$', line) - searchOffset = re.search(r'^(\s*)', line) - if searchOffset: - lineOffset = len(searchOffset.group(1)) - - if lineOffset < offset: - ln -= 1 - break - - if searchDesc: - descOffset = len(searchDesc.group(1)) + 2 - desc, ln = extractDescriptors(data, ln + 1, descOffset) - name = camelCase(searchDesc.group(2).strip()) - output[name] = desc - - elif searchStatus: - statusOffset = len(searchStatus.group(1)) + 2 - desc, ln = extractDescriptors(data, ln + 1, statusOffset) - name = camelCase(searchStatus.group(2).strip()) - if searchStatus.group(3).strip() != '': - desc['value'] = searchStatus.group(3).strip() - output[name] = desc - - elif searchKeyValue: - key = searchKeyValue.group(1).strip(':') - value = searchKeyValue.group(2).strip() - valueSplit = re.search(r'^(\S+)\s+(\S.*)$', value) - if valueSplit: - value = [valueSplit.group(1), valueSplit.group(2)] - if key in output: - output[key] = [output[key], value] - else: - output[key] = value - - else: - if not 'data' in output: - output['data'] = [] - output['data'].append(line.strip()) - - ln += 1 - return output, ln - -def parseBlock(data): - output = {} - lines = data.split('\n') - - while lines[0].strip() == '': - lines.pop(0) - - firstLine = lines.pop(0) - busDevice = re.search(r'Bus\s+(\S+)\s+Device\s+([^:]+):\s+ID\s+([^:]+):(\S+)\s*(.*)$', firstLine, re.IGNORECASE) - if busDevice: - output['bus'] = busDevice.group(1) - output['device'] = busDevice.group(2) - output['idVendor'] = busDevice.group(3) - output['idProduct'] = busDevice.group(4) - output['vendorProduct'] = busDevice.group(5) - - output['desc'], tmp = extractDescriptors(lines, 0, 0) - - return output - -def parser(stdout, stderr): - output = {} - if stdout: - delimiter = '-' * 20 - blocks = re.split(delimiter, re.sub(r'\n\nBus', '\n\n' + delimiter + 'Bus', stdout)) - if blocks: - for block in blocks: - blockData = parseBlock(block) - if 'bus' in blockData and 'device' in blockData: - id = blockData['bus'] + '/' + blockData['device'] - output[id] = blockData - return {'output': output} - -def register(main): - main['lsusb'] = { - 'cmd': 'lsusb -v', - 'description': 'List USB devices', - 'parser': parser - } \ No newline at end of file diff --git a/modules/modinfo.py b/modules/modinfo.py deleted file mode 100644 index 16a90fe..0000000 --- a/modules/modinfo.py +++ /dev/null @@ -1,31 +0,0 @@ -import re -from sysinfo_lib import camelCase - -def parser(stdout, stderr): - output = {} - moduleName = None - if stdout: - for line in stdout.splitlines(): - values = re.search(r'^([^:]+):\s+(.*)$', line) - if not values: - continue - - key = values.group(1) - value = values.group(2) - - if key == 'moduleName': - moduleName = value - output[moduleName] = {} - - if moduleName: - output[moduleName][key] = value.strip() - - - return {'output': output} - -def register(main): - main['modinfo'] = { - 'cmd': """lsmod | grep -v "Module" | sed 's/ .*//g' | xargs -I {} -n 1 sh -c "echo 'moduleName: {}'; modinfo {}" """, - 'description': 'Information about a Linux Kernel modules', - 'parser': parser - } diff --git a/modules/parted.py b/modules/parted.py deleted file mode 100644 index 477bd88..0000000 --- a/modules/parted.py +++ /dev/null @@ -1,52 +0,0 @@ - -import re - -def parser(stdout, stderr): - output = {} - defaultUnit = None - path = None - if stdout: - for line in stdout.splitlines(): - if line.strip() == '': - defaultUnit = None - path = None - - unitSearch = re.search(r'^(\S+);$', line) - if unitSearch: - defaultUnit = unitSearch.group(1) - - lineSplit = (line.strip(';') + (':' * 10)).split(':') - - if defaultUnit and re.search(r'^(\/[^:]+)', line): - path = lineSplit[0] - output[path] = { - 'path': lineSplit[0], - 'defaultUnit': defaultUnit, - 'end': lineSplit[1], - 'devType': lineSplit[2], - 'sectorSize': lineSplit[3], - 'physSectorSize': lineSplit[4], - 'ptName': lineSplit[5], - 'model': lineSplit[6], - 'diskFlags': lineSplit[7], - 'table': {} - } - - if path and re.search(r'^(\d+):', line): - output[path]['table'][lineSplit[0]] = { - 'start': lineSplit[1], - 'end': lineSplit[2], - 'size': lineSplit[3], - 'fileSystem': lineSplit[4], - 'flags': lineSplit[5] - } - - return {'output': output} - -def register(main): - main['parted'] = { - 'cmd': 'parted -m -l print', - 'description': 'Lists partition layout on all block devices', - 'parser': parser - } - \ No newline at end of file diff --git a/modules/proc_cmdline.py b/modules/proc_cmdline.py deleted file mode 100644 index e248f95..0000000 --- a/modules/proc_cmdline.py +++ /dev/null @@ -1,30 +0,0 @@ - -import re - -def parser(stdout, stderr): - output = {} - if stdout: - for kv in re.split(r'[\s\t]+', stdout.strip()): - splitted = re.search(r'^([^=]+)=(.*)$', kv) - if splitted: - key = splitted.group(1) - value = splitted.group(2) - else: - key = kv - value = '' - if key in output: - if isinstance(output[key], str): - output[key] = [output[key]] - - output[key].append(value) - else: - output[key] = value - - return {'output': output} - -def register(main): - main['proc_cmdline'] = { - 'cmd': 'cat /proc/cmdline', - 'description': 'Parameters passed to the kernel at the time it is started', - 'parser': parser - } diff --git a/modules/proc_consoles.py b/modules/proc_consoles.py deleted file mode 100644 index dbd0434..0000000 --- a/modules/proc_consoles.py +++ /dev/null @@ -1,52 +0,0 @@ - -import re - -def parser(stdout, stderr): - """ - The columns are: - device name of the device - operations R = can do read operations - W = can do write operations - U = can do unblank - flags E = it is enabled - C = it is preferred console - B = it is primary boot console - p = it is used for printk buffer - b = it is not a TTY but a Braille device - a = it is safe to use when cpu is offline - major:minor major and minor number of the device separated by a colon - """ - - output = {} - if stdout: - for line in stdout.splitlines(): - values = re.search(r'^(\S+)\s+(.*)\s+(\S+):(\S+)', line) - if values: - params = values.group(2).strip() - output[values.group(1)] = { - 'device': values.group(1), - 'operations': { - 'read': 'R' in params, - 'write': 'W' in params, - 'unblank': 'U' in params, - }, - 'flags': { - 'enabled': 'E' in params, - 'preferred': 'C' in params, - 'primaryBoot': 'B' in params, - 'printkBuffer': 'p' in params, - 'braile': 'b' in params, - 'safeCpuOffline': 'a' in params, - }, - 'major': values.group(3), - 'minor': values.group(4) - } - - return {'output': output} - -def register(main): - main['proc_consoles'] = { - 'cmd': 'cat /proc/consoles', - 'description': 'Information about current consoles including tty', - 'parser': parser - } diff --git a/modules/proc_cpuinfo.py b/modules/proc_cpuinfo.py deleted file mode 100644 index 1b8e920..0000000 --- a/modules/proc_cpuinfo.py +++ /dev/null @@ -1,36 +0,0 @@ - -import re -from sysinfo_lib import camelCase - -def parser(stdout, stderr): - output = { - 'processor': {}, - 'hardware': {}, - 'oth': {} - } - - if stdout: - for block in re.split(r'\r\r|\n\n|\r\n\r\n', stdout): - sub = {} - for line in block.splitlines(): - values = re.search(r'([^\t]+)\s*:\s*(.*)$', line) - if values: - sub[camelCase(values.group(1).strip())] = values.group(2).strip() - - if 'processor' in sub: - output['processor'][sub['processor']] = sub - - elif 'hardware' in sub: - output['hardware'] = sub - - else: - output['oth'] = sub - - return {'output': output} - -def register(main): - main['proc_cpuinfo'] = { - 'cmd': 'cat /proc/cpuinfo', - 'description': 'Type of processor used by your system', - 'parser': parser - } diff --git a/modules/proc_crypto.py b/modules/proc_crypto.py deleted file mode 100644 index 442f82c..0000000 --- a/modules/proc_crypto.py +++ /dev/null @@ -1,25 +0,0 @@ - -import re -from sysinfo_lib import camelCase - -def parser(stdout, stderr): - output = {} - if stdout: - for block in re.split(r'\r\r|\n\n|\r\n\r\n', stdout): - sub = {} - for line in block.splitlines(): - values = re.search(r'([^\t]+)\s*:\s*(.*)$', line) - if values: - sub[camelCase(values.group(1).strip())] = values.group(2).strip() - - if 'name' in sub: - output[sub['name']] = sub - - return {'output': output} - -def register(main): - main['proc_crypto'] = { - 'cmd': 'cat /proc/crypto', - 'description': 'Installed cryptographic ciphers used by the Linux kernel', - 'parser': parser - } diff --git a/modules/proc_devices.py b/modules/proc_devices.py deleted file mode 100644 index 7d11094..0000000 --- a/modules/proc_devices.py +++ /dev/null @@ -1,31 +0,0 @@ - -import re -from sysinfo_lib import camelCase - -def parser(stdout, stderr): - output = {} - deviceType = None - if stdout: - for line in stdout.splitlines(): - dt = re.search(r'^([^:]+):', line) - if dt: - deviceType = camelCase(dt.group(1)) - output[deviceType] = {} - - dv = re.search(r'^\s*(\d+)\s*(.*)$', line) - if dv and deviceType: - id = dv.group(1) - name = dv.group(2) - if not name in output[deviceType]: - output[deviceType][name] = [] - - output[deviceType][name].append(id) - - return {'output': output} - -def register(main): - main['proc_devices'] = { - 'cmd': 'cat /proc/devices', - 'description': 'Installed cryptographic ciphers used by the Linux kernel', - 'parser': parser - } diff --git a/modules/proc_diskstats.py b/modules/proc_diskstats.py deleted file mode 100644 index d3d29cd..0000000 --- a/modules/proc_diskstats.py +++ /dev/null @@ -1,44 +0,0 @@ - -import re -from sysinfo_lib import camelCase - -def parser(stdout, stderr): - output = {} - columnNames = [ - 'majorNumber', - 'minorNumber', - 'deviceName', - 'readsCompletedSuccessfully', - 'readsMerged', - 'sectorsRead', - 'timeSpentReading', - 'writesCompleted', - 'writesMerged', - 'sectorsWritten', - 'timeSpentWriting', - 'IOsCurrentlyInProgress', - 'timeSpentDoingIOs', - 'weightedTimeSpentDoingIOs' - ] - lenColumnNames = len(columnNames) - - if stdout: - for line in stdout.splitlines(): - line = line.strip() - columns = re.split(r'\s+', line) - if not columns: - continue - - output[columns[2]] = {} - for num, val in enumerate(columns, start=0): - if num < lenColumnNames: - output[columns[2]][columnNames[num]] = val - - return {'output': output} - -def register(main): - main['proc_diskstats'] = { - 'cmd': 'cat /proc/diskstats', - 'description': 'I/O statistics of block devices', - 'parser': parser - } diff --git a/modules/proc_dma.py b/modules/proc_dma.py deleted file mode 100644 index da73728..0000000 --- a/modules/proc_dma.py +++ /dev/null @@ -1,19 +0,0 @@ - -import re - -def parser(stdout, stderr): - output = {} - if stdout: - for line in stdout.splitlines(): - keyValueSearch = re.search(r'^\s*([^:]+):\s*(.*)$', line) - if keyValueSearch: - output[keyValueSearch.group(1)] = keyValueSearch.group(2).strip() - - return {'output': output} - -def register(main): - main['proc_dma'] = { - 'cmd': 'cat /proc/dma', - 'description': 'List of the registered ISA DMA channels in use', - 'parser': parser - } \ No newline at end of file diff --git a/modules/proc_filesystems.py b/modules/proc_filesystems.py deleted file mode 100644 index 6a2d22f..0000000 --- a/modules/proc_filesystems.py +++ /dev/null @@ -1,23 +0,0 @@ - -import re - -def parser(stdout, stderr): - output = {} - if stdout: - for line in stdout.splitlines(): - lineMatch = re.search(r'^(\S+)\s+(\S+)', line) - if lineMatch: - output[lineMatch.group(2).strip()] = lineMatch.group(1).strip() - - lineMatch = re.search(r'^\s+(\S+)$', line) - if lineMatch: - output[lineMatch.group(1).strip()] = '' - - return {'output': output} - -def register(main): - main['proc_filesystems'] = { - 'cmd': 'cat /proc/filesystems', - 'description': 'List of the file system types currently supported by the kernel', - 'parser': parser - } \ No newline at end of file diff --git a/modules/proc_fs.py b/modules/proc_fs.py deleted file mode 100644 index 2c38911..0000000 --- a/modules/proc_fs.py +++ /dev/null @@ -1,39 +0,0 @@ - -import re - -def setPathValue(data, path, value): - pathRest = None - pathParts = re.search(r'^([^\/]+)\/?(.*)$', path) - if pathParts: - path = pathParts.group(1) - pathRest = pathParts.group(2) - - if not path in data: - data[path] = {} - - if pathRest: - setPathValue(data[path], pathRest, value) - else: - if isinstance(data[path], dict): - data[path] = [] - data[path].append(value) - -def parser(stdout, stderr): - output = {} - if stdout: - for line in stdout.splitlines(): - pathValue = re.search(r'^\/proc\/fs\/([^:]+):(.*)$', line) - if pathValue: - path = pathValue.group(1) - value = pathValue.group(2) - if not re.search(r'^\s*#', value): - setPathValue(output, path, value) - - return {'output': output} - -def register(main): - main['proc_fs'] = { - 'cmd': 'find /proc/fs -type f -follow -print | xargs grep ""', - 'description': 'File system parameters', - 'parser': parser - } diff --git a/modules/proc_iomem.py b/modules/proc_iomem.py deleted file mode 100644 index 9781a58..0000000 --- a/modules/proc_iomem.py +++ /dev/null @@ -1,23 +0,0 @@ - -import re - -def parser(stdout, stderr): - output = [] - if stdout: - for line in stdout.splitlines(): - values = re.search(r'^\s*([^-]+)-(\S+)\s*:\s*(.*)$', line) - if values: - output.append({ - 'from': values.group(1), - 'to': values.group(2), - 'device': values.group(3) - }) - - return {'output': output} - -def register(main): - main['proc_iomem'] = { - 'cmd': 'cat /proc/iomem', - 'description': """Map of the system's memory for each physical device""", - 'parser': parser - } \ No newline at end of file diff --git a/modules/proc_ioports.py b/modules/proc_ioports.py deleted file mode 100644 index 5d02a71..0000000 --- a/modules/proc_ioports.py +++ /dev/null @@ -1,23 +0,0 @@ - -import re - -def parser(stdout, stderr): - output = [] - if stdout: - for line in stdout.splitlines(): - entrySearch = re.search(r'^\s*([^-]+)-(\S+)\s*:\s*(.*)$', line, re.IGNORECASE) - if entrySearch: - output.append({ - 'from': entrySearch.group(1), - 'to': entrySearch.group(2), - 'device': entrySearch.group(3) - }) - - return {'output': output} - -def register(main): - main['proc_ioports'] = { - 'cmd': 'cat /proc/ioports', - 'description': 'List of currently registered port regions used for input or output communication with a device', - 'parser': parser - } diff --git a/modules/proc_loadavg.py b/modules/proc_loadavg.py deleted file mode 100644 index 7f7929e..0000000 --- a/modules/proc_loadavg.py +++ /dev/null @@ -1,24 +0,0 @@ - -import re - -def parser(stdout, stderr): - output = {} - if stdout: - values = re.search(r'^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)', stdout.strip()) - if values: - output = { - 'periodLast': values.group(1), - 'period5minute': values.group(2), - 'period15minute': values.group(3), - 'processes': values.group(4), - 'lastPid': values.group(5) - } - - return {'output': output} - -def register(main): - main['proc_loadavg'] = { - 'cmd': 'cat /proc/loadavg', - 'description': 'Load average in regard to both the CPU and IO over time', - 'parser': parser - } \ No newline at end of file diff --git a/modules/proc_locks.py b/modules/proc_locks.py deleted file mode 100644 index 82a8eb4..0000000 --- a/modules/proc_locks.py +++ /dev/null @@ -1,27 +0,0 @@ - -import re - -def parser(stdout, stderr): - output = {} - if stdout: - for line in stdout.splitlines(): - lineSplit = re.split(r'[\s\t]+', line) - if lineSplit and len(lineSplit) > 5: - output[lineSplit[0]] = { - 'uid': lineSplit[0], - 'class': lineSplit[1], - 'lockType': lineSplit[2], - 'allowAccessType': lineSplit[3], - 'pid': lineSplit[4], - 'fileID': lineSplit[5], - 'lockedRegion': lineSplit[6] - } - - return {'output': output} - -def register(main): - main['proc_locks'] = { - 'cmd': 'cat /proc/locks', - 'description': 'Files currently locked by the kernel', - 'parser': parser - } \ No newline at end of file diff --git a/modules/proc_meminfo.py b/modules/proc_meminfo.py deleted file mode 100644 index f9bd14d..0000000 --- a/modules/proc_meminfo.py +++ /dev/null @@ -1,33 +0,0 @@ - -import re -from sysinfo_lib import camelCase - -def parser(stdout, stderr): - output = {} - if stdout: - for line in stdout.splitlines(): - keyValueSearch = re.search(r'^([^:]+):\s*(.*)$', line, re.IGNORECASE) - if keyValueSearch: - key = keyValueSearch.group(1).strip(':') - value = keyValueSearch.group(2).strip() - - valueSearch = re.search(r'(.*)\s+(.*)$', value) - if valueSearch: - output[camelCase(key)] = { - 'value': valueSearch.group(1), - 'type': valueSearch.group(2) - } - else: - output[camelCase(key)] = { - 'value': value, - 'type': '' - } - - return {'output': output} - -def register(main): - main['proc_meminfo'] = { - 'cmd': 'cat /proc/meminfo', - 'description': 'Reports a large amount of valuable information about the systems RAM usage', - 'parser': parser - } diff --git a/modules/proc_modules.py b/modules/proc_modules.py deleted file mode 100644 index 4c5e7ea..0000000 --- a/modules/proc_modules.py +++ /dev/null @@ -1,26 +0,0 @@ - -import re - -def parser(stdout, stderr): - output = {} - if stdout: - for line in stdout.splitlines(): - lineSplit = re.split(r'[\s\t]+', line) - if lineSplit and len(lineSplit) > 5: - output[lineSplit[0]] = { - 'moduleName': lineSplit[0], - 'moduleMemorySize': lineSplit[1], - 'numInstancesLoaded': lineSplit[2], - 'depends': lineSplit[3].strip(',').split(','), - 'state': lineSplit[4], - 'kernelMemoryOffset': lineSplit[5] - } - - return {'output': output} - -def register(main): - main['proc_modules'] = { - 'cmd': 'cat /proc/modules', - 'description': 'List of all modules loaded into the kernel', - 'parser': parser - } \ No newline at end of file diff --git a/modules/proc_mounts.py b/modules/proc_mounts.py deleted file mode 100644 index a67d195..0000000 --- a/modules/proc_mounts.py +++ /dev/null @@ -1,29 +0,0 @@ - -import re - -def parser(stdout, stderr): - output = {} - if stdout: - for line in stdout.splitlines(): - lineSplit = re.split(r'[\s\t]+', line) - if lineSplit and len(lineSplit) > 4: - accessValues = {} - for access in re.split(r',', lineSplit[3]): - accessSplit = re.split(r'=', access + '=') - accessValues[accessSplit[0]] = accessSplit[1] - - output[lineSplit[1]] = { - 'device': lineSplit[0], - 'mountPoint': lineSplit[1], - 'type': lineSplit[2], - 'access': accessValues - } - - return {'output': output} - -def register(main): - main['proc_mounts'] = { - 'cmd': 'cat /proc/mounts', - 'description': 'List mounted filesystems (info provides from kernel)', - 'parser': parser - } \ No newline at end of file diff --git a/modules/proc_partitions.py b/modules/proc_partitions.py deleted file mode 100644 index 1a28ee9..0000000 --- a/modules/proc_partitions.py +++ /dev/null @@ -1,17 +0,0 @@ - -from sysinfo_lib import parseSpaceTable, tableToDict - -def parser(stdout, stderr): - output = {} - if stdout: - output = parseSpaceTable(stdout) - output = tableToDict(output, 'name') - - return {'output': output} - -def register(main): - main['proc_partitions'] = { - 'cmd': 'cat /proc/partitions', - 'description': 'Partition block allocation information', - 'parser': parser - } \ No newline at end of file diff --git a/modules/proc_scsi.py b/modules/proc_scsi.py deleted file mode 100644 index a77bdbb..0000000 --- a/modules/proc_scsi.py +++ /dev/null @@ -1,42 +0,0 @@ - -import re - -def parser(stdout, stderr): - output = {} - path = None - if stdout: - for line in stdout.splitlines(): - hostSearch = re.search(r'^Host:\s+(\S+)\s+Channel:\s+(\S+)\s+Id:\s+(\S+)\s+Lun:\s+(\S+)$', line, re.IGNORECASE) - if hostSearch: - host = hostSearch.group(1) - channel = hostSearch.group(2) - id = hostSearch.group(3) - lun = hostSearch.group(4) - path = '%s:%s:%s:%s' % (host.replace('scsi', ''), channel, id, lun) - output[path] = { - 'host': host, - 'channel': channel, - 'id': id, - 'lun': lun - } - - if path: - vendorSearch = re.search(r'^\s+Vendor:\s+(.*)\s+Model:\s+(.*)\s+Rev:\s+(.*)$', line, re.IGNORECASE) - if vendorSearch: - output[path]['vendor'] = vendorSearch.group(1).strip() - output[path]['model'] = vendorSearch.group(2).strip() - output[path]['rev'] = vendorSearch.group(3).strip() - - typeSearch = re.search(r'^\s+Type:\s+(.*)\s+ANSI\s+SCSI\s+revision:\s+(.*)$', line, re.IGNORECASE) - if typeSearch: - output[path]['type'] = typeSearch.group(1).strip() - output[path]['revision'] = typeSearch.group(2).strip() - - return {'output': output} - -def register(main): - main['proc_scsi'] = { - 'cmd': 'cat /proc/scsi/scsi', - 'description': 'List of every recognized SCSI device', - 'parser': parser - } diff --git a/modules/proc_swaps.py b/modules/proc_swaps.py deleted file mode 100644 index 8726e37..0000000 --- a/modules/proc_swaps.py +++ /dev/null @@ -1,16 +0,0 @@ - -from sysinfo_lib import parseSpaceTable - -def parser(stdout, stderr): - output = {} - if stdout: - output = parseSpaceTable(stdout) - - return {'output': output} - -def register(main): - main['proc_swaps'] = { - 'cmd': 'cat /proc/swaps', - 'description': 'Measures swap space and its utilization', - 'parser': parser - } \ No newline at end of file diff --git a/modules/proc_sys.py b/modules/proc_sys.py deleted file mode 100644 index 33944a3..0000000 --- a/modules/proc_sys.py +++ /dev/null @@ -1,39 +0,0 @@ - -import re - -def setPathValue(data, path, value): - pathRest = None - pathParts = re.search(r'^([^\/]+)\/?(.*)$', path) - if pathParts: - path = pathParts.group(1) - pathRest = pathParts.group(2) - - if not path in data: - data[path] = {} - - if pathRest: - setPathValue(data[path], pathRest, value) - else: - data[path] = value - -def parser(stdout, stderr): - output = {} - if stdout: - for line in stdout.splitlines(): - pathValue = re.search(r'^\/proc\/sys\/([^:]+):(.*)$', line) - if pathValue: - path = pathValue.group(1) - value = pathValue.group(2) - if value.strip() == '': - continue - if not re.search(r'^\s*#', value): - setPathValue(output, path, value) - - return {'output': output} - -def register(main): - main['proc_sys'] = { - 'cmd': """find /proc/sys -type f -follow -print 2>/dev/null | xargs -n 1 -I {} sh -c 'VAL=$(cat {} 2>/dev/null); echo {}:$VAL;'""", - 'description': 'Information about the system and kernel features', - 'parser': parser - } \ No newline at end of file diff --git a/modules/proc_uptime.py b/modules/proc_uptime.py deleted file mode 100644 index 76eff29..0000000 --- a/modules/proc_uptime.py +++ /dev/null @@ -1,20 +0,0 @@ -import re - -def parser(stdout, stderr): - output = {} - if stdout: - splitted = re.split(r'\s+', stdout.strip()) - if len(splitted) > 0: - output['systemUp'] = splitted[0] - - if len(splitted) > 1: - output['sumCoresIdle'] = splitted[1] - - return {'output': output} - -def register(main): - main['proc_uptime'] = { - 'cmd': 'cat /proc/uptime', - 'description': 'Information detailing how long the system has been on since its last restart', - 'parser': parser - } \ No newline at end of file diff --git a/modules/proc_version.py b/modules/proc_version.py deleted file mode 100644 index 174f919..0000000 --- a/modules/proc_version.py +++ /dev/null @@ -1,14 +0,0 @@ - -def parser(stdout, stderr): - output = {} - if stdout: - output = stdout.strip() - - return {'output': output} - -def register(main): - main['proc_version'] = { - 'cmd': 'cat /proc/version', - 'description': 'Version of the Linux kernel, the version of gcc used to compile the kernel, and the time of kernel compilation', - 'parser': parser - } \ No newline at end of file diff --git a/modules/proc_vmstat.py b/modules/proc_vmstat.py deleted file mode 100644 index bdf2c31..0000000 --- a/modules/proc_vmstat.py +++ /dev/null @@ -1,18 +0,0 @@ -import re - -def parser(stdout, stderr): - output = {} - if stdout: - for line in stdout.splitlines(): - lineMatch = re.search(r'^([^\s+]+)\s(.*)$', line) - if lineMatch: - output[lineMatch.group(1)] = lineMatch.group(2) - - return {'output': output} - -def register(main): - main['proc_vmstat'] = { - 'cmd': 'cat /proc/vmstat', - 'description': 'Detailed virtual memory statistics from the kernel', - 'parser': parser - } diff --git a/modules/prtstat.py b/modules/prtstat.py deleted file mode 100644 index 9a476dc..0000000 --- a/modules/prtstat.py +++ /dev/null @@ -1,25 +0,0 @@ - -import re - -def parser(stdout, stderr): - output = {} - pid = '' - if stdout: - for line in stdout.splitlines(): - lineMatch = re.findall(r'(\S+):\s(\S+)', line) - for kv in lineMatch: - if kv[0] == 'pid': - pid = kv[1] - output[pid] = {} - - if pid != '': - output[pid][kv[0]] = kv[1] - - return {'output': output} - -def register(main): - main['prtstat'] = { - 'cmd': 'ps -eo pid | xargs -I {} prtstat -r {}', - 'description': 'Print statistics of a processes', - 'parser': parser - } \ No newline at end of file diff --git a/modules/ps.py b/modules/ps.py deleted file mode 100644 index 9d62fe7..0000000 --- a/modules/ps.py +++ /dev/null @@ -1,51 +0,0 @@ - -import re - -def parser(stdout, stderr): - output = {} - columnsNames = [ - 'user', - 'ruser', - 'group', - 'rgroup', - 'pid', - 'ppid', - 'pgid', - 'cpu', - 'size', - 'bytes', - 'nice', - 'time', - 'stime', - 'tty', - 'args' - ] - columnsCount = len(columnsNames) - if stdout: - for line in stdout.splitlines(): - if re.search(r'ps --cols 2048 -eo', line) or re.search(r'USER.*RUSER.*GROUP', line): - continue - - cols = re.split(r'\s+', line) - if cols: - entry = {} - for num, val in enumerate(cols, start=0): - if num < columnsCount: - name = columnsNames[num] - entry[name] = val - elif 'args' in entry: - entry['args'] += ' ' + val - - if 'pid' in entry: - output[entry['pid']] = entry - - return {'output': output} - -def register(main): - main['ps'] = { - 'cmd': 'ps --cols 2048 -eo user:80,ruser:80,group:80,rgroup:80,pid,ppid,pgid,pcpu,vsz,nice,etime,time,stime,tty,args 2>/dev/null', - 'description': 'Report a snapshot of the current processes', - 'parser': parser - } - - diff --git a/modules/python.py b/modules/python.py deleted file mode 100644 index a665ef2..0000000 --- a/modules/python.py +++ /dev/null @@ -1,61 +0,0 @@ -import platform - -loaded_pkg_resources = False - -try: - import pkg_resources - loaded_pkg_resources = True -except: - pass - -def python_pip_packages(): - output = {} - for pkg in pkg_resources.working_set: - package = {} - for key in ['location', 'project_name', 'key', 'version', 'parsed_version', 'py_version', 'platform', 'precedence']: - if hasattr(pkg, key): - package[key] = str(getattr(pkg, key)) - - if 'key' in package: - output[package['key']] = package - - return output - -def python_platform(): - output = { - 'architecture': platform.architecture(), - 'machine': platform.machine(), - 'node': platform.node(), - 'platform': { - 'normal': platform.platform(), - 'aliased': platform.platform(aliased=True), - 'terse': platform.platform(terse=True) - }, - 'processor': platform.processor(), - 'python': { - 'branch': platform.python_branch(), - 'build': platform.python_build(), - 'compiler': platform.python_compiler(), - 'implementation': platform.python_implementation(), - 'revision': platform.python_revision(), - 'version': platform.python_version(), - 'versionTuple': platform.python_version_tuple(), - }, - 'release': platform.release(), - 'system': platform.system(), - 'version': platform.version(), - 'uname': platform.uname(), - } - return output - -def register(main): - if loaded_pkg_resources: - main['python_pip_packages'] = { - 'function': python_pip_packages, - 'description': 'List available python modules', - } - - main['python_platform'] = { - 'function': python_platform, - 'description': 'Probe the underlying platform\'s hardware, operating system, and Python interpreter version information', - } diff --git a/modules/rpm.py b/modules/rpm.py deleted file mode 100644 index 0f2c4f2..0000000 --- a/modules/rpm.py +++ /dev/null @@ -1,18 +0,0 @@ - -from sysinfo_lib import parseCharDelimitedTable, tableToDict - -def parser(stdout, stderr): - output = {} - columns = ['installtime', 'buildtime', 'name', 'version', 'release', 'arch', 'vendor', 'packager', 'distribution', 'disttag'] - if stdout: - output = parseCharDelimitedTable(stdout, '|', columns) - output = tableToDict(output, 'name') - - return {'output': output} - -def register(main): - main['rpm'] = { - 'cmd': 'rpm -q -a --queryformat "%{INSTALLTIME}|%{BUILDTIME}|%{NAME}|%{VERSION}|%{RELEASE}|%{arch}|%{VENDOR}|%{PACKAGER}|%{DISTRIBUTION}|%{DISTTAG}\n"', - 'description': 'Querying all RPM packages', - 'parser': parser - } diff --git a/modules/services_status.py b/modules/services_status.py deleted file mode 100644 index cb9057d..0000000 --- a/modules/services_status.py +++ /dev/null @@ -1,39 +0,0 @@ - -import re -from sysinfo_lib import parseTable, tableToDict - -def parser_services(stdout, stderr): - output = parseTable(stdout) - output = tableToDict(output, 'unit') - return {'output': output} - -def parser_services_params(stdout, stderr): - output = {} - service = None - if stdout: - for line in stdout.splitlines(): - serviceSearch = re.search(r'^>>>\s*Service:\s*(.*)$', line) - if serviceSearch: - service = serviceSearch.group(1).strip() - output[service] = {} - - if service: - keyValueSearch = re.search(r'^([^=]+)=(.*)$', line) - if keyValueSearch: - output[service][keyValueSearch.group(1)] = keyValueSearch.group(2).strip() - - return {'output': output} - -def register(main): - main['services_list'] = { - 'cmd': """systemctl -l --type service --all --plain | grep -i -e ".service\|description" | sed 's/^\s*//g' """, - 'description': 'Displays services with status', - 'parser': parser_services - } - - main['services_params'] = { - 'cmd': """systemctl -l --type service --all --plain | sed -E 's/^\s*(\\S+.service).*$/\\1/g' | grep -i -e ".service" | xargs -I '{}' sh -c "echo '>>> Service: {}'; systemctl show {} --no-page" """, - 'description': 'Displays services with status', - 'parser': parser_services_params - } - diff --git a/modules/sysctl.py b/modules/sysctl.py deleted file mode 100644 index 8b1bd2e..0000000 --- a/modules/sysctl.py +++ /dev/null @@ -1,41 +0,0 @@ - -import re - -def setPathValue(data, path, value): - pathRest = None - pathParts = re.search(r'^([^\.]+)\.?(.*)$', path) - if pathParts: - path = pathParts.group(1) - pathRest = pathParts.group(2) - - if not path in data: - data[path] = {} - - if pathRest: - setPathValue(data[path], pathRest, value) - else: - data[path] = value - -def parser(stdout, stderr): - output = {} - if stdout: - for line in stdout.splitlines(): - kv = re.search(r'^([^=]+)=(.*)$', line) - if kv: - key = kv.group(1).strip() - value = kv.group(2).strip() - setPathValue(output, key, value) - - return {'output': output} - -def register(main): - main['sysctl'] = { - 'cmd': 'sysctl -a -e', - 'description': 'Runtime kernel parameters', - 'parser': parser - } - main['sysctl_system'] = { - 'cmd': 'sysctl -a -e --system', - 'description': 'Runtime kernel parameters from all system configuration files', - 'parser': parser - } diff --git a/modules/timedatectl.py b/modules/timedatectl.py deleted file mode 100644 index 84d286e..0000000 --- a/modules/timedatectl.py +++ /dev/null @@ -1,20 +0,0 @@ - -import re -from sysinfo_lib import camelCase - -def parser(stdout, stderr): - output = {} - if stdout: - for line in stdout.splitlines(): - lineMatch = re.search(r'^([^:]+):\s*(.*)', line) - if lineMatch: - output[camelCase(lineMatch.group(1))] = lineMatch.group(2) - - return {'output': output} - -def register(main): - main['timedatectl'] = { - 'cmd': 'timedatectl status', - 'description': 'System time and date', - 'parser': parser - } \ No newline at end of file diff --git a/modules/udevadm.py b/modules/udevadm.py deleted file mode 100644 index f10dc53..0000000 --- a/modules/udevadm.py +++ /dev/null @@ -1,97 +0,0 @@ - -import re -from sysinfo_lib import camelCase - -def parser(stdout, stderr): - output = {'devices': {}, 'parents': {}} - types = { - 'P': 'path', - 'N': 'node', - 'L': 'linkPriority', - 'E': 'entry', - 'S': 'link' - } - device = None - parent = None - if stdout: - for line in stdout.splitlines(): - deviceSearch = re.search(r'^>>> Device: (\S+)', line) - if deviceSearch: - device = deviceSearch.group(1) - output['devices'][device] = {'parents': [], 'entry': {}, 'link': []} - parent = None - - if not device: - continue - - keyValue = re.search(r'^(\S):\s+(.*)$', line) - if keyValue: - key = keyValue.group(1) - value = keyValue.group(2).strip() - if key in types: - key = types[key] - - if key == 'entry': - valueSearch = re.search(r'^([^=]+)=(.*)$', value) - if valueSearch: - subkey = valueSearch.group(1).lower() - subvalue = valueSearch.group(2).strip() - output['devices'][device]['entry'][subkey] = subvalue - - elif key == 'link': - output['devices'][device]['link'].append(value) - - else: - output['devices'][device][key] = value - - deviceLook = re.search(r'^\s+looking at device \'([^\']+)', line) - if deviceLook: - parent = deviceLook.group(1) - if not parent in output['parents']: - output['parents'][parent] = {} - output['devices'][device]['parents'].append(parent) - - parentDeviceLook = re.search(r'^\s+looking at device \'([^\']+)', line) - if parentDeviceLook: - parent = parentDeviceLook.group(1) - if not parent in output['parents']: - output['parents'][parent] = {} - output['devices'][device]['parents'].append(parent) - - if parent: - parentKeyValue = re.search(r'^\s+([^=]+)=="([^"]+)"', line) - if parentKeyValue: - key = parentKeyValue.group(1).lower() - value = parentKeyValue.group(2) - - multipleValues = re.match(r'^(\s+\d+)+$', value) - if multipleValues: - value = re.split(r'\s+', value.strip()) - - keyAttr = re.match(r'^(\S+){([^}]+)}', key, re.IGNORECASE) - if keyAttr: - attrType = keyAttr.group(1).lower() - if not attrType in output['parents'][parent]: - output['parents'][parent][attrType] = {} - - attrKey = camelCase(keyAttr.group(2)) - output['parents'][parent][attrType][attrKey] = value - - else: - output['parents'][parent][key] = value - - return {'output': output} - - -def register(main): - main['udevadm'] = { - 'cmd': """udevadm info --export-db | grep "DEVNAME" | cut -d "=" -f2 | xargs -n 1 -I {} sh -c "echo '>>> Device: {}'; udevadm info --query=all --name={}; udevadm info --attribute-walk --name={}" """, - 'description': 'Queries the udev database for device information stored in the udev database', - 'parser': parser - } - - main['udevadm_block_devices'] = { - 'cmd': """find /dev/ -type b | xargs -n 1 -I {} sh -c "echo '>>> Device: {}'; udevadm info --query=all --name={}; udevadm info --attribute-walk --name={}" """, - 'description': 'Queries the udev database for block device information stored in the udev database', - 'parser': parser - } diff --git a/modules/uname.py b/modules/uname.py deleted file mode 100644 index 26d5d3a..0000000 --- a/modules/uname.py +++ /dev/null @@ -1,59 +0,0 @@ - -import re - -def parser(stdout, stderr): - output = '' - if stdout: - output = re.sub(r'\n|\r|\r\n', '', stdout) - output = stdout.strip() - - return {'output': output} - -def register(main): - main['kernel_name'] = { - 'cmd': 'uname -s', - 'description': 'Kernel name', - 'parser': parser - } - - main['kernel_release'] = { - 'cmd': 'uname -r', - 'description': 'Kernel release', - 'parser': parser - } - - main['kernel_version'] = { - 'cmd': 'uname -v', - 'description': 'Kernel version', - 'parser': parser - } - - main['nodename'] = { - 'cmd': 'uname -n', - 'description': 'Network node hostname', - 'parser': parser - } - - main['machine'] = { - 'cmd': 'uname -m', - 'description': 'Machine hardware name', - 'parser': parser - } - - main['processor'] = { - 'cmd': 'uname -p', - 'description': 'Processor type', - 'parser': parser - } - - main['hardware_platform'] = { - 'cmd': 'uname -i', - 'description': 'Hardware platform', - 'parser': parser - } - - main['operating_system'] = { - 'cmd': 'uname -o', - 'description': 'Operating system', - 'parser': parser - } diff --git a/modules/vmstat.py b/modules/vmstat.py deleted file mode 100644 index 2b0c2c5..0000000 --- a/modules/vmstat.py +++ /dev/null @@ -1,116 +0,0 @@ - -import re -import sys -from struct import pack, unpack -from sysinfo_lib import camelCase - -PY2 = sys.version_info[0] == 2 -PY3 = sys.version_info[0] == 3 - -def parser_stats(stdout, stderr): - output = {} - if stdout: - for line in stdout.splitlines(): - entry = re.search(r'^\s*(\d+)\s+(.*)$', line) - if entry: - output[camelCase(entry.group(2).strip())] = entry.group(1).strip() - - return {'output': output} - -def parser_disk(stdout, stderr): - output = {} - sectionsNames = [] - sectionsMask = '' - totalLength = 0 - columnPaths = [] - if stdout: - for line in stdout.splitlines(): - if re.match(r'disk.*reads', line, re.IGNORECASE): - topHeader = re.split(r'\s+', line) - if topHeader: - for value in topHeader: - sectionsNames.append(value.strip().strip('-').lower()) - totalLength += len(value) + 1 - sectionsMask += str(len(value) + 1) + 's' - - elif not sectionsMask: - continue - - if re.match(r'.*total.*merged.*sectors.*', line): - lineFix = line + (' ' * (totalLength - len(line))) - if sys.version_info[0] != 2: - lineFix = bytes(lineFix, 'utf-8') - - sectionData = unpack(sectionsMask, lineFix) - if sectionData: - index = 0 - - for sec in sectionData: - if PY2: - secStrip = sec.strip() - else: - secStrip = str(sec.strip(), 'utf-8') - - topColumns = re.split(r'\s+', secStrip) - if topColumns: - - for column in topColumns: - if column: - columnPaths.append(camelCase('%s %s' %(sectionsNames[index], column, ))) - else: - columnPaths.append('%s' %(sectionsNames[index], )) - index += 1 - else: - entry = {} - columns = re.split(r'\s+', line) - for ci, cv in enumerate(columns): - if ci < len(columnPaths): - entry[columnPaths[ci]] = cv - if 'disk' in entry: - output[entry['disk']] = entry - - return {'output': output} - -def parser_disk_sum(stdout, stderr): - output = {} - if stdout: - for line in stdout.splitlines(): - entry = re.search(r'^\s*(\d+)\s+(.*)$', line) - if entry: - output[camelCase(entry.group(2).strip())] = entry.group(1).strip() - - return {'output': output} - -def parser_forks(stdout, stderr): - output = {} - if stdout: - forks = re.search(r'\s*(\d+)\s*forks', stdout) - if forks: - output['forks'] = forks.group(1) - - return {'output': output} - -def register(main): - main['vmstat_stats'] = { - 'cmd': 'vmstat -s', - 'description': 'Displays a table of various event counters and memory statistics', - 'parser': parser_stats - } - - main['vmstat_disk'] = { - 'cmd': 'vmstat -dwn', - 'description': 'Report disk statistics', - 'parser': parser_disk - } - - main['vmstat_disk_sum'] = { - 'cmd': 'vmstat -D', - 'description': 'Report some summary statistics about disk activity', - 'parser': parser_disk_sum - } - - main['vmstat_forks'] = { - 'cmd': 'vmstat -f', - 'description': 'Displays the number of forks since boot', - 'parser': parser_forks - } diff --git a/modules/yum.py b/modules/yum.py deleted file mode 100644 index c3fa9a6..0000000 --- a/modules/yum.py +++ /dev/null @@ -1,73 +0,0 @@ - -import re - -def parser_repolist(stdout, stderr): - output = {'mirrors': [], 'repos': [], 'errors': []} - insideTable = False - if stdout: - for line in stdout.splitlines(): - mirrors = re.search(r'^\s*\*\s*([^:]+):\s*(.*)$', line) - if mirrors: - output['mirrors'].append({ - 'repo': mirrors.group(1).strip(), - 'host': mirrors.group(2).strip() - }) - - tableHeader = re.search(r'^repo id\s+repo name\s+status', line, re.IGNORECASE) - tableRow = re.search(r'^([^\/]+)\/([^\/]+)\/(.*)\s\s+(\S+.*)\s\s+(\S+.*)$', line) - if tableHeader: - insideTable = True - - elif insideTable and tableRow: - output['repos'].append({ - 'repo': tableRow.group(1).strip(), - 'version': tableRow.group(2).strip(), - 'arch': tableRow.group(3).strip(), - 'repo_name': tableRow.group(4).strip(), - 'status': tableRow.group(5).strip() - }) - - else: - insideTable = False - - if stderr: - for line in stderr.splitlines(): - if re.search(r'http.*error .*', line, re.IGNORECASE): - if not line in output['errors']: - output['errors'].append(line) - - return {'output': output} - -def parser_installed(stdout, stderr): - output = {'packages': [], 'errors': []} - if stdout: - for line in stdout.splitlines(): - package = re.search(r'^([\S\.]+)\.(\S+)\s+(\S+\.\S+)\s+(\S+.*)$', line) - if package: - output['packages'].append({ - 'name': package.group(1).strip(), - 'arch': package.group(2).strip(), - 'version': package.group(3).strip(), - 'status': package.group(4).strip() - }) - - if stderr: - for line in stderr.splitlines(): - if re.search(r'http.*error .*', line, re.IGNORECASE): - if not line in output['errors']: - output['errors'].append(line) - - return {'output': output} - -def register(main): - main['yum_repolist'] = { - 'cmd': 'yum repolist all', - 'description': 'YUM - defined repositories', - 'parser': parser_repolist - } - - main['yum_installed'] = { - 'cmd': 'yum list installed', - 'description': 'YUM - list installed packages', - 'parser': parser_installed - } diff --git a/requirements-test.txt b/requirements-test.txt index e69de29..3fca4d8 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -0,0 +1,29 @@ +coverage +pytest +pytest-aiohttp +pytest-asyncio +pytest-cov +pytest-coverage +pytest-datafiles +autoflake +black +pylint +pylint-exit +bandit +certifi +flake8 +flake8-polyfill +isort +lazy-object-proxy +mccabe +mypy +mypy-extensions +pre-commit +py +pycodestyle +pyflakes +pyparsing +safety +types-requests +typing-extensions +interrogate diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..546845a --- /dev/null +++ b/setup.cfg @@ -0,0 +1,16 @@ +[flake8] +extend-ignore = E203 + +[mypy] +follow_imports = silent +strict_optional = True +warn_redundant_casts = True +warn_unused_ignores = True +disallow_any_generics = True +check_untyped_defs = True +no_implicit_reexport = True +disallow_untyped_defs = True +ignore_missing_imports = True + +[mypy-tests.*] +ignore_errors = True diff --git a/setup.py b/setup.py index 87243fd..e2c5284 100644 --- a/setup.py +++ b/setup.py @@ -3,11 +3,14 @@ import os import re +import sys from distutils.core import setup from pathlib import Path from setuptools import find_packages, setup +sys.path.append("src") + def find_version(fname): """Attempts to find the version number in the file names fname. Raises RuntimeError if not found. @@ -35,7 +38,7 @@ def requirements(fname): # We use the version to construct the DOWNLOAD_URL. -VERSION = find_version("__init__.py") +VERSION = find_version("src/sysinfo/__init__.py") # URL to the repository on Github. REPO_URL = 'https://github.com/peterbay/sysinfo' @@ -46,7 +49,7 @@ def requirements(fname): INSTALL_REQUIRES = requirements("requirements.txt") EXTRAS_REQUIRE = { - "test": requirements("requirements-dev.txt"), + "test": requirements("requirements-test.txt"), } try: @@ -74,7 +77,7 @@ def requirements(fname): 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 2.7', ], - package_dir={"": "."}, + package_dir={"": "src"}, packages=find_packages(), install_requires=INSTALL_REQUIRES, include_package_data=True, diff --git a/__init__.py b/src/sysinfo/__init__.py similarity index 91% rename from __init__.py rename to src/sysinfo/__init__.py index 62dae51..04bdaf8 100644 --- a/__init__.py +++ b/src/sysinfo/__init__.py @@ -26,7 +26,9 @@ __program__ = "sysinfo" __executable__ = "sysinfo" -__description__ = "sysinfo - Python based scripts for obtaining system information from Linux." +__description__ = ( + "sysinfo - Python based scripts for obtaining system information from Linux." +) __author__ = "Petr Vavrin" __email__ = "pvavrin@gmail.com" __version__ = "0.0.1" diff --git a/modules/__init__.py b/src/sysinfo/modules/__init__.py similarity index 100% rename from modules/__init__.py rename to src/sysinfo/modules/__init__.py diff --git a/src/sysinfo/modules/arp.py b/src/sysinfo/modules/arp.py new file mode 100644 index 0000000..4463ec9 --- /dev/null +++ b/src/sysinfo/modules/arp.py @@ -0,0 +1,70 @@ +import re +from sysinfo_lib import parseTable, tableToDict, camelCase + + +def parser(stdout, stderr, to_camelcase): + output = parseTable( + stdout, + header_pattern=r"^(Address\s+)\s(HWtype\s+)\s(HWaddress\s+)\s(Flags Mask\s+)\s(Iface\s*)", + to_camelcase=to_camelcase, + ) + + return {"output": output, "unprocessed": []} + + +def parser_win(stdout, stderr, to_camelcase): + output = [] + unprocessed = [] + interface = None + + if stdout: + for line in stdout.splitlines(): + interface_match = re.search(r"^Interface:\s*(\S+)\s*---\s*(\S+)$", line) + if interface_match: + interface = { + "ip": interface_match.group(1).strip(), + "id": interface_match.group(2).strip(), + "entries": [], + } + output.append(interface) + continue + + if re.match(r"^.*Physical\s*Address", line, re.IGNORECASE): + continue + + entry_match = re.search(r"^\s+(\S+)\s+(\S+)\s+(\S+)", line) + if entry_match and interface: + interface["entries"].append( + { + "intenetAddress": entry_match.group(1).strip(), + "physicalAddress": entry_match.group(2).strip(), + "type": entry_match.group(3).strip(), + } + ) + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "arp", + "system": ["linux"], + "cmd": "arp", + "description": "System ARP cache", + "parser": parser, + } + ) + + main.register( + { + "name": "arp", + "system": ["windows"], + "cmd": "%SystemRoot%\\system32\\arp -a", + "description": "System ARP cache", + "parser": parser_win, + } + ) diff --git a/src/sysinfo/modules/assoc.py b/src/sysinfo/modules/assoc.py new file mode 100644 index 0000000..869ee03 --- /dev/null +++ b/src/sysinfo/modules/assoc.py @@ -0,0 +1,33 @@ +import re +from sysinfo_lib import parseTable, tableToDict, camelCase + + +def parser(stdout, stderr, to_camelcase): + output = {} + unprocessed = [] + + if stdout: + device = "" + for line in stdout.splitlines(): + kv = re.search(r"^([^=]+)=(.*)$", line) + if kv: + key = kv.group(1) + value = kv.group(2) + output[key] = value + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "assoc", + "system": ["windows"], + "cmd": "assoc", + "description": "File associations", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/blkid.py b/src/sysinfo/modules/blkid.py new file mode 100644 index 0000000..d2334af --- /dev/null +++ b/src/sysinfo/modules/blkid.py @@ -0,0 +1,39 @@ +import re +from sysinfo_lib import camelCase + + +def parser(stdout, stderr, to_camelcase): + output = {} + unprocessed = [] + + if stdout: + device = "" + for line in stdout.splitlines(): + dev = re.search(r"^>>> Device: (\S+)", line) + if dev: + device = dev.group(1) + output[device] = {} + continue + + kv = re.search(r"^(\w[^=]+)=(.*)$", line) + if kv: + key = camelCase(kv.group(1), to_camelcase) + value = kv.group(2) + output[device][key] = value + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "blkid", + "system": ["linux"], + "cmd": """blkid -o device | xargs -n 1 -I {} sh -c "echo '>>> Device: {}'; blkid -o export -p {}; blkid -o export -i {}" """, + "description": "Block device attributes", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/blockdev.py b/src/sysinfo/modules/blockdev.py new file mode 100644 index 0000000..bae3b54 --- /dev/null +++ b/src/sysinfo/modules/blockdev.py @@ -0,0 +1,57 @@ +from sysinfo_lib import parseTable, camelCase +import re + + +def parser(stdout, stderr, to_camelcase): + output = {} + if stdout: + output = parseTable(stdout, to_camelcase=to_camelcase) + + return {"output": output, "unprocessed": []} + + +def parser_detail(stdout, stderr, to_camelcase): + output = {} + unprocessed = [] + + if stdout: + device = "" + for line in stdout.splitlines(): + dev = re.search(r"^>>> Device: (\S+)", line) + if dev: + device = dev.group(1) + output[device] = {} + continue + + kv = re.search(r"^get (\w[^:]+): (.*)$", line) + if kv: + key = camelCase(kv.group(1), to_camelcase) + value = kv.group(2) + output[device][key] = value + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "blockdev", + "system": ["linux"], + "cmd": "blockdev --report | column -t", + "description": "Block device ioctls", + "parser": parser, + } + ) + + main.register( + { + "name": "blockdev_detail", + "system": ["linux"], + "cmd": """blkid -o device | xargs -n 1 -I {} sh -c "echo '>>> Device: {}'; blockdev -v --getalignoff --getbsz --getdiscardzeroes --getfra --getiomin --getioopt --getmaxsect --getpbsz --getra --getro --getsize64 --getss {}" """, + "description": "Block device ioctls details", + "parser": parser_detail, + } + ) diff --git a/src/sysinfo/modules/busctl.py b/src/sysinfo/modules/busctl.py new file mode 100644 index 0000000..7d3a107 --- /dev/null +++ b/src/sysinfo/modules/busctl.py @@ -0,0 +1,112 @@ +import re +from sysinfo_lib import parseTable, camelCase + + +def parser(stdout, stderr, to_camelcase): + output = {} + if stdout: + output = parseTable(stdout, to_camelcase=to_camelcase) + + return {"output": output, "unprocessed": []} + + +def parser_tree(stdout, stderr, to_camelcase): + output = {} + unprocessed = [] + + if stdout: + name = "" + for line in stdout.splitlines(): + header = re.search(r"^(\S+) (.+):", line) + + if header: + name = header.group(2) + output[name] = [] + continue + + tree = re.search(r"^\/(.+)$", line) + if tree and name: + output[name].append(tree.group(1)) + continue + + if line == "" or line == "/": + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def parser_status(stdout, stderr, to_camelcase): + output = {} + key = "" + is_array = False + unprocessed = [] + + if stdout: + device = "" + for line in stdout.splitlines(): + dev = re.search(r"^>>> Device: (\S+)", line) + if dev: + device = dev.group(1) + output[device] = {} + key = "" + is_array = False + continue + + kv = re.search(r"^(\w[^=]+)=(.*)$", line) + if kv: + key = camelCase(kv.group(1), to_camelcase) + value = kv.group(2).strip() + + if re.match(r"^cap_", value): + is_array = True + output[device][key] = value.split(r" ") + print(value.split(r" ")) + + else: + output[device][key] = value + + continue + + cont = re.search(r"^\s\s+(.*)$", line) + if cont and key and is_array: + key = camelCase(key, to_camelcase) + output[device][key] += cont.group(1).strip().split(r" ") + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "busctl", + "system": ["linux"], + "cmd": "busctl --no-pager | column -t", + "description": "Introspect the bus", + "parser": parser, + } + ) + + main.register( + { + "name": "busctl_tree", + "system": ["linux"], + "cmd": "busctl --no-pager --list tree", + "description": "Object tree for services", + "parser": parser_tree, + } + ) + + main.register( + { + "name": "busctl_status", + "system": ["linux"], + "cmd": """busctl list | awk '!/^(NAME)/ {print $1}' | xargs -n 1 -I {} sh -c "echo '>>> Device: {}'; busctl --no-pager status {}" """, + "description": "Process information and credentials of a bus service", + "parser": parser_status, + } + ) diff --git a/src/sysinfo/modules/chage.py b/src/sysinfo/modules/chage.py new file mode 100644 index 0000000..d766867 --- /dev/null +++ b/src/sysinfo/modules/chage.py @@ -0,0 +1,40 @@ +import re +from sysinfo_lib import camelCase + + +def parser(stdout, stderr, to_camelcase): + output = {} + unprocessed = [] + user = None + + if stdout: + for line in stdout.splitlines(): + user_match = re.search(r"^>>> User:\s+(.*)$", line) + if user_match: + user = user_match.group(1) + output[user] = {"name": user} + continue + + kv = re.search(r"^([^:]+):\s*(.*)$", line) + if user and kv: + key = camelCase(kv.group(1).strip(), to_camelcase) + value = kv.group(2).strip() + + output[user][key] = value + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "chage", + "system": ["linux"], + "cmd": """cat /etc/passwd | awk '{split($0,a,":"); print a[1]}' | xargs -I {} sh -c "echo '>>> User: {}'; chage -l {}" """, + "description": "Users password expiration information", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/chrt.py b/src/sysinfo/modules/chrt.py new file mode 100644 index 0000000..b27e22e --- /dev/null +++ b/src/sysinfo/modules/chrt.py @@ -0,0 +1,56 @@ +import re +from sysinfo_lib import camelCase + + +def parser(stdout, stderr, to_camelcase): + output = {} + pid = None + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + + pidSearch = re.search(r"^>>>\s+PID:\s+(\S+)", line) + if pidSearch: + pid = pidSearch.group(1) + output[pid] = {"pid": pid, "scheduling": {}, "current": {}} + continue + + if pid: + sched = re.search(r"^SCHED_(\S+)[^:]+:\s*(\S+)", line) + if sched: + key = camelCase(sched.group(1).strip(), to_camelcase) + value = sched.group(2).strip() + + output[pid]["scheduling"][key] = value + continue + + current = re.search(r"current scheduling (\S+).*:\s*(\S+)", line) + if current: + key = camelCase(current.group(1).strip(), to_camelcase) + value = current.group(2).strip() + + if re.match(r"^SCHED_", value): + value = camelCase(value.replace("SCHED_", ""), to_camelcase) + + output[pid]["current"][key] = value + continue + + if line.strip("-") == "": + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "chrt", + "system": ["linux"], + "cmd": """ps -eo pid | grep -vi pid | xargs -I {} sh -c "echo '>>> PID: {}'; chrt -a --pid {}; echo '----'; chrt -m --pid {};" """, + "description": "Scheduling attributes of all the tasks (threads)", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/compgen.py b/src/sysinfo/modules/compgen.py new file mode 100644 index 0000000..89454f2 --- /dev/null +++ b/src/sysinfo/modules/compgen.py @@ -0,0 +1,101 @@ +from sysinfo_lib import sortedList + + +def parser(stdout, stderr, to_camelcase): + if stdout: + return {"output": sortedList(stdout), "unprocessed": []} + + else: + return {} + + +def register(main): + main.register( + { + "name": "shell_alias", + "system": ["linux"], + "cmd": '$(which bash) -c "compgen -a"', + "description": "Shell alias names (compgen)", + "parser": parser, + } + ) + + main.register( + { + "name": "shell_builtins", + "system": ["linux"], + "cmd": '$(which bash) -c "compgen -b"', + "description": "Names of shell builtin commands (compgen)", + "parser": parser, + } + ) + + main.register( + { + "name": "shell_all_commands", + "system": ["linux"], + "cmd": '$(which bash) -c "compgen -c"', + "description": "Shell command names (compgen)", + "parser": parser, + } + ) + + main.register( + { + "name": "shell_exported_variables", + "system": ["linux"], + "cmd": '$(which bash) -c "compgen -e"', + "description": "Names of exported shell variables (compgen)", + "parser": parser, + } + ) + + main.register( + { + "name": "shell_variables", + "system": ["linux"], + "cmd": '$(which bash) -c "compgen -v"', + "description": "Names of all shell variables (compgen)", + "parser": parser, + } + ) + + main.register( + { + "name": "groups", + "system": ["linux"], + "cmd": '$(which bash) -c "compgen -g"', + "description": "Group names (compgen)", + "parser": parser, + } + ) + + main.register( + { + "name": "jobs", + "system": ["linux"], + "cmd": '$(which bash) -c "compgen -j"', + "description": "Job names, if job control is active (compgen)", + "parser": parser, + } + ) + + main.register( + { + "name": "services", + "system": ["linux"], + "cmd": '$(which bash) -c "compgen -s"', + "description": "Service names (compgen)", + "parser": parser, + } + ) + + main.register( + { + "name": "users", + "system": ["linux"], + "cmd": '$(which bash) -c "compgen -u"', + "description": "User names (compgen)", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/dev_disk.py b/src/sysinfo/modules/dev_disk.py new file mode 100644 index 0000000..9d16c4f --- /dev/null +++ b/src/sysinfo/modules/dev_disk.py @@ -0,0 +1,46 @@ +import re + + +def parser(stdout, stderr, to_camelcase): + output = {"all": {}} + unprocessed = [] + + if stdout: + typeName = "" + for line in stdout.splitlines(): + matchType = re.search(r"^\/dev\/disk\/by-(.*):\s*$", line) + if matchType: + typeName = matchType.group(1) + output[typeName] = {} + continue + + matchEntry = re.search(r"\s(\S+)\s+->\s+[\.\/]+(.*)$", line) + if matchEntry and typeName: + key = matchEntry.group(1).strip() + value = matchEntry.group(2).strip() + output[typeName][key] = value + + if not value in output["all"]: + output["all"][value] = {} + + output["all"][value][typeName] = key + continue + + if line == "" or re.match(r"^total", line): + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "dev_disk", + "system": ["linux"], + "cmd": "ls -l /dev/disk/by-*", + "description": "Disk devices mapping", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/dev_input.py b/src/sysinfo/modules/dev_input.py new file mode 100644 index 0000000..8d37464 --- /dev/null +++ b/src/sysinfo/modules/dev_input.py @@ -0,0 +1,46 @@ +import re + + +def parser(stdout, stderr, to_camelcase): + output = {"all": {}} + unprocessed = [] + + if stdout: + typeName = "" + for line in stdout.splitlines(): + matchType = re.search(r"^\/dev\/input\/by-(.*):\s*$", line) + if matchType: + typeName = matchType.group(1) + output[typeName] = {} + continue + + matchEntry = re.search(r"\s(\S+)\s+->\s+[\.\/]+(.*)$", line) + if matchEntry and typeName: + key = matchEntry.group(1).strip() + value = matchEntry.group(2).strip() + output[typeName][key] = value + + if not value in output["all"]: + output["all"][value] = {} + + output["all"][value][typeName] = key + continue + + if line == "" or re.match(r"^total", line): + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "dev_input", + "system": ["linux"], + "cmd": "ls -l /dev/input/by-*", + "description": "Input devices mapping", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/df.py b/src/sysinfo/modules/df.py new file mode 100644 index 0000000..800eeb5 --- /dev/null +++ b/src/sysinfo/modules/df.py @@ -0,0 +1,49 @@ +import re + + +def parser(stdout, stderr, to_camelcase): + output = {} + unprocessed = [] + + if stdout: + reSplit = re.compile(r"\s+") + for line in stdout.splitlines(): + cols = reSplit.split(line) + if len(cols) > 11: + if cols[11].lower() == "mounted": + continue + + output[cols[11]] = { + "source": cols[0], + "fstype": cols[1], + "itotal": cols[2], + "iused": cols[3], + "iavail": cols[4], + "ipcent": cols[5], + "size": cols[6], + "used": cols[7], + "avail": cols[8], + "pcent": cols[9], + "file": cols[10], + "target": cols[11], + } + continue + + if re.match(r"Filesystem", line): + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "df", + "system": ["linux"], + "cmd": "df -a --output=source,fstype,itotal,iused,iavail,ipcent,size,used,avail,pcent,file,target", + "description": "Report file system disk space usage", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/dmidecode.py b/src/sysinfo/modules/dmidecode.py new file mode 100644 index 0000000..fc78adb --- /dev/null +++ b/src/sysinfo/modules/dmidecode.py @@ -0,0 +1,207 @@ +import re +from sysinfo_lib import camelCase + + +def parser(stdout, stderr, to_camelcase): + dmiSections = { + "0": "BIOS", + "1": "System", + "2": "Base Board", + "3": "Chassis", + "4": "Processor", + "5": "Memory Controller", + "6": "Memory Module", + "7": "Cache", + "8": "Port Connector", + "9": "System Slots", + "10": "On Board Devices", + "11": "OEM Strings", + "12": "System Configuration Options", + "13": "BIOS Language", + "14": "Group Associations", + "15": "System Event Log", + "16": "Physical Memory Array", + "17": "Memory Device", + "18": "32-bit Memory Error", + "19": "Memory Array Mapped Address", + "20": "Memory Device Mapped Address", + "21": "Built-in Pointing Device", + "22": "Portable Battery", + "23": "System Reset", + "24": "Hardware Security", + "25": "System Power Controls", + "26": "Voltage Probe", + "27": "Cooling Device", + "28": "Temperature Probe", + "29": "Electrical Current Probe", + "30": "Out-of-band Remote Access", + "31": "Boot Integrity Services", + "32": "System Boot", + "33": "64-bit Memory Error", + "34": "Management Device", + "35": "Management Device Component", + "36": "Management Device Threshold Data", + "37": "Memory Channel", + "38": "IPMI Device", + "39": "Power Supply", + "40": "Additional Information", + "41": "Onboard Devices Extended Information", + "42": "Management Controller Host Interface", + "126": "Disabled entry", + "127": "End of table", + } + handle = None + output = {} + unprocessed = [] + + if stdout: + + # fix multiline + stdout = re.sub(r"\n\t\t", " ", stdout) + + for line in stdout.splitlines(): + if line.strip() == "": + handle = None + + handleSearch = re.search( + r"^Handle\s+([^,]+),\s+DMI type\s+([^,]+),", line, re.IGNORECASE + ) + if handleSearch: + handle = handleSearch.group(1) + dmiType = handleSearch.group(2) + intDmiType = int(dmiType) + + if intDmiType > 127 and intDmiType < 256: + output[handle] = {"__dmiType": dmiType, "__section": "OEM Specific"} + continue + + if dmiType in dmiSections: + output[handle] = { + "__dmiType": dmiType, + "__section": dmiSections[dmiType], + } + continue + + output[handle] = {"__dmiType": dmiType, "__section": "Unknown"} + continue + + if handle and re.match(r"^\S", line): + output[handle]["__type"] = line + continue + + entry = re.search(r"^\s+([^:]+):\s+(.*)$", line) + if handle and entry: + key = camelCase(entry.group(1), to_camelcase) + value = entry.group(2).strip() + + output[handle][key] = value + continue + + if line == "": + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "dmidecode", + "system": ["linux"], + "cmd": "dmidecode", + "description": "Dumping all information from DMI (SMBIOS)", + "parser": parser, + } + ) + + main.register( + { + "name": "dmidecode_bios", + "system": ["linux"], + "cmd": "dmidecode -t bios", + "description": "Dumping BIOS information from DMI (SMBIOS)", + "parser": parser, + } + ) + + main.register( + { + "name": "dmidecode_system", + "system": ["linux"], + "cmd": "dmidecode -t system", + "description": "Dumping SYSTEM information from DMI (SMBIOS)", + "parser": parser, + } + ) + + main.register( + { + "name": "dmidecode_baseboard", + "system": ["linux"], + "cmd": "dmidecode -t baseboard", + "description": "Dumping BASEBOARD information from DMI (SMBIOS)", + "parser": parser, + } + ) + + main.register( + { + "name": "dmidecode_chassis", + "system": ["linux"], + "cmd": "dmidecode -t chassis", + "description": "Dumping CHASSIS information from DMI (SMBIOS)", + "parser": parser, + } + ) + + main.register( + { + "name": "dmidecode_processor", + "system": ["linux"], + "cmd": "dmidecode -t processor", + "description": "Dumping CHASSIS information from DMI (SMBIOS)", + "parser": parser, + } + ) + + main.register( + { + "name": "dmidecode_memory", + "system": ["linux"], + "cmd": "dmidecode -t memory", + "description": "Dumping MEMORY information from DMI (SMBIOS)", + "parser": parser, + } + ) + + main.register( + { + "name": "dmidecode_cache", + "system": ["linux"], + "cmd": "dmidecode -t cache", + "description": "Dumping CACHE information from DMI (SMBIOS)", + "parser": parser, + } + ) + + main.register( + { + "name": "dmidecode_connector", + "system": ["linux"], + "cmd": "dmidecode -t connector", + "description": "Dumping CONNECTOR information from DMI (SMBIOS)", + "parser": parser, + } + ) + + main.register( + { + "name": "dmidecode_slot", + "system": ["linux"], + "cmd": "dmidecode -t slot", + "description": "Dumping SLOT information from DMI (SMBIOS)", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/dnf.py b/src/sysinfo/modules/dnf.py new file mode 100644 index 0000000..fb71b2c --- /dev/null +++ b/src/sysinfo/modules/dnf.py @@ -0,0 +1,89 @@ +import re + + +def parser_repolist(stdout, stderr, to_camelcase): + output = {"repos": [], "errors": []} + + insideTable = False + col1 = None + col2 = None + if stdout: + for line in stdout.splitlines(): + tableHeader = re.search( + r"^(repo id\s+)(repo name\s+)status", line, re.IGNORECASE + ) + + if col1 and col2: + tableRow = re.search(r"^(.{%s})(.{%s})(.*)$" % (col1, col2), line) + + if insideTable and tableRow: + output["repos"].append( + { + "repo": tableRow.group(1).strip(), + "repo_name": tableRow.group(2).strip(), + "status": tableRow.group(3).strip(), + } + ) + continue + + if tableHeader: + insideTable = True + col1 = len(tableHeader.group(1)) + col2 = len(tableHeader.group(2)) + + else: + insideTable = False + + if stderr: + for line in stderr.splitlines(): + if re.search(r"http.*error .*", line, re.IGNORECASE): + if not line in output["errors"]: + output["errors"].append(line) + + return {"output": output} + + +def parser_installed(stdout, stderr, to_camelcase): + output = {"packages": [], "errors": []} + if stdout: + for line in stdout.splitlines(): + package = re.search(r"^([\S\.]+)\.(\S+)\s+(\S+\.\S+)\s+(\S+.*)$", line) + if package: + output["packages"].append( + { + "name": package.group(1).strip(), + "arch": package.group(2).strip(), + "version": package.group(3).strip(), + "status": package.group(4).strip(), + } + ) + + if stderr: + for line in stderr.splitlines(): + if re.search(r"http.*error .*", line, re.IGNORECASE): + if not line in output["errors"]: + output["errors"].append(line) + + return {"output": output} + + +def register(main): + main.register( + { + "name": "dnf_repolist", + "system": ["linux"], + "cmd": "dnf repolist --all", + "description": "DNF - defined repositories", + "parser": parser_repolist, + } + ) + + main.register( + { + "name": "dnf_installed", + "system": ["linux"], + "cmd": "dnf list installed", + "description": "DNF - list installed packages", + "parser": parser_installed, + } + ) diff --git a/src/sysinfo/modules/driverquery.py b/src/sysinfo/modules/driverquery.py new file mode 100644 index 0000000..e6dc62f --- /dev/null +++ b/src/sysinfo/modules/driverquery.py @@ -0,0 +1,59 @@ +import re +from sysinfo_lib import camelCase, fixMultilineAndSplit + + +def parser_fo(stdout, stderr, to_camelcase): + output = [] + unprocessed = [] + image = {} + + if stdout: + data = fixMultilineAndSplit(stdout, r"^\s+", ", ") + + for line in data: + if line.strip() == "": + continue + + kv = re.search(r"^([^:]+):\s*(.*)$", line) + if kv: + key = camelCase(kv.group(1).strip(), to_camelcase) + value = kv.group(2).strip().replace("\u00a0", "") + + if ( + key == "moduleName" + or key == "Module Name" + or key == "deviceName" + or key == "DeviceName" + ): + image = {} + output.append(image) + + image[key] = value + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + + main.register( + { + "name": "driverquery", + "system": ["windows"], + "cmd": "%SystemRoot%\\system32\\driverquery.exe /v /fo list", + "description": "List of installed device drivers.", + "parser": parser_fo, + } + ) + + main.register( + { + "name": "driverquery_signed", + "system": ["windows"], + "cmd": "%SystemRoot%\\system32\\driverquery.exe /si /fo list", + "description": "List of installed device signed drivers.", + "parser": parser_fo, + } + ) diff --git a/src/sysinfo/modules/env.py b/src/sysinfo/modules/env.py new file mode 100644 index 0000000..55f17f8 --- /dev/null +++ b/src/sysinfo/modules/env.py @@ -0,0 +1,29 @@ +import re + + +def parser(stdout, stderr, to_camelcase): + output = {} + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + lineMatch = re.search(r"^([^=]+)=(.*)$", line) + if lineMatch: + output[lineMatch.group(1)] = lineMatch.group(2) + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "env", + "system": ["linux"], + "cmd": "env", + "description": "Environment variables", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/etc_default.py b/src/sysinfo/modules/etc_default.py new file mode 100644 index 0000000..0f28803 --- /dev/null +++ b/src/sysinfo/modules/etc_default.py @@ -0,0 +1,58 @@ +import re + + +def setPathValue(data, path, value): + pathRest = None + pathParts = re.search(r"^([^\/]+)\/?(.*)$", path) + if pathParts: + path = pathParts.group(1) + pathRest = pathParts.group(2) + + if not path in data: + data[path] = {} + + if pathRest: + setPathValue(data[path], pathRest, value) + + else: + if isinstance(data[path], dict): + data[path] = [] + data[path].append(value) + + +def parser(stdout, stderr, to_camelcase): + output = {} + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + pathValue = re.search(r"^\/etc\/default\/([^:]+):(.*)$", line) + if pathValue: + path = pathValue.group(1) + value = pathValue.group(2) + if value.strip() == "": + continue + + if not re.search(r"^\s*#", value): + setPathValue(output, path, value) + continue + + if re.match(r"^\s*#", value): + # ignore line with comment + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "etc_default", + "system": ["linux"], + "cmd": 'find /etc/default -type f -follow -print | xargs grep ""', + "description": "Default configuration for programs", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/etc_fstab.py b/src/sysinfo/modules/etc_fstab.py new file mode 100644 index 0000000..a7fbde0 --- /dev/null +++ b/src/sysinfo/modules/etc_fstab.py @@ -0,0 +1,39 @@ +import re + + +def parser(stdout, stderr, to_camelcase): + output = {} + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + if re.match(r"^\s*#", line): + continue + + lineMatch = re.split(r"\s+", line) + if lineMatch and len(lineMatch) > 5: + output[lineMatch[0]] = { + "location": lineMatch[0], + "mountPoint": lineMatch[1], + "type": lineMatch[2], + "security": lineMatch[3], + "dump": lineMatch[4], + "fsckOrder": lineMatch[5], + } + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "etc_fstab", + "system": ["linux"], + "cmd": "cat /etc/fstab", + "description": "Filesystems mounted on boot", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/etc_group.py b/src/sysinfo/modules/etc_group.py new file mode 100644 index 0000000..0f4e3e4 --- /dev/null +++ b/src/sysinfo/modules/etc_group.py @@ -0,0 +1,21 @@ +from sysinfo_lib import parseCharDelimitedTable, tableToDict + + +def parser(stdout, stderr, to_camelcase): + columnsNames = ["groupName", "password", "gid", "groupList"] + output = parseCharDelimitedTable(stdout, ":", columnsNames) + output = tableToDict(output, "groupName") + + return {"output": output, "unprocessed": []} + + +def register(main): + main.register( + { + "name": "etc_group", + "system": ["linux"], + "cmd": "cat /etc/group", + "description": "Groups essential information", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/etc_hosts.py b/src/sysinfo/modules/etc_hosts.py new file mode 100644 index 0000000..1c07c48 --- /dev/null +++ b/src/sysinfo/modules/etc_hosts.py @@ -0,0 +1,74 @@ +import re + + +def parser(stdout, stderr, to_camelcase): + output = {} + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + values = re.search(r"^\/etc\/([^\:]+):\s*(.*)$", line) + if values: + group = values.group(1) + value = values.group(2).strip() + + if value == "": + continue + + if re.match(r"\s*#", value): + # ignore line with comment + continue + + if group not in output: + output[group] = [] + + if group in ["hosts"]: + parts = re.search(r"^(\S+)\s+([^#]+)#?(.*)$", value) + if parts: + ip = parts.group(1).strip() + hostnames = parts.group(2).strip() + comment = parts.group(3).strip() + + output[group].append( + { + "ip": ip, + "hostnames": re.split(r"\s+", hostnames), + "comment": comment, + } + ) + continue + + if group in ["hosts.allow", "hosts.deny"]: + print("value", value) + parts = re.search(r"^([^:]+):\s*([^:]+):?([^#]+)#?(.*)$", value) + if parts: + daemon_list = parts.group(1).strip().split(",") + client_list = parts.group(2).strip().split(",") + command = parts.group(3).strip() + comment = parts.group(4).strip() + + output[group].append( + { + "daemonList": daemon_list, + "clientList": client_list, + "command": command, + "comment": comment, + } + ) + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "etc_hosts", + "system": ["linux"], + "cmd": """grep "" /etc/hosts*""", + "description": "Maps hostnames to IP addresses", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/etc_locale_gen.py b/src/sysinfo/modules/etc_locale_gen.py new file mode 100644 index 0000000..6792e8f --- /dev/null +++ b/src/sysinfo/modules/etc_locale_gen.py @@ -0,0 +1,30 @@ +import re + + +def parser(stdout, stderr, to_camelcase): + output = [] + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + values = re.search(r"^\s*([^#]+)", line) + if not values: + continue + + value = values.group(1).strip() + if value != "": + output.append(value) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "etc_locale_gen", + "system": ["linux"], + "cmd": "cat /etc/locale.gen", + "description": "Configuration file for locale-gen", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/etc_mtab.py b/src/sysinfo/modules/etc_mtab.py new file mode 100644 index 0000000..f08ed57 --- /dev/null +++ b/src/sysinfo/modules/etc_mtab.py @@ -0,0 +1,50 @@ +import re + + +def parse_mount_options(value): + output = {} + for option in re.split(r"\s*,\s*", value): + dir = re.search(r"^([^=]+)=(.*)$", option) + + if dir: + output[dir.group(1)] = dir.group(2).split(":") + + else: + output[option] = True + + return output + + +def parser(stdout, stderr, to_camelcase): + output = {} + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + values = re.split(r"\s+", line) + if len(values) > 5: + output[values[1]] = { + "partition": values[0], + "mountPoint": values[1], + "fileSystem": values[2], + "mountOptions": parse_mount_options(values[3]), + "dump": values[4], + "fsckOrder": values[5], + } + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "etc_mtab", + "system": ["linux"], + "cmd": "cat /etc/mtab", + "description": "Currently mounted filesystems", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/etc_passwd.py b/src/sysinfo/modules/etc_passwd.py new file mode 100644 index 0000000..e053172 --- /dev/null +++ b/src/sysinfo/modules/etc_passwd.py @@ -0,0 +1,20 @@ +from sysinfo_lib import parseCharDelimitedTable, tableToDict + + +def parser(stdout, stderr, to_camelcase): + columnsNames = ["username", "password", "uid", "gid", "idInfo", "homeDir", "shell"] + output = parseCharDelimitedTable(stdout, ":", columnsNames) + output = tableToDict(output, "username") + return {"output": output, "unprocessed": []} + + +def register(main): + main.register( + { + "name": "etc_passwd", + "system": ["linux"], + "cmd": "cat /etc/passwd", + "description": "Attributes of each user or account on a computer", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/etc_release.py b/src/sysinfo/modules/etc_release.py new file mode 100644 index 0000000..91cb598 --- /dev/null +++ b/src/sysinfo/modules/etc_release.py @@ -0,0 +1,32 @@ +import re +from sysinfo_lib import camelCase + + +def parser(stdout, stderr, to_camelcase): + output = {} + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + values = re.search(r"^([^=]+)=(.*)$", line) + if values: + key = camelCase(values.group(1), to_camelcase) + value = values.group(2).strip().strip('"') + output[key] = value + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "etc_release", + "system": ["linux"], + "cmd": "cat /etc/*release", + "description": "OS release info", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/etc_shadow.py b/src/sysinfo/modules/etc_shadow.py new file mode 100644 index 0000000..fd7ba57 --- /dev/null +++ b/src/sysinfo/modules/etc_shadow.py @@ -0,0 +1,30 @@ +from sysinfo_lib import parseCharDelimitedTable, tableToDict + + +def parser(stdout, stderr, to_camelcase): + columnsNames = [ + "username", + "password", + "lastPasswordChange", + "minimum", + "maximum", + "warn", + "inactive", + "expire", + ] + output = parseCharDelimitedTable(stdout, ":", columnsNames) + output = tableToDict(output, "username") + + return {"output": output, "unprocessed": []} + + +def register(main): + main.register( + { + "name": "etc_shadow", + "system": ["linux"], + "cmd": "cat /etc/shadow", + "description": "Shadow database of the passwd file", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/etc_timezone.py b/src/sysinfo/modules/etc_timezone.py new file mode 100644 index 0000000..49f009c --- /dev/null +++ b/src/sysinfo/modules/etc_timezone.py @@ -0,0 +1,19 @@ +def parser(stdout, stderr, to_camelcase): + output = "" + + if stdout: + output = stdout.strip() + + return {"output": output, "unprocessed": []} + + +def register(main): + main.register( + { + "name": "etc_timezone", + "system": ["linux"], + "cmd": "cat /etc/timezone", + "description": "Timezone settings", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/ethtool.py b/src/sysinfo/modules/ethtool.py new file mode 100644 index 0000000..a795321 --- /dev/null +++ b/src/sysinfo/modules/ethtool.py @@ -0,0 +1,40 @@ +import re +from sysinfo_lib import camelCase + + +def parser(stdout, stderr, to_camelcase): + output = {} + unprocessed = [] + user = None + + if stdout: + for line in stdout.splitlines(): + user_match = re.search(r"^>>> User:\s+(.*)$", line) + if user_match: + user = user_match.group(1) + output[user] = {"name": user} + continue + + kv = re.search(r"^([^:]+):\s*(.*)$", line) + if user and kv: + key = camelCase(kv.group(1).strip(), to_camelcase) + value = kv.group(2).strip() + + output[user][key] = value + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "chage", + "system": ["linux"], + "cmd": """ifconfig -a -s | grep -v "Iface" | awk '{split($0,a," "); print a[1]}' | xargs -I {} sh -c "echo '>>> Device: {}'; ethtool -i {}" """, + "description": "Users password expiration information", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/fbset.py b/src/sysinfo/modules/fbset.py new file mode 100644 index 0000000..9f44b7a --- /dev/null +++ b/src/sysinfo/modules/fbset.py @@ -0,0 +1,127 @@ +import re + + +def parser(stdout, stderr, to_camelcase): + output = {} + modeName = "" + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + mode = re.search(r'^\s*mode "([^"]+)\s*"$', line) + if mode: + modeName = mode.group(1) + output[modeName] = {} + continue + + endmode = re.search(r"^\s*endmode", line) + if endmode: + modeName = "" + continue + + if modeName: + geometry = re.search( + r"^\s+geometry\s*(\d+)\s*(\d+)\s*(\d+)\s*(\d+)\s*(\d+)\s*$", line + ) + if geometry: + output[modeName]["geometry"] = { + "xres": geometry.group(1), + "yres": geometry.group(2), + "xresVirtual": geometry.group(3), + "yresVirtual": geometry.group(4), + "bitsPerPixel": geometry.group(5), + } + continue + + timings = re.search( + r"^\s+timings\s*(\d+)\s*(\d+)\s*(\d+)\s*(\d+)\s*(\d+)\s*(\d+)\s*(\d+)\s*$", + line, + ) + if timings: + output[modeName]["timings"] = { + "pixclock": timings.group(1), + "leftMargin": timings.group(2), + "rightMargin": timings.group(3), + "upperMargin": timings.group(4), + "lowerMargin": timings.group(5), + "hsyncLen": timings.group(6), + "vsyncLen": timings.group(7), + } + continue + + rgba = re.search( + r"^\s+rgba\s*(\d+)\/(\d+),(\d+)\/(\d+),(\d+)\/(\d+),(\d+)\/(\d+)\s*$", + line, + ) + if rgba: + output[modeName]["rgba"] = { + "redLength": rgba.group(1), + "redOffset": rgba.group(2), + "greenLength": rgba.group(3), + "greenOffset": rgba.group(4), + "blueLength": rgba.group(5), + "blueOffset": rgba.group(6), + "transpLength": rgba.group(7), + "transpOffset": rgba.group(8), + } + continue + + state = re.search( + r"^\s+(interlaced|double|vsync|hsync|csync|extsync)\s+(\S+)\s*$", + line, + ) + if state: + output[modeName][state.group(1)] = state.group(2) + continue + + kv = re.search(r"^\s+(\S+)\s+(.*)$", line) + if kv: + output[modeName][kv.group(1)] = kv.group(2) + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def parserInfo(stdout, stderr, to_camelcase): + output = parser(stdout, stderr, to_camelcase) + output["output"]["info"] = {} + + informationBlock = False + if stdout: + for line in stdout.splitlines(): + info = re.search(r"^\s*Frame buffer device information:\s*$", line) + if info: + informationBlock = True + continue + + if informationBlock: + keyValue = re.search(r"^\s+(\S+)\s*:\s+(.*)\s*$", line) + if keyValue: + output["output"]["info"][keyValue.group(1)] = keyValue.group(2) + continue + + return output + + +def register(main): + main.register( + { + "name": "fbset", + "system": ["linux"], + "cmd": "fbset -a", + "description": "Show frame buffer device settings", + "parser": parser, + } + ) + + main.register( + { + "name": "fbset_info", + "system": ["linux"], + "cmd": "fbset -i", + "description": "Show frame buffer device information", + "parser": parserInfo, + } + ) diff --git a/src/sysinfo/modules/findmnt.py b/src/sysinfo/modules/findmnt.py new file mode 100644 index 0000000..66790ae --- /dev/null +++ b/src/sysinfo/modules/findmnt.py @@ -0,0 +1,40 @@ +from sysinfo_lib import parseTable +import re + + +def parse_mount_options(value): + output = {} + for option in re.split(r"\s*,\s*", value): + dir = re.search(r"^([^=]+)=(.*)$", option) + + if dir: + output[dir.group(1)] = dir.group(2).split(":") + + else: + output[option] = True + + return output + + +def parser(stdout, stderr, to_camelcase): + output = {} + if stdout: + output = parseTable(stdout, to_camelcase=to_camelcase) + + for entry in output: + if "options" in entry: + entry["options"] = parse_mount_options(entry["options"]) + + return {"output": output, "unprocessed": []} + + +def register(main): + main.register( + { + "name": "findmnt", + "system": ["linux"], + "cmd": "findmnt -Al | column -t", + "description": "List all mounted filesytems", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/free.py b/src/sysinfo/modules/free.py new file mode 100644 index 0000000..684f284 --- /dev/null +++ b/src/sysinfo/modules/free.py @@ -0,0 +1,43 @@ +import re +from sysinfo_lib import camelCase + + +def parser(stdout, stderr, to_camelcase): + output = {} + columns = None + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + if re.search(r"total\s+used", line, re.IGNORECASE): + columns = re.split(r"\s+", line.strip()) + continue + + entrySearch = re.search(r"^([^:]+):\s+(.*)$", line) + if columns and entrySearch: + type = camelCase(entrySearch.group(1), to_camelcase) + output[type] = {} + for idx, value in enumerate( + re.split(r"\s+", entrySearch.group(2).strip()) + ): + if idx < len(columns): + key = camelCase(columns[idx], to_camelcase) + output[type][key] = value + + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "free", + "system": ["linux"], + "cmd": "free -b -l -w", + "description": "Amount of free and used memory in the system", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/getconf.py b/src/sysinfo/modules/getconf.py new file mode 100644 index 0000000..2c2c00a --- /dev/null +++ b/src/sysinfo/modules/getconf.py @@ -0,0 +1,33 @@ +import re +from sysinfo_lib import camelCase + + +def parser(stdout, stderr, to_camelcase): + output = {} + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + kv = re.search(r"^(\S+)\s*(.*)$", line) + if kv: + key = camelCase(kv.group(1), to_camelcase) + value = kv.group(2).strip() + + output[key] = value + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "getconf", + "system": ["linux"], + "cmd": "getconf -a", + "description": "Configuration variables for the current system and their values", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/hostnamectl.py b/src/sysinfo/modules/hostnamectl.py new file mode 100644 index 0000000..fb95070 --- /dev/null +++ b/src/sysinfo/modules/hostnamectl.py @@ -0,0 +1,33 @@ +import re +from sysinfo_lib import camelCase + + +def parser(stdout, stderr, to_camelcase): + output = {} + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + kv = re.search(r"^([^:]+):\s*(.*)", line) + if kv: + key = camelCase(kv.group(1), to_camelcase) + value = kv.group(2) + + output[key] = value + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "hostnamectl", + "system": ["linux"], + "cmd": "hostnamectl status", + "description": "Current system hostname and related information", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/ifconfig.py b/src/sysinfo/modules/ifconfig.py new file mode 100644 index 0000000..7f4d9ea --- /dev/null +++ b/src/sysinfo/modules/ifconfig.py @@ -0,0 +1,90 @@ +import re +from sysinfo_lib import camelCase + + +def extractValue(data, line, key, regExp): + search = re.search(regExp, line, re.IGNORECASE) + if search: + data[key] = search.group(1) + return data + + +def extractValues(data, line, to_camelcase): + for pair in re.split(r"\s\s+", line): + desc = re.search(r"^(.*)\((.*)\)$", pair.strip()) + if desc: + data["description"] = desc.group(2) + if desc.group(1): + pair = desc.group(1).strip() + else: + continue + + kv = re.search(r"^([^\s]+)\s(\S.*)$", pair) + if kv: + key = camelCase(kv.group(1), to_camelcase) + value = kv.group(2).strip() + valueFix = re.search(r"^(\S+)\s+(\S+)\s(\S+)$", value) + + if valueFix: + data[key] = valueFix.group(1) + data[valueFix.group(2)] = valueFix.group(3) + else: + data[key] = value + + return extractValues + + +def parser(stdout, stderr, to_camelcase): + output = {} + blockData = {} + unprocessed = [] + + if stdout: + for block in re.split(r"\r\r|\n\n|\r\n\r\n", stdout): + blockData = {"entries": [], "rx": {}, "tx": {}} + + for line in block.splitlines(): + header = re.search(r"^(\S[^:]+):\s*(.*)$", line) + if header: + name = header.group(1) + blockData["name"] = name + extractValue(blockData, line, "flags", r"flags=(\S+)") + extractValues(blockData, header.group(2), to_camelcase) + output[name] = blockData + continue + + rxTx = re.search(r"^\s+([rt]x)\s+(.*)$", line, re.IGNORECASE) + if rxTx: + type = rxTx.group(1).lower() + extractValues(blockData[type], rxTx.group(2), to_camelcase) + continue + + sub = re.search(r"^\s+(\S+)\s\s(.*)$", line, re.IGNORECASE) + if sub: + subData = {"type": sub.group(1)} + extractValues(subData, sub.group(2), to_camelcase) + blockData["entries"].append(subData) + continue + + sub = re.search(r"^\s+(\S+)\s(\S+)\s\s(.*)$", line, re.IGNORECASE) + if sub: + subData = {"type": sub.group(1), "value": sub.group(2)} + extractValues(subData, sub.group(3), to_camelcase) + blockData["entries"].append(subData) + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "ifconfig", + "system": ["linux"], + "cmd": "ifconfig -a -v", + "description": "List all interfaces which are currently available, even if down", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/lsblk.py b/src/sysinfo/modules/lsblk.py new file mode 100644 index 0000000..ab07659 --- /dev/null +++ b/src/sysinfo/modules/lsblk.py @@ -0,0 +1,42 @@ +import re +from sysinfo_lib import camelCase + + +def parser(stdout, stderr, to_camelcase): + output = {} + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + entry = {} + kv = re.findall(r'(\S[^=]+)=\"([^"]*)\"', line) + if kv: + for pair in kv: + key = camelCase(pair[0], to_camelcase) + value = pair[1].strip() + + entry[key] = value + + if "name" in entry: + output[entry["name"]] = entry + continue + + elif "NAME" in entry: + output[entry["NAME"]] = entry + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "lsblk", + "system": ["linux"], + "cmd": "lsblk -P -o NAME,KNAME,MAJ:MIN,FSTYPE,MOUNTPOINT,LABEL,UUID,PARTLABEL,PARTUUID,RA,RO,RM,MODEL,SERIAL,SIZE,STATE,OWNER,GROUP,MODE,ALIGNMENT,MIN-IO,OPT-IO,PHY-SEC,LOG-SEC,ROTA,SCHED,RQ-SIZE,TYPE,DISC-ALN,DISC-GRAN,DISC-MAX,DISC-ZERO,WSAME,WWN,RAND,PKNAME,HCTL,TRAN,REV,VENDOR", + "description": "Lists information about all block devices", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/lscpu.py b/src/sysinfo/modules/lscpu.py new file mode 100644 index 0000000..a079b13 --- /dev/null +++ b/src/sysinfo/modules/lscpu.py @@ -0,0 +1,34 @@ +import re +from sysinfo_lib import camelCase + + +def parser(stdout, stderr, to_camelcase): + output = {} + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + line = re.sub(r"\(s\)", "s", line) + kv = re.search(r"^([^:]+):\s*(.*)", line) + if kv: + key = camelCase(kv.group(1), to_camelcase) + value = kv.group(2) + + output[key] = value + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "lscpu", + "system": ["linux"], + "cmd": "lscpu", + "description": "Information about the CPU architecture", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/lsmod.py b/src/sysinfo/modules/lsmod.py new file mode 100644 index 0000000..d86416e --- /dev/null +++ b/src/sysinfo/modules/lsmod.py @@ -0,0 +1,42 @@ +import re + + +def parser(stdout, stderr, to_camelcase): + output = {} + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + if re.match(r"Module.*Size", line): + continue + + lineMatch = re.search(r"^(^\S+)\s*(\d+)\s*(\d+)\s*(.*)$", line) + if lineMatch: + used_by = lineMatch.group(4).split(",") + if len(used_by) == 1: + if used_by[0] == "": + used_by = [] + + output[lineMatch.group(1)] = { + "module": lineMatch.group(1), + "size": lineMatch.group(2), + "usedNumber": lineMatch.group(3), + "usedBy": used_by, + } + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "lsmod", + "system": ["linux"], + "cmd": "lsmod", + "description": "Show the status of modules in the Linux Kernel", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/lsns.py b/src/sysinfo/modules/lsns.py new file mode 100644 index 0000000..55964cb --- /dev/null +++ b/src/sysinfo/modules/lsns.py @@ -0,0 +1,26 @@ +from sysinfo_lib import parseTable + + +def parser(stdout, stderr, to_camelcase): + output = {} + + if stdout: + output = parseTable( + stdout, + header_pattern=r"^(\s*NS)(\sTYPE\s+)(\sPATH\s*)(\s\s*NPROCS)(\s*\sPID)(\s*\sPPID)(\s*\sUID)(\sUSER\s*)(\sCOMMAND\s*)", + to_camelcase=to_camelcase, + ) + + return {"output": output, "unprocessed": []} + + +def register(main): + main.register( + { + "name": "lsns", + "system": ["linux"], + "cmd": "lsns -o NS,TYPE,PATH,NPROCS,PID,PPID,UID,USER,COMMAND", + "description": "Block device ioctls", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/lsof.py b/src/sysinfo/modules/lsof.py new file mode 100644 index 0000000..64e3f69 --- /dev/null +++ b/src/sysinfo/modules/lsof.py @@ -0,0 +1,109 @@ +import re + +opField = { + "a": "accessMode", + "c": "commandName", + "C": "structureShareCount", + "d": "deviceCharacterCode", + "D": "majorMinorDeviceNumber", + "f": "fileDescriptor", + "F": "structureAddress", + "g": "processGroupId", + "G": "flags", + "i": "inodeNumber", + "k": "linkCount", + "K": "taskId", + "l": "lockStatus", + "L": "loginName", + "m": "markerBetweenRepeatedOutput", + "n": "name", + "N": "nodeIdentifier", + "o": "fileOffset", + "p": "processId", + "P": "protocolName", + "r": "rawDeviceNumber", + "R": "parentPid", + "s": "fileSize", + "S": "streamModuleAndDeviceNames", + "t": "fileType", + "T": "tcpTpiInfo", + "u": "userId", + "z": "zoneName", + "Z": "selinuxSecurityContext", +} + +tcptpiField = { + "QR": "readQueueSize", + "QS": "sendQueueSize", + "SO": "socketOptionsAndValues", + "SS": "socketStates", + "ST": "connectionState", + "TF": "tcpFlagsAndValues", + "WR": "windowReadSize", + "WW": "windowWriteSize", +} + + +def parseElements(elements): + global opField + global tcptpiField + output = {} + for el in elements: + ident = el[0:1] + content = el[1:].strip() + + identType = opField.get(ident, ident) + if identType: + if not identType in output: + output[identType] = {} + + if ident == "T": + fifc = (content + "=").split("=") + fifcType = tcptpiField.get(fifc[0], fifc[0]) + + output[identType][fifcType] = fifc[1] + + else: + output[identType] = content + + return output + + +def parser(stdout, stderr, to_camelcase): + output = {} + pid = None + + if stdout: + for line in re.split(r"\x00\n", stdout): + line = re.sub(r"^[\s\x00]*", "", line) + elements = re.split(r"\x00", line) + if not elements: + continue + + first = elements.pop(0) + if first: + ident = first[0:1] + content = first[1:] + + if ident == "p": + pid = content + output[pid] = parseElements(elements) + output[pid]["pid"] = pid + output[pid]["files"] = [] + + if pid and ident == "f": + output[pid]["files"].append(parseElements(elements)) + + return {"output": output, "unprocessed": []} + + +def register(main): + main.register( + { + "name": "lsof", + "system": ["linux"], + "cmd": "lsof -F0", + "description": "Information about files opened by processes", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/lspci.py b/src/sysinfo/modules/lspci.py new file mode 100644 index 0000000..69292ef --- /dev/null +++ b/src/sysinfo/modules/lspci.py @@ -0,0 +1,43 @@ +import re +from sysinfo_lib import camelCase + + +def parser(stdout, stderr, to_camelcase): + output = {} + slot = None + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + if line.strip() == "": + continue + + slotSearch = re.search(r"^Slot:\s+(.*)$", line, re.IGNORECASE) + if slotSearch: + slot = slotSearch.group(1).strip() + output[slot] = {} + continue + + kv = re.search(r"^(\S[^:]+):\s+(.*)$", line) + if slot and kv: + key = camelCase(kv.group(1), to_camelcase) + value = kv.group(2).strip() + + output[slot][key] = value + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "lspci", + "system": ["linux"], + "cmd": "lspci -mm -vvv", + "description": "List all PCI devices", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/lsusb.py b/src/sysinfo/modules/lsusb.py new file mode 100644 index 0000000..611559e --- /dev/null +++ b/src/sysinfo/modules/lsusb.py @@ -0,0 +1,140 @@ +import re +from sysinfo_lib import camelCase + + +def extractDescriptors(data, lineNumber, offset, to_camelcase): + output = {} + ln = lineNumber + descOffset = 0 + + while ln < len(data): + line = data[ln] + + searchOffset = re.search(r"^(\s*)", line) + if searchOffset: + lineOffset = len(searchOffset.group(1)) + + if lineOffset < offset: + ln -= 1 + break + + searchDesc = re.search(r"^(\s*)(.*Descriptor):\s*$", line, re.IGNORECASE) + if searchDesc: + descOffset = len(searchDesc.group(1)) + 2 + desc, ln = extractDescriptors(data, ln + 1, descOffset, to_camelcase) + name = camelCase(searchDesc.group(2).strip(), to_camelcase) + output[name] = desc + ln += 1 + continue + + searchStatus = re.search(r"^(\s*)(.*Status):\s*(.*)$", line, re.IGNORECASE) + if searchStatus: + statusOffset = len(searchStatus.group(1)) + 2 + desc, ln = extractDescriptors(data, ln + 1, statusOffset, to_camelcase) + name = camelCase(searchStatus.group(2).strip(), to_camelcase) + if searchStatus.group(3).strip() != "": + desc["value"] = searchStatus.group(3).strip() + + if name == "hubPortStatus": + print("desc", desc) + output[name] = desc + ln += 1 + continue + + searchKeyIndexValue = re.search(r"^\s*(\S+)\s+([0-9]+):\s+(.*)$", line) + if searchKeyIndexValue: + key = camelCase(searchKeyIndexValue.group(1), to_camelcase) + number = searchKeyIndexValue.group(2) + value = searchKeyIndexValue.group(3).strip() + + valueSplit = re.search(r"^(\S+)\s+(\S.*)$", value) + if valueSplit: + value = [valueSplit.group(1), valueSplit.group(2)] + + if not key in output: + output[key] = {} + + output[key][number] = value + + ln += 1 + continue + + searchKeyValue = re.search(r"^\s*(\S+)\s+([0-9].*)$", line) + if searchKeyValue: + key = camelCase(searchKeyValue.group(1).strip(":"), to_camelcase) + value = searchKeyValue.group(2).strip() + + valueSplit = re.search(r"^(\S+)\s+(\S.*)$", value) + if valueSplit: + value = [valueSplit.group(1), valueSplit.group(2)] + + if key in output: + output[key] = [output[key], value] + else: + output[key] = value + + ln += 1 + continue + + if not "data" in output: + output["data"] = [] + + output["data"].append(line.strip()) + + ln += 1 + return output, ln + + +def parseBlock(data, to_camelcase): + output = {} + lines = data.split("\n") + + while lines[0].strip() == "": + lines.pop(0) + + firstLine = lines.pop(0) + busDevice = re.search( + r"Bus\s+(\S+)\s+Device\s+([^:]+):\s+ID\s+([^:]+):(\S+)\s*(.*)$", + firstLine, + re.IGNORECASE, + ) + if busDevice: + output["bus"] = busDevice.group(1) + output["device"] = busDevice.group(2) + output["idVendor"] = busDevice.group(3) + output["idProduct"] = busDevice.group(4) + output["vendorProduct"] = busDevice.group(5) + + output["desc"], tmp = extractDescriptors(lines, 0, 0, to_camelcase) + + return output + + +def parser(stdout, stderr, to_camelcase): + output = {} + + if stdout: + delimiter = "-" * 20 + blocks = re.split( + delimiter, re.sub(r"\n\nBus", "\n\n" + delimiter + "Bus", stdout) + ) + if blocks: + for block in blocks: + blockData = parseBlock(block, to_camelcase) + if "bus" in blockData and "device" in blockData: + id = blockData["bus"] + "/" + blockData["device"] + output[id] = blockData + + return {"output": output, "unprocessed": []} + + +def register(main): + main.register( + { + "name": "lsusb", + "system": ["linux"], + "cmd": "lsusb -v", + "description": "List USB devices", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/modinfo.py b/src/sysinfo/modules/modinfo.py new file mode 100644 index 0000000..02bdfa2 --- /dev/null +++ b/src/sysinfo/modules/modinfo.py @@ -0,0 +1,43 @@ +import re +from sysinfo_lib import camelCase + + +def parser(stdout, stderr, to_camelcase): + output = {} + moduleName = None + unprocessed = [] + + if stdout: + stdout_fix = re.sub(r"\n[\t]+", " ", stdout) + + for line in stdout_fix.splitlines(): + kv = re.search(r"^([^:]+):\s+(.*)$", line) + if kv: + key = kv.group(1) + value = kv.group(2) + + if key == ">>> moduleName": + moduleName = value + output[moduleName] = {} + continue + + if moduleName: + key = camelCase(key, to_camelcase) + output[moduleName][key] = value.strip() + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "modinfo", + "system": ["linux"], + "cmd": """lsmod | grep -v "Module" | sed 's/ .*//g' | xargs -I {} -n 1 sh -c "echo '>>> moduleName: {}'; modinfo {}" """, + "description": "Information about a Linux Kernel modules", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/parted.py b/src/sysinfo/modules/parted.py new file mode 100644 index 0000000..8267ece --- /dev/null +++ b/src/sysinfo/modules/parted.py @@ -0,0 +1,64 @@ +import re + + +def parser(stdout, stderr, to_camelcase): + output = {} + defaultUnit = None + path = None + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + if line.strip() == "": + defaultUnit = None + path = None + continue + + unitSearch = re.search(r"^(\S+);$", line) + if unitSearch: + defaultUnit = unitSearch.group(1) + continue + + lineSplit = (line.strip(";") + (":" * 10)).split(":") + + if defaultUnit and re.search(r"^(\/[^:]+)", line): + path = lineSplit[0] + output[path] = { + "path": lineSplit[0], + "defaultUnit": defaultUnit, + "end": lineSplit[1], + "devType": lineSplit[2], + "sectorSize": lineSplit[3], + "physSectorSize": lineSplit[4], + "ptName": lineSplit[5], + "model": lineSplit[6], + "diskFlags": lineSplit[7], + "table": {}, + } + continue + + if path and re.search(r"^(\d+):", line): + output[path]["table"][lineSplit[0]] = { + "start": lineSplit[1], + "end": lineSplit[2], + "size": lineSplit[3], + "fileSystem": lineSplit[4], + "flags": lineSplit[5], + } + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "parted", + "system": ["linux"], + "cmd": "parted -m -l print", + "description": "Lists partition layout on all block devices", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/proc_buddyinfo.py b/src/sysinfo/modules/proc_buddyinfo.py new file mode 100644 index 0000000..13826f7 --- /dev/null +++ b/src/sysinfo/modules/proc_buddyinfo.py @@ -0,0 +1,37 @@ +import re +from sysinfo_lib import camelCase + + +def parser(stdout, stderr, to_camelcase): + output = {"nodes": {}} + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + row = re.search(r"Node\s([^,]+),\szone\s+(\S+)\s*(.*)$", line) + if row: + node = row.group(1) + zone = row.group(2) + value = re.split(r"\s+", row.group(3).strip()) + + if not node in output["nodes"]: + output["nodes"][node] = {} + + output["nodes"][node][zone] = value + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "proc_buddyinfo", + "system": ["linux"], + "cmd": "cat /proc/buddyinfo", + "description": "Memory fragmentation", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/proc_bus_input.py b/src/sysinfo/modules/proc_bus_input.py new file mode 100644 index 0000000..1b77207 --- /dev/null +++ b/src/sysinfo/modules/proc_bus_input.py @@ -0,0 +1,86 @@ +import re +from sysinfo_lib import camelCase + + +def extract_params(entry, params, to_camelcase): + patterns = [ + r"(\S[^=]+)=(\S+)", + r'(\S[^=]+)=\"([^"]*)\"', + r"(\S[^=]+)=(\s|$)", + ] + + for pattern in patterns: + kv = re.findall(pattern, params) + if kv: + for pair in kv: + key = camelCase(pair[0], to_camelcase) + value = pair[1] + entry[key] = value + + +def parser(stdout, stderr, to_camelcase): + output = {"devices": [], "handlers": {}} + unprocessed = [] + types = { + "I": "deviceId", + "N": "name", + "P": "physicalPath", + "S": "sysfsPath", + "U": "uid", + "H": "inputHandlers", + "B": "bitmaps", + } + + if stdout: + [devices, handlers] = stdout.split(">>> handlers") + + print(devices) + + for block in re.split(r"\r\r|\n\n|\r\n\r\n", devices): + blockData = {} + + for line in block.splitlines(): + parts = re.search(r"^(\w):\s+(.*)$", line) + if parts: + type = parts.group(1).strip() + params = parts.group(2).strip() + + if type in types: + type_label = types[type] + if not type_label in blockData: + blockData[type_label] = {} + + extract_params(blockData[type_label], params, to_camelcase) + + output["devices"].append(blockData) + + for line in handlers.splitlines(): + line = re.sub(r"^N: ", "", line) + + kv = re.findall(r"(\S[^=]+)=(\S+)", line) + if kv: + entry = {} + for pair in kv: + key = camelCase(pair[0], to_camelcase) + value = pair[1] + entry[key] = value + + if "name" in entry: + output["handlers"][entry["name"]] = entry + + if "Name" in entry: + output["handlers"][entry["Name"]] = entry + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "proc_bus_input", + "system": ["linux"], + "cmd": 'cat /proc/bus/input/devices; echo ">>> handlers"; cat /proc/bus/input/handlers;', + "description": "Input devices", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/proc_cgroups.py b/src/sysinfo/modules/proc_cgroups.py new file mode 100644 index 0000000..bd63d3d --- /dev/null +++ b/src/sysinfo/modules/proc_cgroups.py @@ -0,0 +1,28 @@ +from sysinfo_lib import parseSpaceTable, tableToDict + + +def parser(stdout, stderr, to_camelcase): + output = {} + + if stdout: + stdout = stdout.replace("#subsys_name", "subsys_name") + + output = parseSpaceTable(stdout, to_camelcase=to_camelcase) + if to_camelcase: + output = tableToDict(output, "subsysName") + else: + output = tableToDict(output, "subsys_name") + + return {"output": output, "unprocessed": []} + + +def register(main): + main.register( + { + "name": "proc_cgroups", + "system": ["linux"], + "cmd": "cat /proc/cgroups", + "description": "Control groups", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/proc_cmdline.py b/src/sysinfo/modules/proc_cmdline.py new file mode 100644 index 0000000..8331c4c --- /dev/null +++ b/src/sysinfo/modules/proc_cmdline.py @@ -0,0 +1,40 @@ +import re +from sysinfo_lib import camelCase + + +def parser(stdout, stderr, to_camelcase): + output = {} + + if stdout: + for kv in re.split(r"[\s\t]+", stdout.strip()): + splitted = re.search(r"^([^=]+)=(.*)$", kv) + if splitted: + key = camelCase(splitted.group(1), to_camelcase) + value = splitted.group(2) + + else: + key = kv + value = "" + + if key in output: + if isinstance(output[key], str): + output[key] = [output[key]] + + output[key].append(value) + + else: + output[key] = value + + return {"output": output, "unprocessed": []} + + +def register(main): + main.register( + { + "name": "proc_cmdline", + "system": ["linux"], + "cmd": "cat /proc/cmdline", + "description": "Parameters passed to the kernel at the time it is started", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/proc_consoles.py b/src/sysinfo/modules/proc_consoles.py new file mode 100644 index 0000000..5b0e4ad --- /dev/null +++ b/src/sysinfo/modules/proc_consoles.py @@ -0,0 +1,62 @@ +import re + + +def parser(stdout, stderr, to_camelcase): + """ + The columns are: + device name of the device + operations R = can do read operations + W = can do write operations + U = can do unblank + flags E = it is enabled + C = it is preferred console + B = it is primary boot console + p = it is used for printk buffer + b = it is not a TTY but a Braille device + a = it is safe to use when cpu is offline + major:minor major and minor number of the device separated by a colon + """ + + output = {} + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + values = re.search(r"^(\S+)\s+(.*)\s+(\S+):(\S+)", line) + if values: + params = values.group(2).strip() + output[values.group(1)] = { + "device": values.group(1), + "operations": { + "read": "R" in params, + "write": "W" in params, + "unblank": "U" in params, + }, + "flags": { + "enabled": "E" in params, + "preferred": "C" in params, + "primaryBoot": "B" in params, + "printkBuffer": "p" in params, + "braile": "b" in params, + "safeCpuOffline": "a" in params, + }, + "major": values.group(3), + "minor": values.group(4), + } + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "proc_consoles", + "system": ["linux"], + "cmd": "cat /proc/consoles", + "description": "Information about current consoles including tty", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/proc_cpuinfo.py b/src/sysinfo/modules/proc_cpuinfo.py new file mode 100644 index 0000000..b992895 --- /dev/null +++ b/src/sysinfo/modules/proc_cpuinfo.py @@ -0,0 +1,44 @@ +import re +from sysinfo_lib import camelCase + + +def parser(stdout, stderr, to_camelcase): + output = {"processor": {}, "hardware": {}, "oth": {}} + unprocessed = [] + + if stdout: + for block in re.split(r"\r\r|\n\n|\r\n\r\n", stdout): + sub = {} + for line in block.splitlines(): + kv = re.search(r"([^\t]+)\s*:\s*(.*)$", line) + if kv: + key = camelCase(kv.group(1).strip(), to_camelcase) + value = kv.group(2).strip() + + sub[key] = value + continue + + unprocessed.append(line) + + if "processor" in sub: + output["processor"][sub["processor"]] = sub + + elif "hardware" in sub: + output["hardware"] = sub + + else: + output["oth"] = sub + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "proc_cpuinfo", + "system": ["linux"], + "cmd": "cat /proc/cpuinfo", + "description": "Type of processor used by your system", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/proc_crypto.py b/src/sysinfo/modules/proc_crypto.py new file mode 100644 index 0000000..3734989 --- /dev/null +++ b/src/sysinfo/modules/proc_crypto.py @@ -0,0 +1,38 @@ +import re +from sysinfo_lib import camelCase + + +def parser(stdout, stderr, to_camelcase): + output = {} + unprocessed = [] + + if stdout: + for block in re.split(r"\r\r|\n\n|\r\n\r\n", stdout): + sub = {} + for line in block.splitlines(): + kv = re.search(r"([^\t]+)\s*:\s*(.*)$", line) + if kv: + key = camelCase(kv.group(1).strip(), to_camelcase) + value = kv.group(2).strip() + + sub[key] = value + continue + + unprocessed.append(line) + + if "name" in sub: + output[sub["name"]] = sub + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "proc_crypto", + "system": ["linux"], + "cmd": "cat /proc/crypto", + "description": "Installed cryptographic ciphers used by the Linux kernel", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/proc_devices.py b/src/sysinfo/modules/proc_devices.py new file mode 100644 index 0000000..4c629e2 --- /dev/null +++ b/src/sysinfo/modules/proc_devices.py @@ -0,0 +1,43 @@ +import re +from sysinfo_lib import camelCase + + +def parser(stdout, stderr, to_camelcase): + output = {} + deviceType = None + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + dt = re.search(r"^([^:]+):", line) + if dt: + deviceType = camelCase(dt.group(1), to_camelcase) + output[deviceType] = {} + continue + + dv = re.search(r"^\s*(\d+)\s*(.*)$", line) + if dv and deviceType: + id = dv.group(1) + name = dv.group(2) + + if not name in output[deviceType]: + output[deviceType][name] = [] + + output[deviceType][name].append(id) + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "proc_devices", + "system": ["linux"], + "cmd": "cat /proc/devices", + "description": "Installed cryptographic ciphers used by the Linux kernel", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/proc_diskstats.py b/src/sysinfo/modules/proc_diskstats.py new file mode 100644 index 0000000..1555471 --- /dev/null +++ b/src/sysinfo/modules/proc_diskstats.py @@ -0,0 +1,51 @@ +import re +from sysinfo_lib import camelCase + + +def parser(stdout, stderr, to_camelcase): + output = {} + columnNames = [ + "majorNumber", + "minorNumber", + "deviceName", + "readsCompletedSuccessfully", + "readsMerged", + "sectorsRead", + "timeSpentReading", + "writesCompleted", + "writesMerged", + "sectorsWritten", + "timeSpentWriting", + "IOsCurrentlyInProgress", + "timeSpentDoingIOs", + "weightedTimeSpentDoingIOs", + ] + lenColumnNames = len(columnNames) + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + line = line.strip() + columns = re.split(r"\s+", line) + if columns: + output[columns[2]] = {} + for num, val in enumerate(columns, start=0): + if num < lenColumnNames: + output[columns[2]][columnNames[num]] = val + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "proc_diskstats", + "system": ["linux"], + "cmd": "cat /proc/diskstats", + "description": "I/O statistics of block devices", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/proc_dma.py b/src/sysinfo/modules/proc_dma.py new file mode 100644 index 0000000..be7745a --- /dev/null +++ b/src/sysinfo/modules/proc_dma.py @@ -0,0 +1,32 @@ +import re + + +def parser(stdout, stderr, to_camelcase): + output = {} + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + kv = re.search(r"^\s*([^:]+):\s*(.*)$", line) + if kv: + key = kv.group(1).strip() + value = kv.group(2).strip() + + output[key] = value + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "proc_dma", + "system": ["linux"], + "cmd": "cat /proc/dma", + "description": "List of the registered ISA DMA channels in use", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/proc_filesystems.py b/src/sysinfo/modules/proc_filesystems.py new file mode 100644 index 0000000..b72bfd9 --- /dev/null +++ b/src/sysinfo/modules/proc_filesystems.py @@ -0,0 +1,37 @@ +import re + + +def parser(stdout, stderr, to_camelcase): + output = {} + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + kv = re.search(r"^(\S+)\s+(\S+)", line) + if kv: + key = kv.group(2).strip() + value = kv.group(1).strip() + + output[key] = value + continue + + k = re.search(r"^\s+(\S+)$", line) + if k: + output[k.group(1).strip()] = "" + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "proc_filesystems", + "system": ["linux"], + "cmd": "cat /proc/filesystems", + "description": "List of the file system types currently supported by the kernel", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/proc_fs.py b/src/sysinfo/modules/proc_fs.py new file mode 100644 index 0000000..a53e5c1 --- /dev/null +++ b/src/sysinfo/modules/proc_fs.py @@ -0,0 +1,60 @@ +import re +from sysinfo_lib import camelCase + + +def setPathValue(data, path, value): + pathRest = None + pathParts = re.search(r"^([^\/]+)\/?(.*)$", path) + if pathParts: + path = camelCase(pathParts.group(1)) + pathRest = pathParts.group(2) + + if not path in data: + data[path] = {} + + if pathRest: + setPathValue(data[path], pathRest, value) + + else: + path = camelCase(path) + if isinstance(data[path], dict): + data[path] = [] + + data[path].append(value) + + +def parser(stdout, stderr, to_camelcase): + output = {} + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + pathValue = re.search(r"^\/proc\/fs\/([^:]+):(.*)$", line) + if pathValue: + path = pathValue.group(1) + value = pathValue.group(2) + + print(path, value) + # if not re.search(r"^\s*#", value): + # setPathValue(output, path, value) + + # continue + + # else: + # print(line) + + # unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "proc_fs", + "system": ["linux"], + "cmd": """find /proc/fs -type f -follow -print | xargs grep "" """, + "description": "File system parameters", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/proc_iomem.py b/src/sysinfo/modules/proc_iomem.py new file mode 100644 index 0000000..e98c4b6 --- /dev/null +++ b/src/sysinfo/modules/proc_iomem.py @@ -0,0 +1,35 @@ +import re + + +def parser(stdout, stderr, to_camelcase): + output = [] + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + values = re.search(r"^\s*([^-]+)-(\S+)\s*:\s*(.*)$", line) + if values: + output.append( + { + "from": values.group(1), + "to": values.group(2), + "device": values.group(3), + } + ) + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "proc_iomem", + "system": ["linux"], + "cmd": "cat /proc/iomem", + "description": """Map of the system's memory for each physical device""", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/proc_ioports.py b/src/sysinfo/modules/proc_ioports.py new file mode 100644 index 0000000..5ec0abd --- /dev/null +++ b/src/sysinfo/modules/proc_ioports.py @@ -0,0 +1,37 @@ +import re + + +def parser(stdout, stderr, to_camelcase): + output = [] + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + entrySearch = re.search( + r"^\s*([^-]+)-(\S+)\s*:\s*(.*)$", line, re.IGNORECASE + ) + if entrySearch: + output.append( + { + "from": entrySearch.group(1), + "to": entrySearch.group(2), + "device": entrySearch.group(3), + } + ) + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "proc_ioports", + "system": ["linux"], + "cmd": "cat /proc/ioports", + "description": "List of currently registered port regions used for input or output communication with a device", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/proc_loadavg.py b/src/sysinfo/modules/proc_loadavg.py new file mode 100644 index 0000000..67afe9e --- /dev/null +++ b/src/sysinfo/modules/proc_loadavg.py @@ -0,0 +1,34 @@ +import re + + +def parser(stdout, stderr, to_camelcase): + output = {} + unprocessed = [] + + if stdout: + values = re.search(r"^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)", stdout.strip()) + if values: + output = { + "periodLast": values.group(1), + "period5minute": values.group(2), + "period15minute": values.group(3), + "processes": values.group(4), + "lastPid": values.group(5), + } + + else: + unprocessed.append(stdout) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "proc_loadavg", + "system": ["linux"], + "cmd": "cat /proc/loadavg", + "description": "Load average in regard to both the CPU and IO over time", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/proc_locks.py b/src/sysinfo/modules/proc_locks.py new file mode 100644 index 0000000..46483d9 --- /dev/null +++ b/src/sysinfo/modules/proc_locks.py @@ -0,0 +1,37 @@ +import re + + +def parser(stdout, stderr, to_camelcase): + output = {} + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + lineSplit = re.split(r"[\s\t]+", line) + if lineSplit and len(lineSplit) > 5: + output[lineSplit[0]] = { + "uid": lineSplit[0], + "class": lineSplit[1], + "lockType": lineSplit[2], + "allowAccessType": lineSplit[3], + "pid": lineSplit[4], + "fileID": lineSplit[5], + "lockedRegion": lineSplit[6], + } + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "proc_locks", + "system": ["linux"], + "cmd": "cat /proc/locks", + "description": "Files currently locked by the kernel", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/proc_meminfo.py b/src/sysinfo/modules/proc_meminfo.py new file mode 100644 index 0000000..f0ece86 --- /dev/null +++ b/src/sysinfo/modules/proc_meminfo.py @@ -0,0 +1,41 @@ +import re +from sysinfo_lib import camelCase + + +def parser(stdout, stderr, to_camelcase): + output = {} + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + kv = re.search(r"^([^:]+):\s*(.*)$", line, re.IGNORECASE) + if kv: + key = camelCase(kv.group(1).strip(":"), to_camelcase) + value = kv.group(2).strip() + + valueSearch = re.search(r"(.*)\s+(.*)$", value) + if valueSearch: + output[key] = { + "value": valueSearch.group(1), + "type": valueSearch.group(2), + } + else: + output[key] = {"value": value, "type": ""} + + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": []} + + +def register(main): + main.register( + { + "name": "proc_meminfo", + "system": ["linux"], + "cmd": "cat /proc/meminfo", + "description": "Reports a large amount of valuable information about the systems RAM usage", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/proc_modules.py b/src/sysinfo/modules/proc_modules.py new file mode 100644 index 0000000..8ea2431 --- /dev/null +++ b/src/sysinfo/modules/proc_modules.py @@ -0,0 +1,36 @@ +import re + + +def parser(stdout, stderr, to_camelcase): + output = {} + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + lineSplit = re.split(r"[\s\t]+", line) + if lineSplit and len(lineSplit) > 5: + output[lineSplit[0]] = { + "moduleName": lineSplit[0], + "moduleMemorySize": lineSplit[1], + "numInstancesLoaded": lineSplit[2], + "depends": lineSplit[3].strip(",").split(","), + "state": lineSplit[4], + "kernelMemoryOffset": lineSplit[5], + } + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "proc_modules", + "system": ["linux"], + "cmd": "cat /proc/modules", + "description": "List of all modules loaded into the kernel", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/proc_mounts.py b/src/sysinfo/modules/proc_mounts.py new file mode 100644 index 0000000..5835a84 --- /dev/null +++ b/src/sysinfo/modules/proc_mounts.py @@ -0,0 +1,39 @@ +import re + + +def parser(stdout, stderr, to_camelcase): + output = {} + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + lineSplit = re.split(r"[\s\t]+", line) + if lineSplit and len(lineSplit) > 4: + accessValues = {} + for access in re.split(r",", lineSplit[3]): + accessSplit = re.split(r"=", access + "=") + accessValues[accessSplit[0]] = accessSplit[1] + + output[lineSplit[1]] = { + "device": lineSplit[0], + "mountPoint": lineSplit[1], + "type": lineSplit[2], + "access": accessValues, + } + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "proc_mounts", + "system": ["linux"], + "cmd": "cat /proc/mounts", + "description": "List mounted filesystems (info provides from kernel)", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/proc_net.py b/src/sysinfo/modules/proc_net.py new file mode 100644 index 0000000..759e658 --- /dev/null +++ b/src/sysinfo/modules/proc_net.py @@ -0,0 +1,178 @@ +import struct +import socket +from sysinfo_lib import parseSpaceTable, tableToDict + + +def parser_route(stdout, stderr, to_camelcase): + output = {} + + if stdout: + output = parseSpaceTable(stdout, to_camelcase=to_camelcase) + if to_camelcase: + output = tableToDict(output, "iface") + else: + output = tableToDict(output, "Iface") + + return {"output": output, "unprocessed": []} + + +def split_every_n(data, n): + return [data[i : i + n] for i in range(0, len(data), n)] + + +def parse_ipv4_address(address): + hex_addr, hex_port = address.split(":") + + addr_list = split_every_n(hex_addr, 2) + addr_list.reverse() + addr = ".".join(map(lambda x: str(int(x, 16)), addr_list)) + port = str(int(hex_port, 16)) + + return addr, port + + +def parse_ipv6_address(address): + hex_addr, hex_port = address.split(":") + + addr = bytes.fromhex(hex_addr) + addr = struct.unpack(">IIII", addr) + addr = struct.pack("@IIII", *addr) + addr = socket.inet_ntop(socket.AF_INET6, addr) + port = str(int(hex_port, 16)) + + return addr, port + + +def extend_address4(entry, name, name_addr, name_port): + if name in entry: + address, port = parse_ipv4_address(entry[name]) + entry[name_addr] = address + entry[name_port] = port + + +def extend_address6(entry, name, name_addr, name_port): + if name in entry: + address, port = parse_ipv6_address(entry[name]) + entry[name_addr] = address + entry[name_port] = port + + +def parser_tcp_udp(stdout, stderr, to_camelcase): + output = {} + + if stdout: + output = parseSpaceTable(stdout, to_camelcase=to_camelcase) + + for entry in output: + extend_address4(entry, "local_address", "local_addr", "local_port") + extend_address4(entry, "rem_address", "rem_addr", "rem_port") + extend_address4(entry, "localAddress", "localAddr", "localPort") + extend_address4(entry, "remAddress", "remAddr", "remPort") + + return {"output": output, "unprocessed": []} + + +def parser_tcp_udp_6(stdout, stderr, to_camelcase): + output = {} + + if stdout: + output = parseSpaceTable(stdout, to_camelcase=to_camelcase) + + for entry in output: + extend_address6(entry, "local_address", "local_addr", "local_port") + extend_address6(entry, "rem_address", "rem_addr", "rem_port") + extend_address6(entry, "localAddress", "localAddr", "localPort") + extend_address6(entry, "remAddress", "remAddr", "remPort") + + return {"output": output, "unprocessed": []} + + +def parser_arp(stdout, stderr, to_camelcase): + output = {} + + if stdout: + output = parseSpaceTable(stdout, to_camelcase=to_camelcase) + + return {"output": output, "unprocessed": []} + + +def register(main): + main.register( + { + "name": "proc_net_route", + "system": ["linux"], + "cmd": "cat /proc/net/route", + "description": "IP routing information", + "parser": parser_route, + } + ) + + main.register( + { + "name": "proc_net_ax25_route", + "system": ["linux"], + "cmd": "cat /proc/net/ax25_route", + "description": "AX25 routing information", + "parser": parser_route, + } + ) + + main.register( + { + "name": "proc_net_ipx_route", + "system": ["linux"], + "cmd": "cat /proc/net/ipx_route", + "description": "IPX routing information", + "parser": parser_route, + } + ) + + main.register( + { + "name": "proc_net_tcp", + "system": ["linux"], + "cmd": "cat /proc/net/tcp", + "description": "TCP socket table", + "parser": parser_tcp_udp, + } + ) + + main.register( + { + "name": "proc_net_udp", + "system": ["linux"], + "cmd": "cat /proc/net/udp", + "description": "UDP socket table", + "parser": parser_tcp_udp, + } + ) + + main.register( + { + "name": "proc_net_tcp6", + "system": ["linux"], + "cmd": "cat /proc/net/tcp6", + "description": "TCP6 socket table", + "parser": parser_tcp_udp_6, + } + ) + + main.register( + { + "name": "proc_net_udp6", + "system": ["linux"], + "cmd": "cat /proc/net/udp6", + "description": "UDP6 socket table", + "parser": parser_tcp_udp_6, + } + ) + + main.register( + { + "name": "proc_net_arp", + "system": ["linux"], + "cmd": "cat /proc/net/arp", + "description": "ARP ", + "parser": parser_arp, + } + ) diff --git a/src/sysinfo/modules/proc_partitions.py b/src/sysinfo/modules/proc_partitions.py new file mode 100644 index 0000000..ef93029 --- /dev/null +++ b/src/sysinfo/modules/proc_partitions.py @@ -0,0 +1,23 @@ +from sysinfo_lib import parseSpaceTable, tableToDict + + +def parser(stdout, stderr, to_camelcase): + output = {} + + if stdout: + output = parseSpaceTable(stdout, to_camelcase=to_camelcase) + output = tableToDict(output, "name") + + return {"output": output, "unprocessed": []} + + +def register(main): + main.register( + { + "name": "proc_partitions", + "system": ["linux"], + "cmd": "cat /proc/partitions", + "description": "Partition block allocation information", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/proc_scsi.py b/src/sysinfo/modules/proc_scsi.py new file mode 100644 index 0000000..a69b8e1 --- /dev/null +++ b/src/sysinfo/modules/proc_scsi.py @@ -0,0 +1,64 @@ +import re + + +def parser(stdout, stderr, to_camelcase): + output = {} + path = None + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + hostSearch = re.search( + r"^Host:\s+(\S+)\s+Channel:\s+(\S+)\s+Id:\s+(\S+)\s+Lun:\s+(\S+)$", + line, + re.IGNORECASE, + ) + if hostSearch: + host = hostSearch.group(1) + channel = hostSearch.group(2) + id = hostSearch.group(3) + lun = hostSearch.group(4) + path = "%s:%s:%s:%s" % (host.replace("scsi", ""), channel, id, lun) + output[path] = {"host": host, "channel": channel, "id": id, "lun": lun} + continue + + if path: + vendorSearch = re.search( + r"^\s+Vendor:\s+(.*)\s+Model:\s+(.*)\s+Rev:\s+(.*)$", + line, + re.IGNORECASE, + ) + if vendorSearch: + output[path]["vendor"] = vendorSearch.group(1).strip() + output[path]["model"] = vendorSearch.group(2).strip() + output[path]["rev"] = vendorSearch.group(3).strip() + continue + + typeSearch = re.search( + r"^\s+Type:\s+(.*)\s+ANSI\s+SCSI\s+revision:\s+(.*)$", + line, + re.IGNORECASE, + ) + if typeSearch: + output[path]["type"] = typeSearch.group(1).strip() + output[path]["revision"] = typeSearch.group(2).strip() + continue + + if re.match(r"Attached devices", line): + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "proc_scsi", + "system": ["linux"], + "cmd": "cat /proc/scsi/scsi", + "description": "List of every recognized SCSI device", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/proc_slabinfo.py b/src/sysinfo/modules/proc_slabinfo.py new file mode 100644 index 0000000..9558a37 --- /dev/null +++ b/src/sysinfo/modules/proc_slabinfo.py @@ -0,0 +1,75 @@ +import re +from sysinfo_lib import camelCase + + +def extract_key_value(keys, data): + entry = {} + + if len(keys) == len(data): + for key_index, key in enumerate(keys): + entry[key] = data[key_index] + + return entry + + +def parser(stdout, stderr, to_camelcase): + output = {} + key_names = [] + has_keys = False + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + if re.match(r"^slabinfo - version", line): + continue + + header = re.search(r"^# name\s+(.*)$", line) + if header: + parts = header.group(1).strip().split(":") + for part in parts: + part = part.strip() + part = re.sub(r"(tunables|slabdata)\s+", "", part) + part = part.replace("<", "").replace(">", "") + part_columns = re.split(r"\s+", part) + part_columns = [ + camelCase(key, to_camelcase) for key in part_columns + ] + + key_names.append(part_columns) + + has_keys = True + continue + + row = re.search(r"^(\S+)\s+(.*)\s:\stunables(.*)\s:\sslabdata(.*)$", line) + if has_keys and row: + name = row.group(1) + statistics_data = re.split(r"\s+", row.group(2).strip()) + tunables_data = re.split(r"\s+", row.group(3).strip()) + slabdata_data = re.split(r"\s+", row.group(4).strip()) + + statistics = extract_key_value(key_names[0], statistics_data) + tunables = extract_key_value(key_names[1], tunables_data) + slabdata = extract_key_value(key_names[2], slabdata_data) + + output[name] = { + "statistics": statistics, + "tunables": tunables, + "slabdata": slabdata, + } + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "proc_slabinfo", + "system": ["linux"], + "cmd": "cat /proc/slabinfo", + "description": "Kernel caches informations", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/proc_stat.py b/src/sysinfo/modules/proc_stat.py new file mode 100644 index 0000000..705f418 --- /dev/null +++ b/src/sysinfo/modules/proc_stat.py @@ -0,0 +1,42 @@ +import re +from sysinfo_lib import camelCase + + +def parser(stdout, stderr, to_camelcase): + output = {} + + if stdout: + for line in stdout.splitlines(): + parts = re.split(r"\s+", line) + key = parts.pop(0) + + if re.match(r"^cpu", key) and len(parts) == 10: + output[key] = { + "user": parts[0], + "nice": parts[1], + "system": parts[2], + "idle": parts[3], + "iowait": parts[4], + "irq": parts[5], + "softirq": parts[6], + "steal": parts[7], + "guest": parts[8], + "guest_nice": parts[9], + } + continue + + output[key] = " ".join(parts) + + return {"output": output, "unprocessed": []} + + +def register(main): + main.register( + { + "name": "proc_stat", + "system": ["linux"], + "cmd": "cat /proc/stat", + "description": "Kernel/system statistics", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/proc_swaps.py b/src/sysinfo/modules/proc_swaps.py new file mode 100644 index 0000000..032bd7b --- /dev/null +++ b/src/sysinfo/modules/proc_swaps.py @@ -0,0 +1,22 @@ +from sysinfo_lib import parseSpaceTable + + +def parser(stdout, stderr, to_camelcase): + output = {} + + if stdout: + output = parseSpaceTable(stdout, to_camelcase=to_camelcase) + + return {"output": output, "unprocessed": []} + + +def register(main): + main.register( + { + "name": "proc_swaps", + "system": ["linux"], + "cmd": "cat /proc/swaps", + "description": "Measures swap space and its utilization", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/proc_sys.py b/src/sysinfo/modules/proc_sys.py new file mode 100644 index 0000000..8619a23 --- /dev/null +++ b/src/sysinfo/modules/proc_sys.py @@ -0,0 +1,52 @@ +import re +from sysinfo_lib import camelCase + + +def setPathValue(data, path, value, to_camelcase): + pathRest = None + pathParts = re.search(r"^([^\/]+)\/?(.*)$", path) + if pathParts: + path = camelCase(pathParts.group(1), to_camelcase) + pathRest = pathParts.group(2) + + else: + path = camelCase(path) + + if not path in data: + data[path] = {} + + if pathRest: + setPathValue(data[path], pathRest, value, to_camelcase) + else: + key = camelCase(path, to_camelcase) + data[key] = value + + +def parser(stdout, stderr, to_camelcase): + output = {} + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + pathValue = re.search(r"^\/proc\/sys\/([^:]+):(.*)$", line) + if pathValue: + path = pathValue.group(1) + value = pathValue.group(2) + setPathValue(output, path, value, to_camelcase) + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "proc_sys", + "system": ["linux"], + "cmd": """find /proc/sys -type f -follow -print 2>/dev/null | xargs -n 1 -I {} sh -c 'VAL=$(cat {} 2>/dev/null); echo {}:$VAL;'""", + "description": "Information about the system and kernel features", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/proc_uptime.py b/src/sysinfo/modules/proc_uptime.py new file mode 100644 index 0000000..f05445c --- /dev/null +++ b/src/sysinfo/modules/proc_uptime.py @@ -0,0 +1,27 @@ +import re + + +def parser(stdout, stderr, to_camelcase): + output = {} + + if stdout: + splitted = re.split(r"\s+", stdout.strip()) + if len(splitted) > 0: + output["systemUp"] = splitted[0] + + if len(splitted) > 1: + output["sumCoresIdle"] = splitted[1] + + return {"output": output, "unprocessed": []} + + +def register(main): + main.register( + { + "name": "proc_uptime", + "system": ["linux"], + "cmd": "cat /proc/uptime", + "description": "Information detailing how long the system has been on since its last restart", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/proc_version.py b/src/sysinfo/modules/proc_version.py new file mode 100644 index 0000000..c068d69 --- /dev/null +++ b/src/sysinfo/modules/proc_version.py @@ -0,0 +1,19 @@ +def parser(stdout, stderr, to_camelcase): + output = {} + + if stdout: + output = stdout.strip() + + return {"output": output, "unprocessed": []} + + +def register(main): + main.register( + { + "name": "proc_version", + "system": ["linux"], + "cmd": "cat /proc/version", + "description": "Version of the Linux kernel, the version of gcc used to compile the kernel, and the time of kernel compilation", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/proc_version_signature.py b/src/sysinfo/modules/proc_version_signature.py new file mode 100644 index 0000000..382fccd --- /dev/null +++ b/src/sysinfo/modules/proc_version_signature.py @@ -0,0 +1,23 @@ +import re + + +def parser(stdout, stderr, to_camelcase): + output = "" + + if stdout: + output = re.sub(r"\n|\r|\r\n", "", stdout) + output = stdout.strip() + + return {"output": output, "unprocessed": []} + + +def register(main): + main.register( + { + "name": "proc_version_signature", + "system": ["linux"], + "cmd": "cat /proc/version_signature", + "description": "OS version signature", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/proc_vmstat.py b/src/sysinfo/modules/proc_vmstat.py new file mode 100644 index 0000000..cba3d2d --- /dev/null +++ b/src/sysinfo/modules/proc_vmstat.py @@ -0,0 +1,33 @@ +import re +from sysinfo_lib import camelCase + + +def parser(stdout, stderr, to_camelcase): + output = {} + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + lineMatch = re.search(r"^([^\s+]+)\s(.*)$", line) + if lineMatch: + key = camelCase(lineMatch.group(1), to_camelcase) + value = lineMatch.group(2) + + output[key] = value + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "proc_vmstat", + "system": ["linux"], + "cmd": "cat /proc/vmstat", + "description": "Detailed virtual memory statistics from the kernel", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/prtstat.py b/src/sysinfo/modules/prtstat.py new file mode 100644 index 0000000..fc96362 --- /dev/null +++ b/src/sysinfo/modules/prtstat.py @@ -0,0 +1,41 @@ +import re +from sysinfo_lib import camelCase + + +def parser(stdout, stderr, to_camelcase): + output = {} + pid = "" + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + processed = False + pairs = re.findall(r"(\S+):\s(\S+)", line) + for kv in pairs: + if kv[0] == "pid": + pid = kv[1] + output[pid] = {} + + if pid != "": + key = camelCase(kv[0], to_camelcase) + value = kv[1].strip() + + output[pid][key] = value + processed = True + + if not processed: + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "prtstat", + "system": ["linux"], + "cmd": "ps -eo pid | xargs -I {} prtstat -r {}", + "description": "Print statistics of a processes", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/ps.py b/src/sysinfo/modules/ps.py new file mode 100644 index 0000000..76d830d --- /dev/null +++ b/src/sysinfo/modules/ps.py @@ -0,0 +1,62 @@ +import re + + +def parser(stdout, stderr, to_camelcase): + output = {} + unprocessed = [] + columnsNames = [ + "user", + "ruser", + "group", + "rgroup", + "pid", + "ppid", + "pgid", + "cpu", + "size", + "bytes", + "nice", + "time", + "stime", + "tty", + "args", + ] + columnsCount = len(columnsNames) + + if stdout: + for line in stdout.splitlines(): + if re.search(r"ps --cols 12288 -eo", line) or re.search( + r"USER.*RUSER.*GROUP", line + ): + continue + + cols = re.split(r"\s+", line) + if cols: + entry = {} + for num, val in enumerate(cols, start=0): + if num < columnsCount: + name = columnsNames[num] + entry[name] = val + + elif "args" in entry: + entry["args"] += " " + val + + if "pid" in entry: + output[entry["pid"]] = entry + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "ps", + "system": ["linux"], + "cmd": "ps --cols 12288 -eo user:256,ruser:256,group:256,rgroup:256,pid,ppid,pgid,pcpu,vsz,nice,etime,time,stime,tty,args 2>/dev/null", + "description": "Report a snapshot of the current processes", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/python.py b/src/sysinfo/modules/python.py new file mode 100644 index 0000000..feeca6a --- /dev/null +++ b/src/sysinfo/modules/python.py @@ -0,0 +1,86 @@ +import platform + +from modules.sysinfo_lib import camelCase + + +try: + import pkg_resources + + loaded_pkg_resources = True +except: + loaded_pkg_resources = False + + +def python_pip_packages(to_camelcase): + output = {} + + if loaded_pkg_resources: + for pkg in pkg_resources.working_set: + package = {} + for key in [ + "location", + "project_name", + "key", + "version", + "parsed_version", + "py_version", + "platform", + "precedence", + ]: + if hasattr(pkg, key): + key_case = camelCase(key, to_camelcase) + package[key_case] = str(getattr(pkg, key)) + + if "key" in package: + output[package["key"]] = package + + return {"output": output, "unprocessed": []} + + +def python_platform(to_camelcase): + output = { + "architecture": platform.architecture(), + "machine": platform.machine(), + "node": platform.node(), + "platform": { + "normal": platform.platform(), + "aliased": platform.platform(aliased=True), + "terse": platform.platform(terse=True), + }, + "processor": platform.processor(), + "python": { + "branch": platform.python_branch(), + "build": platform.python_build(), + "compiler": platform.python_compiler(), + "implementation": platform.python_implementation(), + "revision": platform.python_revision(), + "version": platform.python_version(), + "versionTuple": platform.python_version_tuple(), + }, + "release": platform.release(), + "system": platform.system(), + "version": platform.version(), + "uname": platform.uname(), + } + return {"output": output, "unprocessed": []} + + +def register(main): + if loaded_pkg_resources: + main.register( + { + "name": "python_pip_packages", + "system": ["linux"], + "function": python_pip_packages, + "description": "List available python modules", + } + ) + + main.register( + { + "name": "python_platform", + "system": ["linux"], + "function": python_platform, + "description": "Probe the underlying platform's hardware, operating system, and Python interpreter version information", + } + ) diff --git a/src/sysinfo/modules/route.py b/src/sysinfo/modules/route.py new file mode 100644 index 0000000..ba9fe07 --- /dev/null +++ b/src/sysinfo/modules/route.py @@ -0,0 +1,26 @@ +from sysinfo_lib import parseTable + + +def parser(stdout, stderr, to_camelcase): + output = {} + + if stdout: + output = parseTable( + stdout, + header_pattern=r"^(Destination\s*)(\sGateway\s*)(\sGenmask\s*)(\sFlags\s*)(\sMetric\s*)(\sRef\s)(\s*Use)(\sIface\s*)(\sMSS\s*)(\sWindow\s*)(\sirtt\s*)", + to_camelcase=to_camelcase, + ) + + return {"output": output, "unprocessed": []} + + +def register(main): + main.register( + { + "name": "route", + "system": ["linux"], + "cmd": "route -ee", + "description": "IP routing table", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/rpm.py b/src/sysinfo/modules/rpm.py new file mode 100644 index 0000000..dc968d0 --- /dev/null +++ b/src/sysinfo/modules/rpm.py @@ -0,0 +1,35 @@ +from sysinfo_lib import parseCharDelimitedTable, tableToDict + + +def parser(stdout, stderr, to_camelcase): + output = {} + columns = [ + "installtime", + "buildtime", + "name", + "version", + "release", + "arch", + "vendor", + "packager", + "distribution", + "disttag", + ] + + if stdout: + output = parseCharDelimitedTable(stdout, "|", columns) + output = tableToDict(output, "name") + + return {"output": output, "unprocessed": []} + + +def register(main): + main.register( + { + "name": "rpm", + "system": ["linux"], + "cmd": 'rpm -q -a --queryformat "%{INSTALLTIME}|%{BUILDTIME}|%{NAME}|%{VERSION}|%{RELEASE}|%{arch}|%{VENDOR}|%{PACKAGER}|%{DISTRIBUTION}|%{DISTTAG}\n"', + "description": "Querying all RPM packages", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/services_status.py b/src/sysinfo/modules/services_status.py new file mode 100644 index 0000000..d0bc2ab --- /dev/null +++ b/src/sysinfo/modules/services_status.py @@ -0,0 +1,58 @@ +import re +from sysinfo_lib import parseTable, tableToDict, camelCase + + +def parser_services(stdout, stderr, to_camelcase): + output = parseTable(stdout, to_camelcase=to_camelcase) + output = tableToDict(output, "unit") + + return {"output": output, "unprocessed": []} + + +def parser_services_params(stdout, stderr, to_camelcase): + output = {} + service = None + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + service_search = re.search(r"^>>>\s*Service:\s*(.*)$", line) + if service_search: + service = service_search.group(1).strip() + output[service] = {} + continue + + if service: + kv = re.search(r"^([^=]+)=(.*)$", line) + if kv: + key = camelCase(kv.group(1), to_camelcase) + value = kv.group(2).strip() + + output[service][key] = value + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "services_list", + "system": ["linux"], + "cmd": """systemctl -l --type service --all --plain | grep -i -e ".service\|description" | sed 's/^\s*//g' """, + "description": "Displays services with status", + "parser": parser_services, + } + ) + + main.register( + { + "name": "services_params", + "system": ["linux"], + "cmd": """systemctl -l --type service --all --plain | sed -E 's/^\s*(\\S+.service).*$/\\1/g' | grep -i -e ".service" | xargs -I '{}' sh -c "echo '>>> Service: {}'; systemctl show {} --no-page" """, + "description": "Displays services with params", + "parser": parser_services_params, + } + ) diff --git a/src/sysinfo/modules/sysctl.py b/src/sysinfo/modules/sysctl.py new file mode 100644 index 0000000..0d482e6 --- /dev/null +++ b/src/sysinfo/modules/sysctl.py @@ -0,0 +1,59 @@ +import re +from sysinfo_lib import camelCase + + +def set_path_value(data, path, value, to_camelcase): + pathRest = None + pathParts = re.search(r"^([^\.]+)\.?(.*)$", path) + if pathParts: + path = camelCase(pathParts.group(1), to_camelcase) + pathRest = pathParts.group(2) + + if not path in data: + data[path] = {} + + if pathRest: + set_path_value(data[path], pathRest, value, to_camelcase) + else: + data[path] = value + + +def parser(stdout, stderr, to_camelcase): + output = {} + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + kv = re.search(r"^([^=]+)=(.*)$", line) + if kv: + key = kv.group(1).strip() + value = kv.group(2).strip() + + set_path_value(output, key, value, to_camelcase) + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "sysctl", + "system": ["linux"], + "cmd": "sysctl -a -e", + "description": "Runtime kernel parameters", + "parser": parser, + } + ) + + main.register( + { + "name": "sysctl_system", + "system": ["linux"], + "cmd": "sysctl -a -e --system", + "description": "Runtime kernel parameters from all system configuration files", + "parser": parser, + } + ) diff --git a/modules/sysinfo_lib.py b/src/sysinfo/modules/sysinfo_lib.py similarity index 52% rename from modules/sysinfo_lib.py rename to src/sysinfo/modules/sysinfo_lib.py index 2bdf4cd..ace01be 100644 --- a/modules/sysinfo_lib.py +++ b/src/sysinfo/modules/sysinfo_lib.py @@ -3,23 +3,39 @@ from struct import pack, unpack PY2 = sys.version_info[0] == 2 -PY3 = sys.version_info[0] == 3 +PY3 = sys.version_info[0] == 3 + def sortedList(st): values = list(set(st.splitlines())) values.sort() return values -def camelCase(st): - output = ''.join(x for x in st.title() if x.isalnum()) + +def camelCase_cb(matchobj): + return " ".join([matchobj.group(1), matchobj.group(2)]) + + +def camelCase(st, to_camelcase): + if not to_camelcase: + return st + + st = re.sub(r"([a-z])([A-Z])", camelCase_cb, st) + + output = "".join(x for x in st.title() if x.isalnum()) if len(output) == 1: return output.lower() + elif len(output) > 1: return output[0].lower() + output[1:] + else: - return '' + return "" + -def parseTable(input, headerPattern = None, endPattern = None, ignoreEmpty = True): +def parseTable( + input, header_pattern=None, end_pattern=None, ignore_empty=True, to_camelcase=False +): output = [] colNames = [] colLengths = [] @@ -29,53 +45,61 @@ def parseTable(input, headerPattern = None, endPattern = None, ignoreEmpty = Tru if len(lines) == 0: return output - while len(lines) > 0 and lines[0].strip() == '': + while len(lines) > 0 and lines[0].strip() == "": lines.pop(0) - if headerPattern: - while len(lines) > 0 and not re.search(headerPattern, lines[0], re.IGNORECASE): + if header_pattern: + while len(lines) > 0 and not re.search(header_pattern, lines[0], re.IGNORECASE): lines.pop(0) if len(lines) == 0: return output - header = re.search(headerPattern, lines.pop(0), re.IGNORECASE) + header = re.search(header_pattern, lines.pop(0), re.IGNORECASE) if header: header = header.groups() else: - header = re.findall(r'(\S+\s*)', lines.pop(0), re.IGNORECASE) + header = re.findall(r"(\S+\s*)", lines.pop(0), re.IGNORECASE) if header: for value in header: - colNames.append(camelCase(value.strip())) + colNames.append(camelCase(value.strip(), to_camelcase)) colLengths.append(len(value)) if len(colNames) > 0: colLengths[-1] = 8192 totalLength = sum(colLengths) - packTemplate = ''.join([str(s) + 's' for s in colLengths]) + packTemplate = "".join([str(s) + "s" for s in colLengths]) for line in lines: - if ignoreEmpty == True and line.strip() == '': + if ignore_empty == True and line.strip() == "": continue + row = {} - if endPattern and re.match(endPattern, line): + if end_pattern and re.match(end_pattern, line): break + if PY2: - cols = unpack(packTemplate, line + (' ' * (totalLength - len(line)))) + cols = unpack(packTemplate, line + (" " * (totalLength - len(line)))) else: - cols = unpack(packTemplate, bytes(line + (' ' * (totalLength - len(line))), 'utf-8')) + cols = unpack( + packTemplate, + bytes(line + (" " * (totalLength - len(line))), "utf-8"), + ) + for num, val in enumerate(cols, start=0): if PY2: row[colNames[num]] = val.strip() else: - row[colNames[num]] = str(val.strip(), 'utf-8') + row[colNames[num]] = str(val.strip(), "utf-8") + output.append(row) return output -def parseSpaceTable(input, ignoreEmpty = True): + +def parseSpaceTable(input, ignore_empty=True, to_camelcase=False): output = [] colNames = [] lines = input.splitlines() @@ -83,27 +107,29 @@ def parseSpaceTable(input, ignoreEmpty = True): if len(lines) == 0: return output - while len(lines) > 0 and lines[0].strip() == '': + while len(lines) > 0 and lines[0].strip() == "": lines.pop(0) - header = re.findall(r'(\S+[\s\t]*)', lines.pop(0), re.IGNORECASE) + header = re.findall(r"(\S+[\s\t]*)", lines.pop(0), re.IGNORECASE) if header: for value in header: - colNames.append(camelCase(value.strip())) + colNames.append(camelCase(value.strip(), to_camelcase)) if len(colNames) > 0: for line in lines: - if ignoreEmpty == True and line.strip() == '': + if ignore_empty == True and line.strip() == "": continue row = {} - cols = re.split(r'\s+', line.strip()) + cols = re.split(r"\s+", line.strip()) for num, val in enumerate(cols, start=0): if num < len(colNames): row[colNames[num]] = val.strip() + output.append(row) return output + def parseCharDelimitedTable(input, delimiter, columnsNames): output = [] if input: @@ -117,6 +143,7 @@ def parseCharDelimitedTable(input, delimiter, columnsNames): output.append(row) return output + def tableToDict(input, key): output = {} @@ -126,5 +153,31 @@ def tableToDict(input, key): output[row[key]] = row else: return input - + + return output + + +def fixMultilineAndSplit(data, match, delimiter): + output = [] + last_line = None + + for line in data.splitlines(): + if last_line == None: + last_line = line + continue + + if line.strip() == "": + continue + + if re.match(r"^\s+", line): + line = line.strip() + last_line = f"{last_line}{delimiter}{line}" + + else: + output.append(last_line) + last_line = line + + if not last_line == None: + output.append(last_line) + return output diff --git a/src/sysinfo/modules/tasklist.py b/src/sysinfo/modules/tasklist.py new file mode 100644 index 0000000..55bee38 --- /dev/null +++ b/src/sysinfo/modules/tasklist.py @@ -0,0 +1,82 @@ +import re +from sysinfo_lib import camelCase, fixMultilineAndSplit + + +def parser_fo(stdout, stderr, to_camelcase): + output = [] + unprocessed = [] + image = {} + + if stdout: + data = fixMultilineAndSplit(stdout, r"^\s+", ", ") + + for line in data: + if line.strip() == "": + continue + + kv = re.search(r"^([^:]+):\s*(.*)$", line) + if kv: + key = camelCase(kv.group(1).strip(), to_camelcase) + value = kv.group(2).strip().replace("\u00a0", "") + + if key == "imageName" or key == "Image Name": + image = {} + output.append(image) + + if ( + key == "modules" + or key == "Modules" + or key == "services" + or key == "Services" + ): + value = value.split(", ") + + image[key] = value + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + + main.register( + { + "name": "tasklist", + "system": ["windows"], + "cmd": "%SystemRoot%\\system32\\tasklist.exe /fo list", + "description": "Get currently running processes", + "parser": parser_fo, + } + ) + + main.register( + { + "name": "tasklist_services", + "system": ["windows"], + "cmd": "%SystemRoot%\\system32\\tasklist.exe /svc /fo list", + "description": "Get services hosted in each process", + "parser": parser_fo, + } + ) + + main.register( + { + "name": "tasklist_apps", + "system": ["windows"], + "cmd": "%SystemRoot%\\system32\\tasklist.exe /apps /fo list", + "description": "Get services hosted in each process", + "parser": parser_fo, + } + ) + + main.register( + { + "name": "tasklist_modules", + "system": ["windows"], + "cmd": "%SystemRoot%\\system32\\tasklist.exe /m /fo list", + "description": "Get modules loaded in each process", + "parser": parser_fo, + } + ) diff --git a/src/sysinfo/modules/timedatectl.py b/src/sysinfo/modules/timedatectl.py new file mode 100644 index 0000000..7e3f338 --- /dev/null +++ b/src/sysinfo/modules/timedatectl.py @@ -0,0 +1,43 @@ +import re +from sysinfo_lib import camelCase + + +def parser(stdout, stderr, to_camelcase): + output = {} + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + lineMatch = re.search(r"^([^:]+):\s*(.*)", line) + if lineMatch: + key = camelCase(lineMatch.group(1).strip(), to_camelcase) + value = lineMatch.group(2).strip() + + output[key] = value + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "timedatectl", + "system": ["linux"], + "cmd": "timedatectl status", + "description": "System time and date", + "parser": parser, + } + ) + + main.register( + { + "name": "timedatectl_timesync", + "system": ["linux"], + "cmd": "timedatectl timesync-status", + "description": "Status of systemd-timesyncd.service", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/udevadm.py b/src/sysinfo/modules/udevadm.py new file mode 100644 index 0000000..a9e24bf --- /dev/null +++ b/src/sysinfo/modules/udevadm.py @@ -0,0 +1,149 @@ +import re +from sysinfo_lib import camelCase + + +def parse_looking_entry(line, entry, to_camelcase): + key_value = re.search(r'^\s+([^=]+)=="([^"]*)"', line) + if key_value: + key = key_value.group(1).lower() + value = key_value.group(2) + + multiple_values = re.match(r"^(\s+\d+)+$", value) + if multiple_values: + value = re.split(r"\s+", value.strip()) + + key_attr = re.match(r"^(\S+){([^}]+)}", key, re.IGNORECASE) + if key_attr: + attrType = camelCase(key_attr.group(1), to_camelcase) + + if not attrType in entry: + entry[attrType] = {} + + path = key_attr.group(2).split("/") + + if len(path) == 1: + key_case = camelCase(path[0], to_camelcase) + entry[attrType][key_case] = value + + else: + sub_entry = entry[attrType] + + for part in path[0:-1]: + part_case = camelCase(part, to_camelcase) + + if not part_case in sub_entry: + sub_entry[part_case] = {} + + sub_entry = sub_entry[part_case] + + key_case = camelCase(path[-1], to_camelcase) + sub_entry[key_case] = value + + else: + entry[key] = value + + return True + + return False + + +def parser(stdout, stderr, to_camelcase): + output = {"devices": {}, "parents": {}} + types = {"P": "path", "N": "node", "L": "linkPriority", "E": "entry", "S": "link"} + device = None + parent = None + looking_entry = None + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + deviceSearch = re.search(r"^>>> Device: (\S+)", line) + if deviceSearch: + device = deviceSearch.group(1) + output["devices"][device] = {"parents": [], "entry": {}, "link": []} + parent = None + continue + + if not device: + continue + + if line.strip() == "": + looking_entry = None + continue + + if re.match( + r"^.*(Udevadm info starts|chain of parent|the udev rules|rule to match|single parent device)", + line, + ): + continue + + keyValue = re.search(r"^(\S):\s+(.*)$", line) + if keyValue: + key = keyValue.group(1) + value = keyValue.group(2).strip() + if key in types: + key = types[key] + + if key == "entry": + valueSearch = re.search(r"^([^=]+)=(.*)$", value) + if valueSearch: + subkey = camelCase(valueSearch.group(1), to_camelcase) + subvalue = valueSearch.group(2).strip() + output["devices"][device]["entry"][subkey] = subvalue + + elif key == "link": + output["devices"][device]["link"].append(value) + + else: + output["devices"][device][key] = value + + continue + + deviceLook = re.search(r"^\s+looking at device \'([^\']+)", line) + if deviceLook: + looking_entry = output["devices"][device] + continue + + parentDeviceLook = re.search( + r"^\s+looking at parent device \'([^\']+)", line + ) + if parentDeviceLook: + parent = parentDeviceLook.group(1) + output["devices"][device]["parents"].append(parent) + + if not parent in output["parents"]: + output["parents"][parent] = {} + + looking_entry = output["parents"][parent] + continue + + if isinstance(looking_entry, dict): + processed = parse_looking_entry(line, looking_entry, to_camelcase) + if processed: + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "udevadm", + "system": ["linux"], + "cmd": """udevadm info --export-db | grep "DEVNAME" | cut -d "=" -f2 | xargs -n 1 -I {} sh -c "echo '>>> Device: {}'; udevadm info --query=all --name={}; udevadm info --attribute-walk --name={}" """, + "description": "Queries the udev database for device information stored in the udev database", + "parser": parser, + } + ) + + main.register( + { + "name": "udevadm_block_devices", + "system": ["linux"], + "cmd": """find /dev/ -type b | xargs -n 1 -I {} sh -c "echo '>>> Device: {}'; udevadm info --query=all --name={}; udevadm info --attribute-walk --name={}" """, + "description": "Queries the udev database for block device information stored in the udev database", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/uname.py b/src/sysinfo/modules/uname.py new file mode 100644 index 0000000..c58e4d9 --- /dev/null +++ b/src/sysinfo/modules/uname.py @@ -0,0 +1,93 @@ +import re + + +def parser(stdout, stderr, to_camelcase): + output = "" + + if stdout: + output = re.sub(r"\n|\r|\r\n", "", stdout) + output = stdout.strip() + + return {"output": output, "unprocessed": []} + + +def register(main): + main.register( + { + "name": "kernel_name", + "system": ["linux"], + "cmd": "uname -s", + "description": "Kernel name (uname)", + "parser": parser, + } + ) + + main.register( + { + "name": "kernel_release", + "system": ["linux"], + "cmd": "uname -r", + "description": "Kernel release (uname)", + "parser": parser, + } + ) + + main.register( + { + "name": "kernel_version", + "system": ["linux"], + "cmd": "uname -v", + "description": "Kernel version (uname)", + "parser": parser, + } + ) + + main.register( + { + "name": "nodename", + "system": ["linux"], + "cmd": "uname -n", + "description": "Network node hostname (uname)", + "parser": parser, + } + ) + + main.register( + { + "name": "machine", + "system": ["linux"], + "cmd": "uname -m", + "description": "Machine hardware name (uname)", + "parser": parser, + } + ) + + main.register( + { + "name": "processor", + "system": ["linux"], + "cmd": "uname -p", + "description": "Processor type (uname)", + "parser": parser, + } + ) + + main.register( + { + "name": "hardware_platform", + "system": ["linux"], + "cmd": "uname -i", + "description": "Hardware platform (uname)", + "parser": parser, + } + ) + + main.register( + { + "name": "operating_system", + "system": ["linux"], + "cmd": "uname -o", + "description": "Operating system (uname)", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/update_alternatives.py b/src/sysinfo/modules/update_alternatives.py new file mode 100644 index 0000000..60d8dc0 --- /dev/null +++ b/src/sysinfo/modules/update_alternatives.py @@ -0,0 +1,35 @@ +import re + + +def parser(stdout, stderr, to_camelcase): + output = [] + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + lineMatch = re.search(r"^(\S+)\s+(.*)\s+(\S+)$", line) + if lineMatch: + output.append( + { + "name": lineMatch.group(1).strip(), + "mode": lineMatch.group(2).strip(), + "link": lineMatch.group(3).strip(), + } + ) + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def register(main): + main.register( + { + "name": "update_alternatives", + "system": ["linux"], + "cmd": "update-alternatives --get-selections", + "description": "Symbolic links determining default commands", + "parser": parser, + } + ) diff --git a/src/sysinfo/modules/vmstat.py b/src/sysinfo/modules/vmstat.py new file mode 100644 index 0000000..c556f89 --- /dev/null +++ b/src/sysinfo/modules/vmstat.py @@ -0,0 +1,160 @@ +import re +import sys +from struct import pack, unpack +from sysinfo_lib import camelCase + +PY2 = sys.version_info[0] == 2 +PY3 = sys.version_info[0] == 3 + + +def parser_stats(stdout, stderr, to_camelcase): + output = {} + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + entry = re.search(r"^\s*(\d+)\s+(.*)$", line) + if entry: + key = camelCase(entry.group(2).strip(), to_camelcase) + value = entry.group(1).strip() + output[key] = value + + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def parser_disk(stdout, stderr, to_camelcase): + output = {} + sectionsNames = [] + sectionsMask = "" + totalLength = 0 + columnPaths = [] + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + if re.match(r"disk.*reads", line, re.IGNORECASE): + topHeader = re.split(r"\s+", line) + if topHeader: + for value in topHeader: + sectionsNames.append(value.strip().strip("-").lower()) + totalLength += len(value) + 1 + sectionsMask += str(len(value) + 1) + "s" + continue + + if sectionsMask: + if re.match(r".*total.*merged.*sectors.*", line): + lineFix = line + (" " * (totalLength - len(line))) + if PY3: + lineFix = bytes(lineFix, "utf-8") + + sectionData = unpack(sectionsMask, lineFix) + if sectionData: + for index, sec in enumerate(sectionData): + if PY2: + secStrip = sec.strip() + else: + secStrip = str(sec.strip(), "utf-8") + + topColumns = re.split(r"\s+", secStrip) + for column in topColumns: + if column: + columnPaths.append( + camelCase( + "%s %s" + % ( + sectionsNames[index], + column, + ), + to_camelcase, + ) + ) + else: + columnPaths.append("%s" % (sectionsNames[index],)) + + else: + entry = {} + columns = re.split(r"\s+", line) + for ci, cv in enumerate(columns): + if ci < len(columnPaths): + entry[columnPaths[ci]] = cv + + if "disk" in entry: + output[entry["disk"]] = entry + + return {"output": output, "unprocessed": unprocessed} + + +def parser_disk_sum(stdout, stderr, to_camelcase): + output = {} + unprocessed = [] + + if stdout: + for line in stdout.splitlines(): + entry = re.search(r"^\s*(\d+)\s+(.*)$", line) + if entry: + key = camelCase(entry.group(2).strip(), to_camelcase) + value = entry.group(1).strip() + + output[key] = value + continue + + unprocessed.append(line) + + return {"output": output, "unprocessed": unprocessed} + + +def parser_forks(stdout, stderr, to_camelcase): + output = {} + + if stdout: + forks = re.search(r"\s*(\d+)\s*forks", stdout) + if forks: + output["forks"] = forks.group(1) + + return {"output": output, "unprocessed": []} + + +def register(main): + main.register( + { + "name": "vmstat_stats", + "system": ["linux"], + "cmd": "vmstat -s", + "description": "Displays a table of various event counters and memory statistics", + "parser": parser_stats, + } + ) + + main.register( + { + "name": "vmstat_disk", + "system": ["linux"], + "cmd": "vmstat -dwn", + "description": "Report disk statistics", + "parser": parser_disk, + } + ) + + main.register( + { + "name": "vmstat_disk_sum", + "system": ["linux"], + "cmd": "vmstat -D", + "description": "Report some summary statistics about disk activity", + "parser": parser_disk_sum, + } + ) + + main.register( + { + "name": "vmstat_forks", + "system": ["linux"], + "cmd": "vmstat -f", + "description": "Displays the number of forks since boot", + "parser": parser_forks, + } + ) diff --git a/src/sysinfo/modules/yum.py b/src/sysinfo/modules/yum.py new file mode 100644 index 0000000..03c62b1 --- /dev/null +++ b/src/sysinfo/modules/yum.py @@ -0,0 +1,90 @@ +import re + + +def parser_repolist(stdout, stderr, to_camelcase): + output = {"mirrors": [], "repos": [], "errors": []} + insideTable = False + if stdout: + for line in stdout.splitlines(): + mirrors = re.search(r"^\s*\*\s*([^:]+):\s*(.*)$", line) + if mirrors: + output["mirrors"].append( + {"repo": mirrors.group(1).strip(), "host": mirrors.group(2).strip()} + ) + + tableHeader = re.search( + r"^repo id\s+repo name\s+status", line, re.IGNORECASE + ) + tableRow = re.search( + r"^([^\/]+)\/([^\/]+)\/(.*)\s\s+(\S+.*)\s\s+(\S+.*)$", line + ) + if tableHeader: + insideTable = True + + elif insideTable and tableRow: + output["repos"].append( + { + "repo": tableRow.group(1).strip(), + "version": tableRow.group(2).strip(), + "arch": tableRow.group(3).strip(), + "repo_name": tableRow.group(4).strip(), + "status": tableRow.group(5).strip(), + } + ) + + else: + insideTable = False + + if stderr: + for line in stderr.splitlines(): + if re.search(r"http.*error .*", line, re.IGNORECASE): + if not line in output["errors"]: + output["errors"].append(line) + + return {"output": output} + + +def parser_installed(stdout, stderr, to_camelcase): + output = {"packages": [], "errors": []} + if stdout: + for line in stdout.splitlines(): + package = re.search(r"^([\S\.]+)\.(\S+)\s+(\S+\.\S+)\s+(\S+.*)$", line) + if package: + output["packages"].append( + { + "name": package.group(1).strip(), + "arch": package.group(2).strip(), + "version": package.group(3).strip(), + "status": package.group(4).strip(), + } + ) + + if stderr: + for line in stderr.splitlines(): + if re.search(r"http.*error .*", line, re.IGNORECASE): + if not line in output["errors"]: + output["errors"].append(line) + + return {"output": output} + + +def register(main): + main.register( + { + "name": "yum_repolist", + "system": ["linux"], + "cmd": "yum repolist all", + "description": "YUM - defined repositories", + "parser": parser_repolist, + } + ) + + main.register( + { + "name": "yum_installed", + "system": ["linux"], + "cmd": "yum list installed", + "description": "YUM - list installed packages", + "parser": parser_installed, + } + ) diff --git a/src/sysinfo/sysinfo.py b/src/sysinfo/sysinfo.py new file mode 100644 index 0000000..e324243 --- /dev/null +++ b/src/sysinfo/sysinfo.py @@ -0,0 +1,422 @@ +#!/usr/bin/python3 + +""" + * + * sysinfo - Python based scripts for obtaining system information from Linux. + * + * Petr Vavrin (peterbay) pvavrin@gmail.com + * https://github.com/peterbay + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * +""" + +import os +import sys +import glob +import json +import locale +import argparse +import platform +import subprocess +from threading import Timer +from multiprocessing import Pool +from os.path import dirname, basename, isfile, join + +sys.path.append(join(dirname(__file__), "modules")) + + +class systemInfoModules: + modules = {} + + def __init__(self, system): + self.system = system + + def register(self, definition): + if "system" in definition: + if not self.system in definition["system"]: + return + + if not "name" in definition: + return + + self.modules[definition["name"]] = definition + + def items(self): + return self.modules.items() + + +siModules = None +PY2 = sys.version_info[0] == 2 +PY3 = sys.version_info[0] == 3 + + +def loadModules(): + global siModules + global PY2 + global PY3 + modules = glob.glob(join(dirname(__file__), "modules", "*.py")) + for f in modules: + if isfile(f) and not f.endswith("__init__.py"): + if PY2: + import imp + + lib = imp.load_source(basename(f)[:-3], f) + if hasattr(lib, "register"): + lib.register(siModules) + else: + from importlib import import_module + + lib = __import__(basename(f)[:-3]) + if hasattr(lib, "register"): + lib.register(siModules) + + +def readFile(pathToFile): + try: + f = open(pathToFile, "r") + content = f.read() + f.close() + return content, None + + except Exception as err: + return None, err + + +def writeToFile(pathToFile, content): + try: + f = open(pathToFile, "w") + f.write(content) + f.close() + + except Exception as err: + sys.stdout.write("ERROR: Can't write to file '%s': %s\n" % (pathToFile, err)) + + +def pathCheck(args): + if args.export_dir: + if not os.access(args.export_dir, os.W_OK): + sys.stdout.write( + "ERROR: Export directory '%s' not exist or is not writable\n" + % (args.export_dir,) + ) + exit(1) + + if args.import_dir: + if not os.access(args.import_dir, os.R_OK): + sys.stdout.write( + "ERROR: Import directory '%s' not exist or is not readable\n" + % (args.import_dir,) + ) + exit(1) + + +def kill(process): + return process.kill() + + +def executeCmd(cmd): + proc = None + outs = None + errs = None + + try: + command = cmd.get("cmd", None) + if not command: + cmd["error"] = "Empty command" + return + + system = cmd.get("system", None) + if system: + if system == "windows": + subprocess.call( + ["chcp", "65001"], + shell=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + proc = subprocess.Popen( + command, + shell=True, + # executable="/usr/bin/bash", + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + cmdTimer = Timer(int(cmd.get("timeout", 30)), kill, [proc]) + + try: + cmdTimer.start() + outs, errs = proc.communicate() + + except Exception as err: + proc.kill() + outs, errs = proc.communicate() + cmd["error"] = str(err) + + finally: + cmdTimer.cancel() + + except Exception as err: + cmd["error"] = str(err) + + if proc: + procPoll = proc.poll() + if procPoll != 0: + cmd["rc"] = procPoll + if not "error" in cmd: + cmd["error"] = "Unknown error" + + if PY2: + cmd["stdout"] = outs + cmd["stderr"] = errs + else: + try: + cmd["stdout"] = str(outs, "utf-8") if outs else None + cmd["stderr"] = str(errs, "utf-8") if errs else None + except Exception as e: + cmd["error"] = str(e) + + +def execute(cmd): + if not isinstance(cmd, dict): + return {} + + if "import_dir" in cmd: + commandImportPath = join(cmd.get("import_dir", ""), cmd.get("name", "")) + cmd["stdout"], cmd["stderr"] = readFile(commandImportPath) + + else: + outs, errs = "", "" + to_camelcase = cmd.get("to_camelcase", None) + + if "function" in cmd: + moduleFunction = cmd.get("function", None) + if moduleFunction and callable(moduleFunction): + outs = moduleFunction(to_camelcase) + + cmd["stdout"] = outs + cmd["stderr"] = errs + return cmd + + elif "cmd" in cmd: + executeCmd(cmd) + + return cmd + + +def run(args): + poolSize = int(args.pool) + loadModules() + p = Pool(poolSize) + selectedModules = [] + executeModules = False + results = {} + + to_camelcase = args.camel_case + + for name, settings in sorted(siModules.items()): + if args.list: + sys.stdout.write( + "%-25s - %s\n" + % ( + name, + settings.get("description", ""), + ) + ) + + elif args.info: + if "function" in settings: + info = "built-in function" + else: + info = settings.get("cmd", "") + + sys.stdout.write( + "%-25s - %s\n" + % ( + name, + info, + ) + ) + + elif args.all or name in args.commands: + settings["name"] = name + if args.import_dir: + settings["import_dir"] = args.import_dir + + settings["to_camelcase"] = to_camelcase + settings["system"] = args.system + selectedModules.append(settings) + executeModules = True + + if executeModules: + for result in p.map(execute, selectedModules): + name = result.get("name", None) + if not name: + continue + + if args.error and not result.get("error", None): + continue + + if args.export_dir: + commandExportPath = join(args.export_dir, name) + writeToFile(commandExportPath, result.get("stdout", None)) + + if args.export_only: + continue + + parser = result.get("parser", None) + if parser and callable(parser): + try: + parsed = parser( + result.get("stdout", None), + result.get("stderr", None), + to_camelcase, + ) + + if isinstance(parsed, dict): + result["output"] = parsed.get("output", None) + result["unprocessed"] = parsed.get("unprocessed", None) + + except Exception as err: + result["parser_error"] = str(err) + + else: + stdout = result.get("stdout", None) + if isinstance(stdout, dict): + result["output"] = stdout.get("output", None) + result["unprocessed"] = stdout.get("unprocessed", None) + + else: + result["output"] = stdout + result["unprocessed"] = [] + + result.pop("parser", None) + result.pop("function", None) + + if not args.verbose and not args.error: + result.pop("stdout", None) + result.pop("stderr", None) + result.pop("rc", None) + result.pop("cmd", None) + result.pop("description", None) + result.pop("name", None) + result.pop("unprocessed", None) + result.pop("import_dir", None) + result.pop("system", None) + + results[name] = result + + if not args.export_only: + if args.output: + writeToFile(args.output, json.dumps(results, indent=4, sort_keys=True)) + + else: + sys.stdout.write(json.dumps(results, indent=4, sort_keys=True) + "\n") + + elif not args.list and not args.info: + sys.stdout.write("No commands to execute\n") + + +def argsError(error): + pass + + +def main(argv): + global siModules + parser = argparse.ArgumentParser() + parser.error = argsError + + parser.add_argument( + "--all", "-a", action="store_true", default=False, help="Execute all commands." + ) + + parser.add_argument( + "--camel-case", + "-c", + action="store_true", + default=False, + help="Convert keys to CamelCase.", + ) + + parser.add_argument( + "--error", + "-e", + action="store_true", + default=False, + help="Show only error outputs from commands.", + ) + + parser.add_argument( + "--export-only", + action="store_true", + default=False, + help="Export output from commands without processing.", + ) + + parser.add_argument( + "--export-dir", help="Path to the directory for saving output from commands." + ) + + parser.add_argument( + "--import-dir", + help="Path to the directory for reading the stored outputs of commands.", + ) + + parser.add_argument( + "--info", + "-i", + action="store_true", + default=False, + help="List all commands with command line arguments.", + ) + + parser.add_argument( + "--list", "-l", action="store_true", default=False, help="List all commands." + ) + + parser.add_argument("--output", "-o", help="Path to the output file.") + + parser.add_argument( + "--pool", + "-p", + default="5", + type=int, + help="Pool size for parallel execution of commands. (default value is 5)", + ) + + parser.add_argument( + "--system", + "-s", + default=platform.system().lower(), + help="Execute or parse commands for selected system [linux, darwin, java, windows].", + ) + + parser.add_argument( + "--verbose", + "-v", + action="store_true", + default=False, + help="Add more info to output - options, commands, raw command result.", + ) + + parser.add_argument("commands", nargs="*", help="Commands") + args = parser.parse_args() + pathCheck(args) + siModules = systemInfoModules(args.system) + run(args) + + +if __name__ == "__main__": + main(sys.argv) diff --git a/src/tests/__init__.py b/src/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sysinfo.py b/sysinfo.py deleted file mode 100644 index d8bfa98..0000000 --- a/sysinfo.py +++ /dev/null @@ -1,255 +0,0 @@ -""" - * - * sysinfo - Python based scripts for obtaining system information from Linux. - * - * Petr Vavrin (peterbay) pvavrin@gmail.com - * https://github.com/peterbay - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * -""" - -import os -import sys -import glob -import json -import argparse -import subprocess -from threading import Timer -from multiprocessing import Pool -from os.path import dirname, basename, isfile, join - -sys.path.append(join(dirname(__file__), 'modules')) - -siModules = {} -PY2 = sys.version_info[0] == 2 -PY3 = sys.version_info[0] == 3 - -def loadModules(): - global siModules - global PY2 - global PY3 - modules = glob.glob(join(dirname(__file__), 'modules', '*.py')) - for f in modules: - if isfile(f) and not f.endswith('__init__.py'): - if PY2: - import imp - lib = imp.load_source(basename(f)[:-3], f) - if hasattr(lib, 'register'): - lib.register(siModules) - else: - from importlib import import_module - lib = __import__(basename(f)[:-3]) - if hasattr(lib, 'register'): - lib.register(siModules) - -def readFile(pathToFile): - try: - f = open(pathToFile, 'r') - content = f.read() - f.close() - return content, None - - except Exception as err: - return None, err - -def writeToFile(pathToFile, content): - try: - f = open(pathToFile, 'w') - f.write(content) - f.close() - - except Exception as err: - sys.stdout.write('ERROR: Can\'t write to file \'%s\': %s\n' % (pathToFile, err)) - -def pathCheck(args): - if args.export_dir: - if not os.access(args.export_dir, os.W_OK): - sys.stdout.write('ERROR: Export directory \'%s\' not exist or is not writable\n' % (args.export_dir, )) - exit(1) - - if args.import_dir: - if not os.access(args.import_dir, os.R_OK): - sys.stdout.write('ERROR: Import directory \'%s\' not exist or is not readable\n' % (args.import_dir, )) - exit(1) - -def kill(process): - return process.kill() - -def executeCmd(cmd): - try: - proc = subprocess.Popen(cmd.get('cmd', ''), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - cmdTimer = Timer(int(cmd.get('timeout', 30)), kill, [proc]) - - try: - cmdTimer.start() - outs, errs = proc.communicate() - - except Exception as err: - proc.kill() - outs, errs = proc.communicate() - cmd['error'] = err - - finally: - cmdTimer.cancel() - - except Exception as err: - cmd['error'] = err - - if proc: - procPoll = proc.poll() - if procPoll != 0: - cmd['rc'] = procPoll - if not cmd.get('error', None): - cmd['error'] = 'error' - - if PY2: - cmd['stdout'] = outs - cmd['stderr'] = errs - else: - cmd['stdout'] = str(outs, 'utf-8') - cmd['stderr'] = str(errs, 'utf-8') - -def execute(cmd): - if not isinstance(cmd, dict): - return {} - - if 'import_dir' in cmd: - commandImportPath = join(cmd.get('import_dir', ''), cmd.get('name', '')) - cmd['stdout'], cmd['stderr'] = readFile(commandImportPath) - - else: - outs, errs = '', '' - - if 'function' in cmd: - moduleFunction = cmd.get('function', None) - if moduleFunction and callable(moduleFunction): - outs = moduleFunction() - - cmd['stdout'] = outs - cmd['stderr'] = errs - return cmd - - elif 'cmd' in cmd: - executeCmd(cmd) - - return cmd - -def run(args): - poolSize = int(args.pool) - loadModules() - p = Pool(poolSize) - selectedModules = [] - executeModules = False - results = {} - - for name, settings in sorted(siModules.items()): - if args.list: - sys.stdout.write('%-25s - %s\n' % (name, settings.get('description', ''), )) - - elif args.info: - if 'function' in settings: - info = 'built-in function' - else: - info = settings.get('cmd', '') - - sys.stdout.write('%-25s - %s\n' % (name, info, )) - - elif args.all or name in args.commands: - settings['name'] = name - if args.import_dir: - settings['import_dir'] = args.import_dir - selectedModules.append(settings) - executeModules = True - - if executeModules: - for result in p.map(execute, selectedModules): - name = result.get('name', None) - if not name: - continue - - if args.error and not result.get('error', None): - continue - - if args.export_dir: - commandExportPath = join(args.export_dir, name) - writeToFile(commandExportPath, result.get('stdout', None)) - - if args.export_only: - continue - - parser = result.get('parser', None) - if parser and callable(parser): - try: - parsed = parser(result.get('stdout', None), result.get('stderr', None)) - - if isinstance(parsed, dict): - result['output'] = parsed.get('output', None) - result['ignored'] = parsed.get('ignored', None) - except Exception as err: - result['parser_error'] = str(err) - - else: - result['output'] = result.get('stdout', None) - - result.pop('parser', None) - result.pop('function', None) - - if not args.verbose and not args.error: - result.pop('stdout', None) - result.pop('stderr', None) - result.pop('rc', None) - result.pop('cmd', None) - result.pop('description', None) - result.pop('name', None) - result.pop('ignored', None) - result.pop('import_dir', None) - - results[name] = result - - if not args.export_only: - if args.output: - writeToFile(args.output, json.dumps(results, indent=4, sort_keys=True)) - - else: - sys.stdout.write(json.dumps(results, indent=4, sort_keys=True) + '\n') - - elif not args.list and not args.info: - sys.stdout.write('No commands to execute\n') - -def argsError(error): - pass - -def main(argv): - parser = argparse.ArgumentParser() - parser.error = argsError - - parser.add_argument('--all', '-a', action='store_true', default=False, help='Execute all commands.') - parser.add_argument('--error', '-e', action='store_true', default=False, help='Show only error outputs from commands.') - parser.add_argument('--export-only', action='store_true', default=False, help='Export output from commands without processing.') - parser.add_argument('--export-dir', help='Path to the directory for saving output from commands.') - parser.add_argument('--import-dir', help='Path to the directory for reading the stored outputs of commands.') - parser.add_argument('--info', '-i', action='store_true', default=False, help='List all commands with command line arguments.') - parser.add_argument('--list', '-l', action='store_true', default=False, help='List all commands.') - parser.add_argument('--output', '-o', help='Path to the output file.') - parser.add_argument('--pool', '-p', default='5', type=int, help='Pool size for parallel execution of commands. (default value is 5)') - parser.add_argument('--verbose', '-v', action='store_true', default=False, help='Add more info to output - options, commands, raw command result.') - parser.add_argument('commands', nargs='*', help='Commands') - - args = parser.parse_args() - pathCheck(args) - run (args) - -if __name__ == '__main__': - main(sys.argv)