from datetime import datetime from glob import glob from hashlib import md5 from os import mkdir, rename, rmdir, stat from os.path import basename, exists, isdir, isfile, ismount, join as pathjoin from struct import calcsize, pack, unpack from subprocess import check_output from tempfile import mkdtemp # NOTE: This module must not import from SystemInfo.py as this module is # used to populate BoxInfo / SystemInfo and will create a boot loop! # from Components.Console import Console from Tools.Directories import SCOPE_CONFIG, copyfile, fileHas, fileReadLine, fileReadLines, resolveFilename MODULE_NAME = __name__.split(".")[-1] MOUNT = "/bin/mount" UMOUNT = "/bin/umount" REMOVE = "/bin/rm" PREFIX = "MultiBoot_" COMMAND_FILE = "cmdline.txt" DUAL_BOOT_FILE = "/dev/block/by-name/flag" DREAM_BOOT_FILE = "/data/bootconfig.txt" STARTUP_FILE = "STARTUP" STARTUP_ONCE = "STARTUP_ONCE" STARTUP_TEMPLATE = "STARTUP_*" STARTUP_ANDROID = "STARTUP_ANDROID" STARTUP_ANDROID_LINUXSE = "STARTUP_ANDROID_LINUXSE" STARTUP_RECOVERY = "STARTUP_RECOVERY" STARTUP_BOXMODE = "BOXMODE" # This is known as bootCode in this code. BOOT_DEVICE_LIST = ("/dev/mmcblk0p1", "/dev/mmcblk1p1", "/dev/mmcblk0p3", "/dev/mmcblk0p4", "/dev/mtdblock2", "/dev/block/by-name/bootoptions") BOOT_DEVICE_LIST_VUPLUS = ("/dev/mmcblk0p4", "/dev/mmcblk0p7", "/dev/mmcblk0p9") # Kexec kernel Vu+ MultiBoot. # STARTUP # STARTUP_LINUX_1_BOXMODE_1 # boot emmcflash0.linuxkernel 'root=/dev/mmcblk0p3 rootsubdir=linuxrootfs1 kernel=/dev/mmcblk0p2 rw rootwait h7_4.boxmode=1' # STARTUP_LINUX_2_BOXMODE_1 # boot emmcflash0.linuxkernel2 'root=/dev/mmcblk0p8 rootsubdir=linuxrootfs2 kernel=/dev/mmcblk0p4 rw rootwait h7_4.boxmode=1' # STARTUP_LINUX_3_BOXMODE_1 # boot emmcflash0.linuxkernel3 'root=/dev/mmcblk0p8 rootsubdir=linuxrootfs3 kernel=/dev/mmcblk0p5 rw rootwait h7_4.boxmode=1' # STARTUP_LINUX_4_BOXMODE_1 # boot emmcflash0.linuxkernel4 'root=/dev/mmcblk0p8 rootsubdir=linuxrootfs4 kernel=/dev/mmcblk0p6 rw rootwait h7_4.boxmode=1' # STARTUP_LINUX_1_BOXMODE_12 # boot emmcflash0.linuxkernel 'brcm_cma=520M@248M brcm_cma=192M@768M root=/dev/mmcblk0p3 rootsubdir=linuxrootfs1 kernel=/dev/mmcblk0p2 rw rootwait h7_4.boxmode=12' # STARTUP_LINUX_2_BOXMODE_12 # boot emmcflash0.linuxkernel2 'brcm_cma=520M@248M brcm_cma=192M@768M root=/dev/mmcblk0p8 rootsubdir=linuxrootfs2 kernel=/dev/mmcblk0p4 rw rootwait h7_4.boxmode=12' # STARTUP_LINUX_3_BOXMODE_12 # boot emmcflash0.linuxkernel3 'brcm_cma=520M@248M brcm_cma=192M@768M root=/dev/mmcblk0p8 rootsubdir=linuxrootfs3 kernel=/dev/mmcblk0p5 rw rootwait h7_4.boxmode=12' # STARTUP_LINUX_4_BOXMODE_12 # boot emmcflash0.linuxkernel4 'brcm_cma=520M@248M brcm_cma=192M@768M root=/dev/mmcblk0p8 rootsubdir=linuxrootfs4 kernel=/dev/mmcblk0p6 rw rootwait h7_4.boxmode=12' # # STARTUP # STARTUP_1 # boot emmcflash0.kernel1: 'root=/dev/mmcblk0p5 rootwait rw rootflags=data=journal libata.force=1:3.0G,2:3.0G,3:3.0G coherent_poll=2M vmalloc=525m bmem=529m@491m bmem=608m@2464m' # STARTUP_2 # boot emmcflash0.kernel2: 'root=/dev/mmcblk0p7 rootwait rw rootflags=data=journal libata.force=1:3.0G,2:3.0G,3:3.0G coherent_poll=2M vmalloc=525m bmem=529m@491m bmem=608m@2464m' # STARTUP_3 # boot emmcflash0.kernel3: 'root=/dev/mmcblk0p9 rootwait rw rootflags=data=journal libata.force=1:3.0G,2:3.0G,3:3.0G coherent_poll=2M vmalloc=525m bmem=529m@491m bmem=608m@2464m' # # STARTUP (sfx6008) # boot internalflash0.linuxkernel1 'ubi.mtd=12 root=ubi0:ubifs rootsubdir=linuxrootfs1 rootfstype=ubifs kernel=/dev/mtd10 userdataroot=/dev/mtd12 userdatasubdir=userdata1 mtdparts=hinand:1M(boot),1M(bootargs),1M(bootoptions),1M(baseparam),1M(pqparam),1M(logo),1M(deviceinfo),1M(softwareinfo),1M(loaderdb),16M(loader),6M(linuxkernel1),6M(linuxkernel2),-(userdata)' # # /sys/firmware/devicetree/base/chosen/bootargs # console=ttyAMA0,115200 ubi.mtd=12 root=ubi0:ubifs rootsubdir=linuxrootfs1 rootfstype=ubifs kernel=/dev/mtd10 userdataroot=/dev/mtd12 userdatasubdir=userdata1 mtdparts=hinand:1M(boot),1M(bootargs),1M(bootoptions),1M(baseparam),1M(pqparam),1M(logo),1M(deviceinfo),1M(softwareinfo),1M(loaderdb),16M(loader),6M(linuxkernel1),6M(linuxkernel2),-(userdata) mem=512M mmz=ddr,0,0,160M vmalloc=500M MACHINEBUILD=sfx6008 OEM=octagon MODEL=sfx6008 # # root=/dev/mmcblk0p3 rootsubdir=linuxrootfs1 kernel=/dev/mmcblk0p2 rw rootwait h7_4.boxmode=1 # class MultiBootClass(): def __init__(self): print("[MultiBoot] MultiBoot is initializing.") lines = [] lines = fileReadLines(resolveFilename(SCOPE_CONFIG, "settings"), default=lines, source=MODULE_NAME) self.debugMode = "config.crash.debugMultiBoot=True" in lines self.bootArgs = fileReadLine("/sys/firmware/devicetree/base/chosen/bootargs", default="", source=MODULE_NAME) self.console = Console() self.loadMultiBoot() def loadMultiBoot(self): self.bootDevice, self.startupCmdLine = self.loadBootDevice() self.bootSlots, self.bootSlotsKeys = self.loadBootSlots() if exists(DUAL_BOOT_FILE): try: with open(DUAL_BOOT_FILE, "rb") as fd: structFormat = "B" flag = fd.read(calcsize(structFormat)) slot = unpack(structFormat, flag) self.bootSlot = str(slot[0]) self.bootCode = "" except OSError as err: print("MultiBoot] Error %d: Unable to read dual boot file '%s'! (%s)" % (err.errno, DUAL_BOOT_FILE, err.strerror)) self.bootSlot = None self.bootCode = "" except struct.error as err: print("MultiBoot] Unable to interpret dual boot file '%s' data! (%s)" % (DUAL_BOOT_FILE, err)) #elif exists(DREAM_BOOT_FILE): # with open(DREAM_BOOT_FILE, "r") as fd: # lines = fd.readlines() # for line in lines: # if line.startswith("default="): # self.bootSlot = str(int(line.strip().split("=")[1]) + 1) # self.bootCode = "" else: self.bootSlot, self.bootCode = self.loadCurrentSlotAndBootCodes() def loadBootDevice(self): bootDeviceList = BOOT_DEVICE_LIST_VUPLUS if fileHas("/proc/cmdline", "kexec=1") else BOOT_DEVICE_LIST for device in bootDeviceList: bootDevice = None startupCmdLine = None if exists(device): tempDir = mkdtemp(prefix=PREFIX) self.console.ePopen([MOUNT, MOUNT, device, tempDir]) cmdFile = pathjoin(tempDir, COMMAND_FILE) startupFile = pathjoin(tempDir, STARTUP_FILE) if isfile(cmdFile) or isfile(startupFile): file = cmdFile if isfile(cmdFile) else startupFile startupCmdLine = " ".join(x.strip() for x in fileReadLines(file, default=[], source=MODULE_NAME) if x.strip()) bootDevice = device self.console.ePopen([UMOUNT, UMOUNT, tempDir]) rmdir(tempDir) if bootDevice: print("[MultiBoot] Startup device identified as '%s'." % device) if startupCmdLine: print("[MultiBoot] Startup command line '%s'." % startupCmdLine) break return bootDevice, startupCmdLine def loadBootSlots(self): def saveKernel(bootSlots, slotCode, kernel): value = bootSlots[slotCode].get("kernel") if value is None: bootSlots[slotCode]["kernel"] = kernel elif value != kernel: print("[MultiBoot] Error: Inconsistent kernels found for slot '%s'! ('%s' != '%s')" % (slotCode, value, kernel)) bootSlots = {} bootSlotsKeys = [] if self.bootDevice: tempDir = mkdtemp(prefix=PREFIX) self.console.ePopen([MOUNT, MOUNT, self.bootDevice, tempDir]) for path in sorted(glob(pathjoin(tempDir, STARTUP_TEMPLATE))): file = basename(path) if "DISABLE" in file: if self.debugMode: print("[MultiBoot] Skipping disabled boot file '%s'." % file) continue elif file == STARTUP_ANDROID: bootCode = "" slotCode = "A" elif file == STARTUP_ANDROID_LINUXSE: bootCode = "" slotCode = "L" elif file == STARTUP_RECOVERY: bootCode = "" slotCode = "R" elif STARTUP_BOXMODE in file: parts = file.rsplit("_", 3) bootCode = parts[3] slotCode = parts[1] else: bootCode = "" slotCode = file.rsplit("_", 1)[1] if self.debugMode: print("[MultiBoot] Processing boot file '%s'%s%s." % (file, " as slot code '%s'" % slotCode if slotCode else "", ", boot mode '%s'" % bootCode if bootCode else "")) if slotCode: line = " ".join(x.strip() for x in fileReadLines(path, default=[], source=MODULE_NAME) if x.strip()) if "root=" in line: # print("[MultiBoot] loadBootSlots DEBUG: 'root=' found.") device = self.getParam(line, "root") if "UUID=" in device: uuidDevice = self.getUUIDtoDevice(device) # print("[MultiBoot] loadBootSlots DEBUG: 'UUID=' found for device '%s'.", uuidDevice) if uuidDevice: device = uuidDevice if exists(device) or device == "ubi0:ubifs": if slotCode not in bootSlots: bootSlots[slotCode] = {} # print("[MultiBoot] Root dictionary entry in slot '%s' created." % slotCode) value = bootSlots[slotCode].get("device") if value is None: bootSlots[slotCode]["device"] = device elif value != device: print("[MultiBoot] Error: Inconsistent root devices found for slot '%s'! ('%s' != '%s')" % (slotCode, value, device)) value = bootSlots[slotCode].get("bootCodes") if value is None: bootSlots[slotCode]["bootCodes"] = [bootCode] else: bootSlots[slotCode]["bootCodes"].append(bootCode) value = bootSlots[slotCode].get("startupfile") if value is None: bootSlots[slotCode]["startupfile"] = {} bootSlots[slotCode]["startupfile"][bootCode] = file value = bootSlots[slotCode].get("cmdline") if value is None: bootSlots[slotCode]["cmdline"] = {} bootSlots[slotCode]["cmdline"][bootCode] = line if "ubi.mtd=" in line: bootSlots[slotCode]["ubi"] = True if "UUID=" in line: bootSlots[slotCode]["uuid"] = True if "rootsubdir" in line: bootSlots[slotCode]["kernel"] = self.getParam(line, "kernel") bootSlots[slotCode]["rootsubdir"] = self.getParam(line, "rootsubdir") elif "sda" in line: saveKernel(bootSlots, slotCode, "/dev/sda%s" % line.split("sda", 1)[1].split(" ", 1)[0]) else: parts = device.split("p") saveKernel(bootSlots, slotCode, "%sp%s" % (parts[0], int(parts[1]) - 1)) elif "bootcmd=" in line or " recovery " in line: # print("[MultiBoot] loadBootSlots DEBUG: 'bootcmd=' or ' recovery ' text found.") if slotCode not in bootSlots: bootSlots[slotCode] = {} # print("[MultiBoot] Boot Command/Recovery dictionary entry in slot '%s' created." % slotCode) value = bootSlots[slotCode].get("bootCodes") if value is None: bootSlots[slotCode]["bootCodes"] = [bootCode] else: bootSlots[slotCode]["bootCodes"].append(bootCode) value = bootSlots[slotCode].get("startupfile") if value is None: bootSlots[slotCode]["startupfile"] = {} bootSlots[slotCode]["startupfile"][bootCode] = file value = bootSlots[slotCode].get("cmdline") if value is None: bootSlots[slotCode]["cmdline"] = {} bootSlots[slotCode]["cmdline"][bootCode] = line else: print("[MultiBoot] Error: Slot can't be identified. (%s)" % line) else: print("[MultiBoot] Error: Slot code can not be determined from '%s'!" % file) self.console.ePopen([UMOUNT, UMOUNT, tempDir]) rmdir(tempDir) bootSlotsKeys = sorted(bootSlots.keys()) if self.debugMode: for slotCode in bootSlotsKeys: # print("[MultiBoot] loadBootSlots DEBUG: Boot slot '%s': %s" % (slotCode, bootSlots[slotCode])) print("[MultiBoot] Slot '%s':" % slotCode) modes = bootSlots[slotCode].get("bootCodes") if modes and modes != [""]: print("[MultiBoot] Boot modes: '%s'." % "', '".join(modes)) startupFile = bootSlots[slotCode].get("startupfile") if startupFile: if isinstance(startupFile, dict): if "" in startupFile: print("[MultiBoot] Startup file: '%s'." % startupFile[""]) else: print("[MultiBoot] Startup files:") for key in sorted(startupFile.keys()): print("[MultiBoot] Mode '%s': '%s'." % (key, startupFile[key])) else: print("[MultiBoot] Startup file: '%s'." % startupFile) commandLine = bootSlots[slotCode].get("cmdline") if commandLine: if isinstance(commandLine, dict): if "" in startupFile: print("[MultiBoot] Command line: '%s'." % startupFile[""]) else: print("[MultiBoot] Command lines:") for key in sorted(commandLine.keys()): print("[MultiBoot] Mode '%s': '%s'." % (key, commandLine[key])) else: print("[MultiBoot] Command line: '%s'." % startupFile) print("[MultiBoot] Kernel device: '%s'." % bootSlots[slotCode].get("kernel", "Unknown")) print("[MultiBoot] Root device: '%s'." % bootSlots[slotCode].get("device", "Unknown")) print("[MultiBoot] Root directory: '%s'." % bootSlots[slotCode].get("rootsubdir", "Unknown")) print("[MultiBoot] UBI device: '%s'." % ("Yes" if bootSlots[slotCode].get("ubi", False) else "No")) print("[MultiBoot] UUID device: '%s'." % ("Yes" if bootSlots[slotCode].get("uuid", False) else "No")) print("[MultiBoot] %d boot slots detected." % len(bootSlots)) return bootSlots, bootSlotsKeys def getParam(self, line, param): return line.replace("userdataroot", "rootuserdata").rsplit("%s=" % param, 1)[1].split(" ", 1)[0] def getUUIDtoDevice(self, UUID): # Returns None on failure. if UUID.startswith("UUID="): # Remove the "UUID=" from startup files that have it. UUID = UUID[5:] targetUUID = "UUID=\"%s\"" % UUID lines = check_output(["/sbin/blkid"]).decode(encoding="UTF-8", errors="ignore").split("\n") for line in lines: if targetUUID in line: return line.split(":")[0].strip() return None def loadCurrentSlotAndBootCodes(self): if self.bootSlots and self.bootSlotsKeys: for slotCode in self.bootSlotsKeys: cmdLines = self.bootSlots[slotCode]["cmdline"] bootCodes = sorted(self.bootSlots[slotCode]["cmdline"].keys()) for bootCode in bootCodes: if cmdLines[bootCode] == self.startupCmdLine: if self.debugMode: print("[MultiBoot] Startup slot code is '%s'%s." % (slotCode, " and boot mode is '%s'" % bootCode if bootCode else "")) return slotCode, bootCode return None, "" def canMultiBoot(self): return self.bootSlots != {} def getBootDevice(self): return self.bootDevice def getBootSlots(self): return self.bootSlots def getCurrentSlotAndBootCodes(self): return self.bootSlot, self.bootCode def getCurrentSlotCode(self): return self.bootSlot def getCurrentBootMode(self): return self.bootCode def hasRecovery(self): return "R" in self.bootSlots def getBootCodeDescription(self, bootCode=None): bootCodeDescriptions = { "": _("Normal: No boot modes required."), "1": _("Mode 1: Supports Kodi but PiP may not work"), "12": _("Mode 12: Supports PiP but Kodi may not work") } if bootCode is None: return bootCodeDescriptions return bootCodeDescriptions.get(bootCode, "") def getStartupFile(self, slotCode=None): slotCode = slotCode if slotCode in self.bootSlots else self.bootSlot return self.bootSlots[slotCode]["startupfile"][self.bootCode] def hasRootSubdir(self, slotCode=None): if slotCode is None: slotCode = slotCode if slotCode in self.bootSlots else self.bootSlot return "rootsubdir" in self.bootSlots[slotCode] def getSlotImageList(self, callback): self.imageList = {} if self.bootSlots: self.callback = callback self.tempDir = mkdtemp(prefix=PREFIX) self.slotCodes = self.bootSlotsKeys[:] self.findSlot() else: callback(self.imageList) def findSlot(self): # Part of getSlotImageList(). if self.slotCodes: self.slotCode = self.slotCodes.pop(0) hasMultiBootMTD = self.bootSlots[self.slotCode].get("ubi", False) self.imageList[self.slotCode] = { "ubi": hasMultiBootMTD, "bootCodes": self.bootSlots[self.slotCode].get("bootCodes", [""]), "device": self.bootSlots[self.slotCode].get("device", _("Unknown")), "devicelog": self.bootSlots[self.slotCode].get("device", "Unknown"), "root": self.bootSlots[self.slotCode].get("rootsubdir", _("Not required")), "rootlog": self.bootSlots[self.slotCode].get("rootsubdir", "Not required") } if self.slotCode == "A": self.imageList[self.slotCode]["detection"] = "Found an Android slot" self.imageList[self.slotCode]["imagename"] = _("Android") self.imageList[self.slotCode]["imagelogname"] = "Android" self.imageList[self.slotCode]["status"] = "android" self.findSlot() elif self.slotCode == "L": self.imageList[self.slotCode]["detection"] = "Found an Android Linux SE slot" self.imageList[self.slotCode]["imagename"] = _("Android Linux SE") self.imageList[self.slotCode]["imagelogname"] = "Android Linux SE" self.imageList[self.slotCode]["status"] = "androidlinuxse" self.findSlot() elif self.slotCode == "R" and fileHas("/proc/cmdline", "kexec=1"): self.imageList[self.slotCode]["detection"] = "Found a Root Image slot" self.imageList[self.slotCode]["imagename"] = _("Root Image") self.imageList[self.slotCode]["imagelogname"] = "Root Image" self.imageList[self.slotCode]["status"] = "rootimage" self.findSlot() elif self.slotCode == "R": self.imageList[self.slotCode]["detection"] = "Found a Recovery slot" self.imageList[self.slotCode]["imagename"] = _("Recovery") self.imageList[self.slotCode]["imagelogname"] = "Recovery" self.imageList[self.slotCode]["status"] = "recovery" self.findSlot() elif self.bootSlots[self.slotCode].get("device"): self.device = self.bootSlots[self.slotCode]["device"] # print("[MultiBoot] DEBUG: Analyzing slot='%s' (%s)." % (self.slotCode, self.device)) if hasMultiBootMTD: self.console.ePopen([MOUNT, MOUNT, "-t", "ubifs", self.device, self.tempDir], self.analyzeSlot) else: self.console.ePopen([MOUNT, MOUNT, self.device, self.tempDir], self.analyzeSlot) else: self.imageList[self.slotCode]["detection"] = "Found an unexpected/ill-defined slot" self.imageList[self.slotCode]["imagename"] = _("Unknown") self.imageList[self.slotCode]["imagelogname"] = "Unknown" self.imageList[self.slotCode]["status"] = "unknown" self.findSlot() else: rmdir(self.tempDir) if self.debugMode: for slotCode in sorted(self.imageList.keys()): # print("[MultiBoot] findSlot DEBUG: Image slot '%s': %s" % (slotCode, self.imageList[slotCode])) print("[MultiBoot] Slot '%s' content: '%s'." % (slotCode, self.imageList[slotCode].get("imagelogname", "Unknown"))) print("[MultiBoot] Device: '%s'." % self.imageList[slotCode].get("devicelog", "Unknown")) print("[MultiBoot] Root: '%s'." % self.imageList[slotCode].get("rootlog", "Unknown")) print("[MultiBoot] Detection: '%s'." % self.imageList[slotCode].get("detection", "Unknown")) print("[MultiBoot] Status: '%s'." % self.imageList[slotCode].get("status", "Unknown").capitalize()) modes = self.imageList[slotCode].get("bootCodes") if modes and modes != [""]: print("[MultiBoot] Boot modes: '%s'." % "', '".join(modes)) print("[MultiBoot] UBI device: '%s'." % ("Yes" if self.imageList[slotCode].get("ubi", False) else "No")) print("[MultiBoot] UUID device: '%s'." % ("Yes" if self.imageList[slotCode].get("uuid", False) else "No")) print("[MultiBoot] %d boot slots detected." % len(self.imageList)) self.callback(self.imageList) def analyzeSlot(self, data, retVal, extraArgs): # Part of getSlotImageList(). if retVal: self.imageList[self.slotCode]["detection"] = "Error %d: Unable to mount slot '%s' (%s) for analysis" % (retVal, self.slotCode, self.device) self.imageList[self.slotCode]["imagename"] = _("Inaccessible") self.imageList[self.slotCode]["imagelogname"] = "Inaccessible" self.imageList[self.slotCode]["status"] = "unknown" else: rootDir = self.bootSlots[self.slotCode].get("rootsubdir") imageDir = pathjoin(self.tempDir, rootDir) if rootDir else self.tempDir infoFile = pathjoin(imageDir, "usr/lib/enigma.info") if isfile(infoFile): info = self.readSlotInfo(infoFile) compileDate = str(info.get("compiledate")) revision = info.get("imgrevision") revision = ".%03d" % revision if info.get("distro") == "openvix" and isinstance(revision, int) else " %s" % revision revision = "" if revision.strip() == compileDate else revision compileDate = "%s-%s-%s" % (compileDate[0:4], compileDate[4:6], compileDate[6:8]) self.imageList[self.slotCode]["detection"] = "Found an enigma information file" self.imageList[self.slotCode]["imagename"] = "%s %s%s (%s)" % (info.get("displaydistro", info.get("distro")), info.get("imgversion"), revision, compileDate) self.imageList[self.slotCode]["imagelogname"] = "%s %s%s (%s)" % (info.get("displaydistro", info.get("distro")), info.get("imgversion"), revision, compileDate) self.imageList[self.slotCode]["status"] = "active" elif isfile(pathjoin(imageDir, "usr/bin/enigma2")): info = self.deriveSlotInfo(imageDir) compileDate = str(info.get("compiledate")) compileDate = "%s-%s-%s" % (compileDate[0:4], compileDate[4:6], compileDate[6:8]) self.imageList[self.slotCode]["detection"] = "Found an enigma2 binary file" self.imageList[self.slotCode]["imagename"] = "%s %s (%s)" % (info.get("displaydistro", info.get("distro")), info.get("imgversion"), compileDate) self.imageList[self.slotCode]["imagelogname"] = "%s %s (%s)" % (info.get("displaydistro", info.get("distro")), info.get("imgversion"), compileDate) self.imageList[self.slotCode]["status"] = "active" else: self.imageList[self.slotCode]["detection"] = "Found no enigma files" self.imageList[self.slotCode]["imagename"] = _("Empty") self.imageList[self.slotCode]["imagelogname"] = "Empty" self.imageList[self.slotCode]["status"] = "empty" if ismount(self.tempDir): self.console.ePopen([UMOUNT, UMOUNT, self.tempDir], self.finishSlot) else: self.findSlot() def finishSlot(self, data, retVal, extraArgs): # Part of getSlotImageList(). if retVal: print("[MultiBoot] finishSlot Error %d: Unable to unmount slot '%s' (%s)!" % (retVal, self.slotCode, self.device)) else: self.findSlot() def readSlotInfo(self, path): # Part of analyzeSlot() within getSlotImageList(). info = {} lines = fileReadLines(path, source=MODULE_NAME) if lines: if self.checkChecksum(lines): print("[MultiBoot] WARNING: Enigma information file found but checksum is incorrect!") for line in lines: if line.startswith("#") or line.strip() == "": continue if "=" in line: item, value = (x.strip() for x in line.split("=", 1)) if item: info[item] = self.processValue(value) lines = fileReadLines(path.replace(".info", ".conf"), source=MODULE_NAME) if lines: for line in lines: if line.startswith("#") or line.strip() == "": continue if "=" in line: item, value = (x.strip() for x in line.split("=", 1)) if item: if item in info: print("[MultiBoot] Note: Enigma information value '%s' with value '%s' being overridden to '%s'." % (item, info[item], value)) info[item] = self.processValue(value) return info def checkChecksum(self, lines): # Part of readSlotInfo() within analyzeSlot() within getSlotImageList(). value = "Undefined!" data = [] for line in lines: if line.startswith("checksum"): item, value = (x.strip() for x in line.split("=", 1)) else: data.append(line) data.append("") result = md5(bytearray("\n".join(data), "UTF-8", errors="ignore")).hexdigest() # NOSONAR return value != result def processValue(self, value): # Part of readSlotInfo() within analyzeSlot() within getSlotImageList(). valueTest = value.upper() if value else "" if value is None: pass elif (value.startswith("\"") or value.startswith("'")) and value.endswith(value[0]): value = value[1:-1] elif value.startswith("(") and value.endswith(")"): data = [] for item in [x.strip() for x in value[1:-1].split(",")]: data.append(self.processValue(item)) value = tuple(data) elif value.startswith("[") and value.endswith("]"): data = [] for item in [x.strip() for x in value[1:-1].split(",")]: data.append(self.processValue(item)) value = list(data) elif valueTest == "NONE": value = None elif valueTest in ("FALSE", "NO", "OFF", "DISABLED"): value = False elif valueTest in ("TRUE", "YES", "ON", "ENABLED"): value = True elif value.isdigit() or (value[0:1] in ("-", "+") and value[1:].isdigit()): value = int(value) elif valueTest.startswith("0X"): try: value = int(value, 16) except ValueError: pass elif valueTest.startswith("0O"): try: value = int(value, 8) except ValueError: pass elif valueTest.startswith("0B"): try: value = int(value, 2) except ValueError: pass else: try: value = float(value) except ValueError: pass return value def deriveSlotInfo(self, path): # Part of analyzeSlot() within getSlotImageList(). info = {} try: date = datetime.fromtimestamp(stat(pathjoin(path, "var/lib/opkg/status")).st_mtime).strftime("%Y%m%d") if date.startswith("1970"): date = datetime.fromtimestamp(stat(pathjoin(path, "usr/share/bootlogo.mvi")).st_mtime).strftime("%Y%m%d") date = max(date, datetime.fromtimestamp(stat(pathjoin(path, "usr/bin/enigma2")).st_mtime).strftime("%Y%m%d")) except OSError as err: date = "00000000" info["compiledate"] = date lines = fileReadLines(pathjoin(path, "etc/issue"), source=MODULE_NAME) if lines and "vuplus" not in lines[0]: data = lines[-2].strip()[:-6].split() info["distro"] = " ".join(data[:-1]) info["displaydistro"] = { "beyonwiz": "Beyonwiz", "blackhole": "Black Hole", "egami": "EGAMI", "openatv": "openATV", "openbh": "OpenBH", "opendroid": "OpenDroid", "openeight": "OpenEight", "openhdf": "OpenHDF", "opennfr": "OpenNFR", "openpli": "OpenPLi", "openspa": "OpenSpa", "openvision": "Open Vision", "openvix": "OpenViX", "sif": "Sif", "teamblue": "teamBlue", "vti": "VTi" }.get(info["distro"].lower(), info["distro"].capitalize()) info["imgversion"] = data[-1] else: info["distro"] = "Enigma2" info["displaydistro"] = "Enigma2" info["imgversion"] = "???" return info def activateSlot(self, slotCode, bootCode, callback): self.slotCode = slotCode self.bootCode = bootCode self.callback = callback self.tempDir = mkdtemp(prefix=PREFIX) self.console.ePopen([MOUNT, MOUNT, self.bootDevice, self.tempDir], self.bootDeviceMounted) def bootDeviceMounted(self, data, retVal, extraArgs): # Part of activateSlot(). if retVal: print("[MultiBoot] bootDeviceMounted Error %d: Unable to mount boot device '%s'!" % (retVal, self.bootDevice)) self.callback(1) else: bootSlot = self.bootSlots[self.slotCode] startup = bootSlot["startupfile"][self.bootCode] if fileHas("/proc/cmdline", "kexec=1") and startup == STARTUP_RECOVERY: target = STARTUP_FILE else: target = STARTUP_ONCE if startup == STARTUP_RECOVERY else STARTUP_FILE if exists(DREAM_BOOT_FILE) and startup == STARTUP_RECOVERY: pass else: copyfile(pathjoin(self.tempDir, startup), pathjoin(self.tempDir, target)) if exists(DUAL_BOOT_FILE): slot = self.slotCode if self.slotCode.isdecimal() else "0" with open(DUAL_BOOT_FILE, "wb") as fd: fd.write(pack("B", int(slot))) if exists(DREAM_BOOT_FILE): if self.slotCode == "R": cmd_count = 0 with open(DREAM_BOOT_FILE, "r") as fd: lines = fd.readlines() for line in lines: if line.startswith("cmd="): cmd_count += 1 self.slotCode = str(cmd_count) slot = int(self.slotCode) - 1 with open(DREAM_BOOT_FILE, "r+") as fd: lines = fd.readlines() fd.seek(0) for line in lines: if line.startswith("default="): line = f"default={slot}\n" fd.write(line) fd.truncate() if self.debugMode: print("[MultiBoot] Installing '%s' as '%s'." % (startup, target)) self.console.ePopen([UMOUNT, UMOUNT, self.tempDir], self.bootDeviceUnmounted) def bootDeviceUnmounted(self, data, retVal, extraArgs): # Part of activateSlot(). if retVal: print("[MultiBoot] bootDeviceUnmounted Error %d: Unable to mount boot device '%s'!" % (retVal, self.bootDevice)) self.callback(2) else: rmdir(self.tempDir) self.callback(0) def emptySlot(self, slotCode, callback): self.manageSlot(slotCode, callback, self.hideSlot) def restoreSlot(self, slotCode, callback): self.manageSlot(slotCode, callback, self.revealSlot) def manageSlot(self, slotCode, callback, method): # Part of emptySlot() and restoreSlot(). if self.bootSlots: self.slotCode = slotCode self.callback = callback self.device = self.bootSlots[self.slotCode]["device"] self.tempDir = mkdtemp(prefix=PREFIX) if self.bootSlots[self.slotCode].get("ubi", False): self.console.ePopen([MOUNT, MOUNT, "-t", "ubifs", self.device, self.tempDir], method) else: self.console.ePopen([MOUNT, MOUNT, self.device, self.tempDir], method) else: self.callback(1) def hideSlot(self, data, retVal, extraArgs): # Part of emptySlot(). if retVal: print("[MultiBoot] hideSlot Error %d: Unable to mount slot '%s' (%s)!" % (retVal, self.slotCode, self.device)) self.callback(2) else: rootDir = self.bootSlots[self.slotCode].get("rootsubdir") imageDir = pathjoin(self.tempDir, rootDir) if rootDir else self.tempDir if self.bootSlots[self.slotCode].get("ubi", False) or fileHas("/proc/cmdline", "kexec=1"): try: if isfile(pathjoin(imageDir, "usr/bin/enigma2")): self.console.ePopen([REMOVE, REMOVE, "-rf", imageDir]) mkdir(imageDir) except OSError as err: print("[MultiBoot] hideSlot Error %d: Unable to wipe all files in slot '%s' (%s)! (%s)" % (err.errno, self.slotCode, self.device, err.strerror)) else: enigmaFile = "" # This is in case the first pathjoin fails. try: enigmaFile = pathjoin(imageDir, "usr/bin/enigma2") if isfile(enigmaFile): rename(enigmaFile, "%sx.bin" % enigmaFile) enigmaFile = pathjoin(imageDir, "usr/lib/enigma.info") if isfile(enigmaFile): rename(enigmaFile, "%sx" % enigmaFile) enigmaFile = pathjoin(imageDir, "etc") if isdir(enigmaFile): rename(enigmaFile, "%sx" % enigmaFile) except OSError as err: print("[MultiBoot] hideSlot Error %d: Unable to hide item '%s' in slot '%s' (%s)! (%s)" % (err.errno, enigmaFile, self.slotCode, self.device, err.strerror)) self.console.ePopen([UMOUNT, UMOUNT, self.tempDir], self.cleanUpSlot) def revealSlot(self, data, retVal, extraArgs): # Part of restoreSlot(). if retVal: print("[MultiBoot] revealSlot Error %d: Unable to mount slot '%s' (%s)!" % (retVal, self.slotCode, self.device)) self.callback(2) else: rootDir = self.bootSlots[self.slotCode].get("rootsubdir") imageDir = pathjoin(self.tempDir, rootDir) if rootDir else self.tempDir enigmaFile = "" # This is in case the first pathjoin fails. try: enigmaFile = pathjoin(imageDir, "usr/bin/enigma2") hiddenFile = "%sx.bin" % enigmaFile if isfile(hiddenFile): rename(hiddenFile, enigmaFile) enigmaFile = pathjoin(imageDir, "usr/lib/enigma.info") hiddenFile = "%sx" % enigmaFile if isfile(hiddenFile): rename(hiddenFile, enigmaFile) enigmaFile = pathjoin(imageDir, "etc") hiddenFile = "%sx" % enigmaFile if isdir(hiddenFile): rename(hiddenFile, enigmaFile) except OSError as err: print("[MultiBoot] revealSlot Error %d: Unable to reveal item '%s' in slot '%s' (%s)! (%s)" % (err.errno, enigmaFile, self.slotCode, self.device, err.strerror)) self.console.ePopen([UMOUNT, UMOUNT, self.tempDir], self.cleanUpSlot) def cleanUpSlot(self, data, retVal, extraArgs): # Part of emptySlot() and restoreSlot(). if retVal: print("[MultiBoot] emptySlotCleanUp Error %d: Unable to unmount slot '%s' (%s)!" % (retVal, self.slotCode, self.device)) self.callback(3) else: rmdir(self.tempDir) self.callback(0) MultiBoot = MultiBootClass()