From 902e0195055099c9ab59567407e5e5a90e80e7e7 Mon Sep 17 00:00:00 2001 From: "Jose M. Guisado" Date: Mon, 18 Apr 2022 10:59:35 +0200 Subject: Add utils modules * disk.py Disk discovery * fs.py Uses psutil to fetch fs usage information * menu.py ogBrowser menu generation * net.py: gets nic status information IP address, MAC address and ethernet speed. * probe.py: probes mountpoints for operating systems Uses hivexget command to try fetching Windows installation information. Looks for /etc/os-release for probing linux systems. --- src/utils/disk.py | 19 +++++++++++++ src/utils/fs.py | 60 +++++++++++++++++++++++++++++++++++++++ src/utils/menu.py | 58 ++++++++++++++++++++++++++++++++++++++ src/utils/net.py | 53 ++++++++++++++++++++++++++++++++++ src/utils/probe.py | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 273 insertions(+) create mode 100644 src/utils/disk.py create mode 100644 src/utils/fs.py create mode 100644 src/utils/menu.py create mode 100644 src/utils/net.py create mode 100644 src/utils/probe.py diff --git a/src/utils/disk.py b/src/utils/disk.py new file mode 100644 index 0000000..4f11a82 --- /dev/null +++ b/src/utils/disk.py @@ -0,0 +1,19 @@ +# +# Copyright (C) 2022 Soleta Networks +# +# This program is free software: you can redistribute it and/or modify it under +# the terms of the GNU Affero General Public License as published by the +# Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. + +import os + +def get_disks(): + """ + Walks /sys/block/ and returns files starting with 'sd', + 'nvme' or 'vd' + """ + return sorted([ dev for dev in os.listdir('/sys/block/') + if dev.startswith('sd') + or dev.startswith('nvme') + or dev.startswith('vd')]) diff --git a/src/utils/fs.py b/src/utils/fs.py new file mode 100644 index 0000000..43011ef --- /dev/null +++ b/src/utils/fs.py @@ -0,0 +1,60 @@ +# +# Copyright (C) 2022 Soleta Networks +# +# This program is free software: you can redistribute it and/or modify it under +# the terms of the GNU Affero General Public License as published by the +# Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. + +import os +import subprocess + +from subprocess import DEVNULL + +import psutil + + +def mount_mkdir(source, target): + """ + Mounts and creates the mountpoint directory if it's not present. + """ + if not os.path.exists(target): + os.mkdir(target) + if not os.path.ismount(target): + return mount(source, target) + return False + + +def mount(source, target): + """ + Mounts source into target directoru using mount(8). + + Return true if exit code is 0. False otherwise. + """ + cmd = f'mount {source} {target}' + proc = subprocess.run(cmd.split(), stderr=DEVNULL) + + return not proc.returncode + + +def umount(target): + """ + Umounts target using umount(8). + + Return true if exit code is 0. False otherwise. + """ + cmd = f'umount {target}' + proc = subprocess.run(cmd.split(), stderr=DEVNULL) + + return not proc.returncode + + +def get_usedperc(mountpoint): + """ + Returns percetage of used filesystem as decimal number. + """ + try: + total, used, free, perc = psutil.disk_usage(mountpoint) + except FileNotFoundError: + return '0' + return str(perc) diff --git a/src/utils/menu.py b/src/utils/menu.py new file mode 100644 index 0000000..48e2641 --- /dev/null +++ b/src/utils/menu.py @@ -0,0 +1,58 @@ +# +# Copyright (C) 2022 Soleta Networks +# +# This program is free software: you can redistribute it and/or modify it under +# the terms of the GNU Affero General Public License as published by the +# Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. + +""" +Utility module for ogBrowser menu generation +""" + +import os +import socket + +from src.utils.net import getifaddr, getifhwaddr, ethtool + +MENU_TEMPLATE = """ +
+

+ + + + + +

Hostname IP MAC SPEED
{hostname} {ip} {mac} {speed}
+

+ +{boot_list} + +

Power Off

+
+""" + +MENU_OS_TEMPLATE = "

Boot {os} ({diskno}, {partno})

" + +def generate_menu(part_setup): + """ + Writes html menu to /opt/opengnsys/log/{ip}.info.html based on a partition + setup + """ + device = os.getenv('DEVICE') + if not device: + return False + + ip = getifaddr(device) + mac = getifhwaddr(device) + speed = ethtool(device) + hostname = socket.gethostname() + menufile = f'/opt/opengnsys/log/{ip}.info.html' + + l = [MENU_OS_TEMPLATE.format(diskno=part['disk'], partno=part['partition'], os=part['os']) + for part in part_setup if part['os']] + boot_list = '\n'.join(l) + + with open(menufile, 'w') as f: + f.write(MENU_TEMPLATE.format(hostname=hostname, ip=ip, mac=mac, speed=speed, boot_list=boot_list)) + return True diff --git a/src/utils/net.py b/src/utils/net.py new file mode 100644 index 0000000..01c6f04 --- /dev/null +++ b/src/utils/net.py @@ -0,0 +1,53 @@ +# +# Copyright (C) 2022 Soleta Networks +# +# This program is free software: you can redistribute it and/or modify it under +# the terms of the GNU Affero General Public License as published by the +# Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. + +import array +import fcntl +import socket +import struct + +def ethtool(interface): + try: + ETHTOOL_GSET = 0x00000001 + SIOCETHTOOL = 0x8946 + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sockfd = sock.fileno() + ecmd = array.array( + "B", struct.pack("I39s", ETHTOOL_GSET, b"\x00" * 39) + ) + interface = interface.encode("utf-8") + ifreq = struct.pack("16sP", interface, ecmd.buffer_info()[0]) + fcntl.ioctl(sockfd, SIOCETHTOOL, ifreq) + res = ecmd.tobytes() + speed = struct.unpack("12xH29x", res)[0] + except IOError: + speed = 0 + finally: + sock.close() + return speed + +def getifaddr(device): + """ + """ + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + return socket.inet_ntoa(fcntl.ioctl( + s.fileno(), + 0x8915, # SIOCGIFADDR + struct.pack('256s', bytes(device[:15], 'utf-8')) + )[20:24]) + +def getifhwaddr(device): + """ + """ + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + hwaddr = fcntl.ioctl( + s.fileno(), + 0x8927, # SIOCGIFHWADDR + struct.pack('256s', bytes(device[:15], 'utf-8')) + )[18:24] + return "%02x:%02x:%02x:%02x:%02x:%02x" % struct.unpack("BBBBBB", hwaddr) diff --git a/src/utils/probe.py b/src/utils/probe.py new file mode 100644 index 0000000..4600ff4 --- /dev/null +++ b/src/utils/probe.py @@ -0,0 +1,83 @@ +# +# Copyright (C) 2022 Soleta Networks +# +# This program is free software: you can redistribute it and/or modify it under +# the terms of the GNU Affero General Public License as published by the +# Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. + +import os +import subprocess + +from subprocess import PIPE + +def getlinuxversion(osrelease): + """ + Parses a os-release file to fetch 'PRETTY_NAME' key. + If file or key are not found, then returns generic + 'Linux' string. + """ + with open(osrelease, 'r') as f: + for line in f: + if line.find('=') == -1: + continue + key, value = line.split('=') + if key == 'PRETTY_NAME': + value = value.replace('\n', '') + value = value.strip('"') + return value + return 'Linux' + + +def getwindowsversion(winreghives): + """ + Tries to obtain windows version information by + querying the SOFTWARE registry hive. Registry + hives path is a required parameter. + + Runs hivexget(1) to fetch ProductName and + ReleaseId. If something fails (hivexget is + not installed, or registry is not found) it + returns a generic "Microsoft Windows" string. + """ + + # XXX: 3.6 friendly + try: + proc_prodname = subprocess.run(['hivexget', + f'{winreghives}/SOFTWARE', + 'microsoft\windows nt\currentversion', + 'ProductName'], stdout=PIPE) + proc_releaseid = subprocess.run(['hivexget', + f'{winreghives}/SOFTWARE', + 'microsoft\windows nt\currentversion', + 'ReleaseId'], stdout=PIPE) + + proc_prodname = proc_releaseid.stdout.replace('\n', '') + releaseid = proc_releaseid.stdout.replace('\n', '') + + if proc_prodname.returncode == 0 and proc_releaseid.returncode == 0: + return f'{prodname} {releaseid}' + except FileNotFoundError: # hivexget command not found + pass + return 'Microsoft Windows' + + +def os_probe(mountpoint): + """ + Probes mountpoint for typical OS dependant files. + + Windows: Tries finding and querying the software + registry hive. + Linux: Looks for /etc/os-release file. + + Returns a string depending on the OS it detects. + """ + winreghives = f'{mountpoint}Windows/System32/config' + osrelease = f'{mountpoint}/etc/os-release' + + if os.path.exists(osrelease): + return getlinuxversion(osrelease) + elif os.path.exists(winreghives): + return getwindowsversion(winreghives) + else: + return '' -- cgit v1.2.3-18-g5258