# SPDX-License-Identifier: GPL-2.0-only # This code is based on MFSUtil by Youness Alaoui (see `doc/LICENSE.orig` for original copyright) import posixpath import struct from functools import cmp_to_key def cmp(a, b): return (a > b) - (a < b) class CFGAlignment: ALIGN_NONE = 0 ALIGN_START = 1 ALIGN_END = 2 class CFG(object): CFG_FMT = struct.Struct(" 1 file = self.getFile(posixpath.join(*path)) assert file and file.isDirectory() assert file.record.mode == record.mode path.pop() else: file = CFGFile(posixpath.join(*path + [record.name]), record, self.data, parent) self.files.append(file) if record.isDirectory(): path.append(record.name) parent = self.getFile(posixpath.join(*path)) self.records.append(record) def getFile(self, path): for file in self.files: if file.path == path: return file return None def removeFile(self, path, recursive = False): file = self.getFile(path) if file: if len(file.children) > 0 and not recursive: return False # Copy list of children since we're modifying the list for child in file.children[:]: self.removeFile(child.path, recursive) self.files.remove(file) if file.parent: file.parent.removeChild(file) return True else: return False def addFile(self, path, data, mode, opt, uid, gid): # Make sure it doesn't already exists file = self.getFile(path) if file: raise ValueError(f"CFG path {path} already exists") directory = False (parent_path, filename) = posixpath.split(path) if filename == "": directory = True (parent_path, filename) = posixpath.split(parent_path) # Make sure parent exists if it is not the root parent = self.getFile(parent_path) if parent_path != "/"and parent is None: raise ValueError(f"CFG path {path} already exists") record = CFGRecord.createRecord(filename, mode, opt, uid, gid, len(data), 0) file = CFGFile(path, record, data, parent) self.files.append(file) def generate(self, alignment): self.records = [] file_data = b"" if len(self.files) > 0: (self.records, file_data) = self.files[0].generateRecords(alignment=alignment) self.num_records = len(self.records) self.data = self.CFG_FMT.pack(self.num_records) data_offset = len(self.data) + CFGRecord.RECORD_FMT.size * self.num_records alignment_data = b"" if alignment != CFGAlignment.ALIGN_NONE: alignment_extra = data_offset % 0x40 if alignment_extra > 0: alignment_data += struct.pack("> i): ret += modeStr[i] else: ret += "-" return ret @staticmethod def strToMode(str): modeStr = "dAEIrwxrwxrwx" assert len(str) == len(modeStr) mode = 0 for i in range(13): if str[i] == modeStr[i]: mode |= (0x1000 >> i) else: assert str[i] == '-' or str[i] == ' ' return mode @staticmethod def optToStr(opt): assert opt & 0xFFF0 == 0 optStr = "?!MF" ret = "" for i in range(4): if opt & (8 >> i): ret += optStr[i] else: ret += "-" return ret @staticmethod def strToOpt(str): optStr = "?!MF" assert len(str) == len(optStr) opt = 0 for i in range(4): if str[i] == optStr[i]: opt |= (8 >> i) else: assert str[i] == '-' or str[i] == ' ' return opt class CFGRecord(object): RECORD_FMT = struct.Struct("<12sHHHHHHL") def __init__(self, data, index): offset = CFG.CFG_FMT.size + self.RECORD_FMT.size * index self.data = data[offset:offset + self.RECORD_FMT.size] (self.name, zero, self.mode, self.opt, self.size, self.uid, self.gid, self.offset) = self.RECORD_FMT.unpack(self.data) self.name = self.name.decode('utf-8') self.name = self.name.strip('\0') if self.name == "..": assert self.isDirectory() assert self.opt == 0 if self.isDirectory(): assert self.size == 0 def isDirectory(self): return self.mode & 0x1000 == 0x1000 def generate(self): self.data = CFGRecord.RECORD_FMT.pack(self.name.encode("utf-8"), 0, self.mode, self.opt, self.size, self.uid, self.gid, self.offset) @staticmethod def createRecord(name, mode, opt, uid, gid, size, offset): data = b'\0' * CFG.CFG_FMT.size + \ CFGRecord.RECORD_FMT.pack(name.encode("utf-8"), 0, mode, opt, size, uid, gid, offset) return CFGRecord(data, 0) def copy(self): return self.createRecord(self.name, self.mode, self.opt, self.uid, self.gid, self.size, self.offset) def __str__(self): return "%-12s (%04X:%04X) [%4d bytes @ %8X] %s _ %s" % (self.name, self.uid, self.gid, self.size, self.offset, CFG.modeToStr(self.mode), CFG.optToStr(self.opt)) class CFGFile(object): def __init__(self, path, record, data, parent=None): self.path = path self.record = record self.data = data[record.offset:record.offset + record.size] self.parent = parent self.children = [] if parent: parent.addChild(self) @property def size(self): return self.record.size def isDirectory(self): return self.record.isDirectory() def addChild(self, child): assert self.isDirectory() self.children.append(child) self.children.sort(key=cmp_to_key(CFGFile.__cmp__)) def removeChild(self, child): assert self.isDirectory() and child in self.children self.children.remove(child) def generateRecords(self, data = b"", alignment=CFGAlignment.ALIGN_NONE): self.record.size = 0 if self.isDirectory() else self.size self.record.offset = 0 if self.isDirectory() else len(data) records = [self.record] if self.isDirectory(): for child in self.children: (sub_records, new_data) = child.generateRecords(data, alignment) records += sub_records data = new_data dotdot = self.record.copy() dotdot.name = '..' dotdot.opt = 0 records.append(dotdot) else: alignment_extra = 0 if alignment == CFGAlignment.ALIGN_START: alignment_extra = self.record.offset % 0x40 elif self.record.size != 0 and alignment == CFGAlignment.ALIGN_END: alignment_extra = (self.record.offset + self.record.size) % 0x40 if alignment_extra > 0: data += struct.pack("