def get_net(path: str, baseboard: dict, interactive: bool = False): try: with open(path, 'r') as f: output = f.read() except FileNotFoundError: raise InputFileNotFoundError(path) mergeit = { "ethernet-ports-100m-n": 0, "ethernet-ports-1000m-n": 0, "mac": [], } other_devices = [] for line in output.split("\n"): if 'u' in line: # USB adapters, ignore them continue line = line.split(' ', 3) if line[0].startswith('en'): if line[2] == "1000": mergeit["ethernet-ports-1000m-n"] += 1 elif line[2] == "100": mergeit["ethernet-ports-100m-n"] += 1 mergeit["mac"].append(line[1]) if line[0].startswith('wl'): other_devices.append({ "type": "wifi-card", "mac": line[1], "notes": f"Device name {line[0]}" }) mergeit["mac"] = ', '.join(mergeit["mac"]) if 'ethernet-ports-n' in baseboard: found_ports = mergeit["ethernet-ports-100m-n"] + mergeit[ "ethernet-ports-1000m-n"] baseboard['ethernet-ports-n'] -= found_ports if baseboard['ethernet-ports-n'] > 0: if baseboard['ethernet-ports-n'] > 1: message = f"\nBIOS reported {baseboard['ethernet-ports-n']} more ethernet ports that were not found by the kernel" else: message = f"\nBIOS reported {baseboard['ethernet-ports-n']} more ethernet port that was not found by the kernel" if 'notes' in baseboard: baseboard['notes'] += message baseboard['notes'] = baseboard['notes'].strip() else: baseboard['notes'] = message.strip() del baseboard['ethernet-ports-n'] if mergeit["ethernet-ports-100m-n"] <= 0: del mergeit["ethernet-ports-100m-n"] if mergeit["ethernet-ports-1000m-n"] <= 0: del mergeit["ethernet-ports-1000m-n"] if len(mergeit["mac"]) <= 0: del mergeit["mac"] baseboard = {**baseboard, **mergeit} if len(other_devices) > 0: return [baseboard] + other_devices else: return baseboard
def get_output(path): try: with open(path, 'r') as f: output = f.read() except FileNotFoundError: raise InputFileNotFoundError(path) return output
def parse_glxinfo_output(gpu: VideoCard, glxinfo_path: str): try: with open(glxinfo_path, 'r') as f: glxinfo_output = f.read() except FileNotFoundError: raise InputFileNotFoundError(glxinfo_path) for i, line in enumerate(glxinfo_output.splitlines()): # this line comes before the "Dedicated video memory" line # this basically saves a default value if the dedicated memory line cannot be found if "Video memory" in line: try: tmp_vid_mem = int(line.split(" ")[6].split(" ")[0][:-2]) tmp_vid_mem_multiplier = line[-2:] except ValueError: exit(-1) return # To stop complaints from PyCharm gpu.capacity = convert_video_memory_size(tmp_vid_mem, tmp_vid_mem_multiplier) if "Dedicated video memory" in line: try: tmp_vram = int(line.split(" ")[7].split(" ")[0]) tmp_vram_multiplier = line[-2:] except ValueError: exit(-1) return capacity = convert_video_memory_size(tmp_vram, tmp_vram_multiplier) if capacity < 0: gpu.warning = "Could not find dedicated video memory" if gpu.capacity < 0: gpu.warning += ". The value cannot be trusted." else: gpu.capacity = capacity break if gpu.capacity > 0: # Round to the next power of 2 # this may be different from human readable capacity... rounded = 2**(gpu.capacity - 1).bit_length() one_and_half = int(rounded / 2 * 1.5) # Accounts for 3 GB VRAM cards and similar # Yes they do exist, try to remove this part and watch tests fail (and the card was manually verified to be 3 GB) if one_and_half >= gpu.capacity: gpu.capacity = one_and_half else: gpu.capacity = rounded
def get_baseboard(path: str): mobo = Baseboard() # p = sp.Popen(['sudo dmidecode -t baseboard'], shell=True, stdin=sp.PIPE, stdout=sp.PIPE, stderr=sp.PIPE) # out = p.communicate() # strings = str.split(str(out[0]), sep="\\n") try: with open(path, 'r') as f: output = f.read() except FileNotFoundError: raise InputFileNotFoundError(path) for line in output.splitlines(): if "Manufacturer:" in line: mobo.brand = line.split("Manufacturer:")[1].strip() elif "Product Name:" in line: mobo.model = line.split("Product Name:")[1].strip() elif "Serial Number:" in line: mobo.serial_number = line.split("Serial Number:")[1].strip() if not mobo.serial_number.endswith('O.E.M.'): mobo.serial_number = mobo.serial_number.strip('.') result = { "type": mobo.type, "brand": mobo.brand, "model": mobo.model, "sn": mobo.serial_number, "working": 'yes', # Indeed it is working } for key, value in result.items(): if value == "Unknown": del result[key] result[key] = None return result
def get_chassis(path: str): chassis = Chassis() try: with open(path, 'r') as f: output = f.read() except FileNotFoundError: raise InputFileNotFoundError(path) for line in output.splitlines(): if "Manufacturer" in line: chassis.brand = line.split("Manufacturer:")[1].strip() # This is Desktop, Laptop, etc... elif "Type: " in line: ff = line.split("Type: ")[1].strip() if ff == 'Laptop' or ff == 'Notebook': # Both exist in the wild and in tests, difference unknonw chassis.form_factor = 'proprietary-laptop' elif "Serial Number" in line: chassis.serial_number = line.split("Serial Number:")[1].strip() if not chassis.serial_number.endswith('O.E.M.'): chassis.serial_number = chassis.serial_number.strip('.') result = { "type": chassis.type, "brand": chassis.brand, "model": chassis.model, "sn": chassis.serial_number, "motherboard-form-factor": chassis.form_factor, } for key, value in result.items(): if value == "Unknown": # Restore the default of empty string result[key] = '' return result
def read_smartctl(path: str, interactive: bool = False): disks = [] for filename in os.listdir(path): if "smartctl-dev-" in filename: disk = Disk() try: with open(path + "/" + filename, 'r') as f: output = f.read() except FileNotFoundError: raise InputFileNotFoundError(path) if '=== START OF INFORMATION SECTION ===' not in output: if interactive: print( f"{filename} does not contain disk information, was it a USB stick?" ) continue data = output.split('=== START OF INFORMATION SECTION ===', 1)[1] \ .split('=== START OF READ SMART DATA SECTION ===', 1)[0] # For manual inspection later on if '=== START OF SMART DATA SECTION ===' in output: disk.smart_data_long = '=== START OF SMART DATA SECTION ===' + \ output.split('=== START OF SMART DATA SECTION ===', 1)[1] elif '=== START OF READ SMART DATA SECTION ===' in output: disk.smart_data_long = '=== START OF READ SMART DATA SECTION ===' + \ output.split('=== START OF READ SMART DATA SECTION ===', 1)[1] if disk.smart_data_long is not None: status = None for line in disk.smart_data_long.splitlines(): if "SMART overall-health" in line: status = line.split(":")[1].strip() elif "Device does not support Self Test logging" in line: status = "not supported" if status == "PASSED": # the disk is working fine disk.smart_data = SMART.working elif status == "FAILED!": # the disk is not working fine disk.smart_data = SMART.fail elif status == "UNKNOWN!": # the connection timed out, there could be different reasons disk.smart_data = SMART.not_available elif status == "not supported": # the smart data need to be switched on or the smart capability is not supported for line in data.splitlines(): if "SMART support is:" in line: line = line.split("SMART support is:")[1].strip() if "device lacks SMART capability" in line: # disk doesn't support smart capabilities disk.smart_data = SMART.old elif "device has SMART capability" in line: # you need to enable smart capabilities print( "you need to enable smart capabilities on disk" ) disk.smart_data = SMART.not_available for line in data.splitlines(): if "Model Family:" in line: line = line.split("Model Family:")[1].strip() brand, family = split_brand_and_other(line) disk.family = family if brand is not None: disk.brand = brand elif "Model Number:" in line: line = line.split("Model Number:")[1].strip() brand, model = split_brand_and_other(line) disk.model = model if brand is not None: disk.brand = brand elif "Device Model:" in line: line = line.split("Device Model:")[1].strip() brand, model = split_brand_and_other(line) disk.model = model if brand is not None: disk.brand = brand elif "Serial Number:" in line: disk.serial_number = line.split( "Serial Number:")[1].strip() elif "LU WWN Device Id:" in line: disk.wwn = line.split("LU WWN Device Id:")[1].strip() elif "Form Factor:" in line: ff = line.split("Form Factor:")[1].strip() # https://github.com/smartmontools/smartmontools/blob/40468930fd77d681b034941c94dc858fe2c1ef10/smartmontools/ataprint.cpp#L405 if ff == '3.5 inches': disk.form_factor = '3.5' elif ff == '2.5 inches': # This is the most common height, just guessing... disk.form_factor = '2.5-7mm' elif ff == '1.8 inches': # Still guessing... disk.form_factor = '1.8-8mm' elif ff == 'M.2': disk.form_factor = 'm2' elif "User Capacity:" in line: # https://stackoverflow.com/a/3411435 num_bytes = line.split('User Capacity:')[1].split( "bytes")[0].strip().replace(',', '').replace('.', '') round_digits = int(floor(log10(abs(float(num_bytes))))) - 2 bytes_rounded = int(round(float(num_bytes), -round_digits)) disk.capacity = bytes_rounded tmp_capacity = line.split("[")[1].split("]")[0] if tmp_capacity is not None: disk.human_readable_capacity = tmp_capacity elif "Rotation Rate:" in line: if "Solid State Device" not in line: disk.rotation_rate = int( line.split("Rotation Rate:")[1].split("rpm") [0].strip()) disk.type = "hdd" else: disk.type = "ssd" if disk.brand == 'Western Digital': # These are useless and usually not even printed on labels and in bar codes... disk.model = remove_prefix('WDC ', disk.model) disk.serial_number = remove_prefix('WD-', disk.serial_number) if disk.model.startswith('SSD '): disk.model = disk.model[4:] disks.append(disk) result = [] for disk in disks: if disk.type == "ssd": # ssd this_disk = { "type": "ssd", "brand": disk.brand, "model": disk.model, "family": disk.family, "wwn": disk.wwn, "sn": disk.serial_number, "capacity-byte": disk.capacity, "human_readable_capacity": disk.human_readable_capacity, # "human_readable_smart_data": disk.smart_data_long "smart_data": disk.smart_data } else: this_disk = { "type": "hdd", "brand": disk.brand, "model": disk.model, "family": disk.family, "wwn": disk.wwn, "sn": disk.serial_number, # Despite the name it's still in bytes, but with SI prefix (not power of 2), "deci" is there just to # tell some functions how to convert it to human-readable format "capacity-decibyte": disk.capacity, "human_readable_capacity": disk.human_readable_capacity, "spin-rate-rpm": disk.rotation_rate, # "human_readable_smart_data": disk.smart_data.long "smart_data": disk.smart_data } if disk.form_factor is not None: this_disk["hdd-form-factor"] = disk.form_factor if 'SATA' in disk.family or 'SATA' in disk.model: this_disk["sata-ports-n"] = 1 result.append(this_disk) return result
def parse_lspci_output(gpu: VideoCard, lspci_path: str, interactive: bool = False): try: with open(lspci_path, 'r') as f: lspci_output = f.read() except FileNotFoundError: raise InputFileNotFoundError(lspci_path) lspci_sections = lspci_output.split("\n\n") for section in lspci_sections: if "VGA compatible controller" in section: first_line = section.splitlines()[0].split( ': ', 1)[1] # removes "VGA compatible controller:" second_line = section.splitlines()[1] part_between_square_brackets = None try: # take the first string between [] from the first line part_between_square_brackets = first_line.split("[")[1].split( "]")[0] except IndexError: # there may not be an argument in between [] pass if 'Subsystem:' in second_line: # The model or model family is often repeated here, but removing it automatically is complicated gpu.reseller_brand = second_line.split('Subsystem: ')[1].split( '[', 1)[0].strip() gpu.reseller_brand = gpu.reseller_brand\ .replace('Integrated Graphics Controller', '') # ----------------------------------------------------------------- # AMD/ATI # ----------------------------------------------------------------- if part_between_square_brackets is not None and ( "AMD" in part_between_square_brackets or "ATI" in part_between_square_brackets): gpu.manufacturer_brand = part_between_square_brackets # take second string between [] gpu.model = first_line.split("[")[2].split("]")[0] if "controller" in gpu.model: gpu.model = section.splitlines()[1].split(" ")[-1] # ----------------------------------------------------------------- # Nvidia # ----------------------------------------------------------------- elif "NVIDIA" in first_line.upper(): gpu.manufacturer_brand = "Nvidia" gpu.model = part_between_square_brackets if gpu.reseller_brand != '': pieces = gpu.reseller_brand.rsplit(' ', 1) gpu.reseller_brand = pieces[0] gpu.internal_name = pieces[1] # ----------------------------------------------------------------- # Intel # ----------------------------------------------------------------- elif "INTEL" in first_line.upper(): gpu.manufacturer_brand = "Intel" if "Integrated Graphics" in first_line: tmp_model = first_line.split("Intel Corporation ")[ 1].split(" Integrated Graphics")[0] # if there are no numbers, e.g. "Core Processor", tmp_model is not a model number if not re.search("\\d+", tmp_model): tmp_model = "" elif "HD Graphics" in first_line: tmp_model = first_line.split( "Intel Corporation ")[1].split("(", 1)[0].strip() elif "[" in first_line and "]" in first_line: tmp_model = first_line.split("[")[1].split("]")[0] else: tmp_model = "" if tmp_model != "": gpu.model = tmp_model else: gpu.model = "" # ----------------------------------------------------------------- # VIA # ----------------------------------------------------------------- elif first_line.startswith('VIA'): gpu.manufacturer_brand = 'VIA' gpu.model = part_between_square_brackets tmp_model = first_line.split('[')[0] i = 0 for i, char in enumerate('VIA Technologies, Inc. '): if tmp_model[i] != char: break gpu.internal_name = tmp_model[i:].strip() # ----------------------------------------------------------------- # SiS # ----------------------------------------------------------------- elif part_between_square_brackets == 'SiS': # May be written somewhere else on other models, but we have so few SiS cards that it's difficult to # find more examples. Also, they haven't made any video card in the last 15 years or so. gpu.manufacturer_brand = part_between_square_brackets if gpu.reseller_brand.lower() == 'silicon integrated systems': gpu.reseller_brand = 'SiS' gpu.model = first_line.split(']', 1)[1] # These may be useful for non-integrated cards, however the example ones are all integrated if " PCIE" in gpu.model: gpu.model = gpu.model.split(" PCIE", 1)[0].strip() elif " PCI/AGP" in gpu.model: gpu.model = gpu.model.split(" PCI/AGP", 1)[0].strip() if gpu.model in gpu.reseller_brand: gpu.reseller_brand = gpu.reseller_brand.split( gpu.model, 1)[0].strip() else: gpu.manufacturer_brand = None error = "I couldn't find the Video Card brand. The model was set to 'None' and is to be edited " \ "logging into the TARALLO afterwards. The information you're looking for should be in the " \ f"following 2 lines:\n{first_line}\n{second_line}\n" if interactive: print(error) gpu.warning += error if gpu.model is None: error = "I couldn\'t find the Integrated Graphics model. The model was set to \'None\' and is to be " \ "edited logging into the TARALLO afterwards. The information you\'re looking for should be in " \ f"the following 2 lines:\n{first_line}\n{second_line}\n" if interactive: print(error) gpu.warning += error else: # Try to remove duplicate information gpu.reseller_brand = gpu.reseller_brand.replace(gpu.model, '').strip() if gpu.internal_name is not None: # Same gpu.reseller_brand = gpu.reseller_brand.replace( gpu.internal_name, '').strip() break
def get_connectors(path: str, baseboard: dict, interactive: bool = False): try: with open(path, 'r') as f: output = f.read() except FileNotFoundError: raise InputFileNotFoundError(path) possible_connectors = set(connectors_map.values()) | set( connectors_map_tuples.values()) possible_connectors.remove(None) connectors = dict(zip(possible_connectors, [0] * len(connectors_map))) # TODO: this part (is it needed?) # port_types = [] # devices = output.split("On Board Device") # for device in devices: # type = device.split("Description:") # if len(type) > 1: # port_types.append(type[1].replace("\n", "").replace(" ", "")) warnings = [] for section in output.split("\n\n"): if not section.startswith('Handle '): continue internal = get_dmidecoded_value(section, "Internal Connector Type:") external = get_dmidecoded_value(section, "External Connector Type:") internal_des = get_dmidecoded_value(section, "Internal Reference Designator:") external_des = get_dmidecoded_value(section, "External Reference Designator:") if external in ('None', 'Other', 'Not Specified'): if internal in ('None', 'Other', 'Not Specified'): if external_des in ('None', 'Other', 'Not Specified'): connector = internal_des else: connector = external_des else: connector = internal else: connector = external if connector in connectors_map: if connectors_map[connector] is not None: connectors[connectors_map[connector]] += 1 elif connector in extra_connectors: # Dark magic: https://stackoverflow.com/a/26853961 connectors = {**connectors, **(extra_connectors[connector])} else: found = find_connector_from_tuple(connectors, external, external_des, internal, internal_des) if not found: warning = f"Unknown connector: {internal} / {external} ({internal_des} / {external_des})" if interactive: print(warning) warnings.append(warning) warnings = '\n'.join(warnings) connectors_clean = {} # Keys to avoid changing dict size at runtime (raises an exception) for connector in connectors: if isinstance(connectors[connector], int): if connectors[connector] > 0: connectors_clean[connector] = connectors[connector] else: connectors_clean[connector] = connectors[connector] # Dark magic: https://stackoverflow.com/a/26853961 return {**baseboard, **connectors_clean, **{'notes': warnings}}
def read_decode_dimms(path: str, interactive: bool = False): try: with open(path, 'r') as f: output = f.read() except FileNotFoundError: raise InputFileNotFoundError(path) if interactive: print("Reading decode-dimms...") # this optimization can crash the script if output is empty # last_line = output.splitlines()[-1] # check based on output of decode-dimms v6250 if "Number of SDRAM DIMMs detected and decoded: 0" in output\ or "Number of SDRAM DIMMs detected and decoded: " not in output: if interactive: print("decode-dimms was not able to find any RAM details") return [] # split strings in 1 str array for each DIMM dimm_sections = output.split("Decoding EEPROM") # remove useless first part del dimm_sections[0] # create list of as many dimms as there are dimm_sections dimms = [Dimm() for i in range(len(dimm_sections))] for i, dimm in enumerate(dimm_sections): for line in dimm.splitlines(): if line.startswith("Fundamental Memory type"): dimms[i].ram_type = line.split(" ")[-2].lower() if dimms[i].ram_type == 'unknown': dimms[i].ram_type = '' if line.startswith("Maximum module speed"): freq = line.split(" ")[-3:-1] dimms[i].frequency = int(freq[0]) if "KHz" in freq[1] or "kHz" in freq[1]: dimms[i].human_readable_frequency = freq[0] + " KHz" dimms[i].frequency *= 1000 elif "MHz" in freq[1]: dimms[i].human_readable_frequency = freq[0] + " MHz" dimms[i].frequency *= 1000 * 1000 elif "GHz" in freq[1]: dimms[i].human_readable_frequency = freq[0] + " GHz" dimms[i].frequency *= 1000 * 1000 * 1000 # The official thing is 667 MHz even if they run at 666 MHz if dimms[i].frequency == 666000000: dimms[i].frequency = 667000000 if line.startswith("Size"): cap = line.split(" ")[-2:] dimms[i].capacity = int(cap[0]) if "KB" in cap[1] or "kB" in cap[1]: dimms[i].human_readable_capacity = cap[0] + " KB" dimms[i].capacity *= 1024 elif "MB" in cap[1]: dimms[i].human_readable_capacity = cap[0] + " MB" dimms[i].capacity *= 1024 * 1024 elif "GB" in cap[1]: dimms[i].human_readable_capacity = cap[0] + " GB" dimms[i].capacity *= 1024 * 1024 * 1024 # alternatives to "Manufacturer" are "DRAM Manufacturer" and "Module Manufacturer" if "---=== Manufacturer Data ===---" in line: dimms[i].manufacturer_data_type = "DRAM Manufacturer" if "---=== Manufacturing Information ===---" in line: dimms[i].manufacturer_data_type = "Manufacturer" if line.startswith(dimms[i].manufacturer_data_type): if dimms[i].manufacturer_data_type == "DRAM Manufacturer": dimms[i].brand = ignore_spaces(line, len("DRAM Manufacturer")) elif dimms[i].manufacturer_data_type == "Manufacturer": dimms[i].brand = ignore_spaces(line, len("Manufacturer")) # This seems to always be the model (or at least never be the serial number) if line.startswith("Part Number"): dimms[i].model = ignore_spaces(line, len("Part Number")) # part number can be overwritten by serial number if present if line.startswith("Assembly Serial Number"): dimms[i].serial_number = ignore_spaces( line, len("Assembly Serial Number")) if dimms[i].serial_number.startswith('0x'): try: dimms[i].serial_number = str( int(dimms[i].serial_number[2:], base=16)) except ValueError: # Ooops, this isn't an hex number after all... pass if line.startswith("Module Configuration Type") and ( "Data Parity" in line or "Data ECC" in line or "Address/Command Parity" in line): dimms[i].ecc = ECC.available # Two (or more) spaces after because there are lines like "tCL-tRCD-tRP-tRAS as ..." if line.startswith("tCL-tRCD-tRP-tRAS "): dimms[i].cas_latencies = ignore_spaces( line, len("tCL-tRCD-tRP-tRAS")) dimms_dicts = [] for dimm in dimms: dimms_dicts.append({ "type": "ram", "brand": dimm.brand, "model": dimm.model, "sn": dimm.serial_number, "frequency-hertz": dimm.frequency, "human_readable_frequency": dimm.human_readable_frequency, "capacity-byte": dimm.capacity, "human_readable_capacity": dimm.human_readable_capacity, "ram-type": dimm.ram_type, "ram-ecc": dimm.ecc, "ram-timings": dimm.cas_latencies, "working": 'yes', # Indeed it is working }) return dimms_dicts
def parse_disks(interactive: bool = False, ignore: list = [], usbdebug: bool = False): """ Parses disks mounted on the current machine :param interactive: adds verbosity if set to True :param ignore: list of disks to ignore (eg. 'sda', 'sdb', etc.) :param usbdebug: allow scan of USB drives (FOR TEST PURPOSES, USE ONLY ON A TEST INSTANCE OF TARALLO!!!) :return: list of disks in a TARALLO friendly format """ disks = [] smartctl_path = os.path.join(os.getcwd(), "smartctl") if not os.path.exists(smartctl_path): os.makedirs(smartctl_path) filegen = os.path.join(os.getcwd(), "smartctl_filegen.sh") return_code = sp.run(["sudo", "-S", filegen, smartctl_path]).returncode assert (return_code == 0), 'Error during disk detection' files = os.listdir(smartctl_path) for filename in files: if "smartctl-dev-" in filename: # Ignoring disks pointed on call ignored = False for mount_point in ignore: if mount_point in filename: ignored = True break if ignored is True: if interactive is True: print("Disk mounted at /dev/"+mount_point+" ignored") os.remove(os.path.join(smartctl_path, filename)) continue # File reading try: with open(os.path.join(smartctl_path, filename), 'r') as f: output = f.read() except FileNotFoundError: raise InputFileNotFoundError(smartctl_path) # Checks if it's a valid disk # If usbdebug is True, the disk is filled with dummy informations disk = read_smartctl(output) if not check_complete(disk): if usbdebug is True: disk = dummy_disk(disk) else: if interactive: print(f"{filename} does not contain disk information, was it a USB stick?") continue disk.dev = filename.split("smartctl-dev-")[1].split(".txt")[0] old_filename = os.path.join("smartctl/", filename) new_filename = os.path.join("smartctl/", disk.serial_number) + ".txt" os.rename(old_filename, new_filename) disks.append(disk) if len(disks) >= 1: return tarallo_conversion(disks) return []