mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-22 01:13:00 +02:00
210 lines
6.1 KiB
Python
Executable File
210 lines
6.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import os
|
|
import random
|
|
import struct
|
|
|
|
class ID:
|
|
def __init__(self):
|
|
charset = "abcdefghijklmnopqrstuvwxyz"
|
|
charset += "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
charset += "0123456789"
|
|
|
|
b = random.choices(charset, k=32)
|
|
self.bytes = "".join(b).encode()
|
|
|
|
def dashed(self):
|
|
return self.bytes[0:6].decode() + \
|
|
"-" + self.bytes[6:10].decode() + \
|
|
"-" + self.bytes[10:14].decode() + \
|
|
"-" + self.bytes[14:18].decode() + \
|
|
"-" + self.bytes[18:22].decode() + \
|
|
"-" + self.bytes[22:26].decode() + \
|
|
"-" + self.bytes[26:32].decode()
|
|
|
|
def crc32(data: bytes) -> int:
|
|
crc = 0xf597a6cf
|
|
|
|
for byte in data:
|
|
crc ^= byte
|
|
for _ in range(8):
|
|
if crc & 1:
|
|
crc = (crc >> 1) ^ 0xedb88320
|
|
else:
|
|
crc >>= 1
|
|
|
|
return crc & 0xffffffff
|
|
|
|
def sendfile(dst, src, count):
|
|
while count > 0:
|
|
chunk = os.sendfile(dst, src, None, count)
|
|
if chunk < 0:
|
|
raise OSError("sendfile")
|
|
|
|
count -= chunk
|
|
|
|
class Linear:
|
|
def __init__(self, start, align, path, name=None):
|
|
self.id = ID()
|
|
self.name = name if name else os.path.basename(path)
|
|
self.path = path
|
|
self.start = start
|
|
self.align = align
|
|
self.size = os.path.getsize(path)
|
|
self.asize = (self.size + align - 1) & ~(align - 1)
|
|
|
|
def sendfile(self, fd):
|
|
pad = self.asize - self.size
|
|
|
|
with open(self.path, "rb") as f:
|
|
sendfile(fd, f.fileno(), self.size)
|
|
|
|
if pad:
|
|
os.write(fd, b"\0" * pad)
|
|
|
|
def meta(self):
|
|
return f""" {self.name} {{
|
|
id = "{self.id.dashed()}"
|
|
status = ["READ", "WRITE", "VISIBLE"]
|
|
segment_count = 1
|
|
|
|
segment1 {{
|
|
start_extent = 0
|
|
extent_count = {self.asize // self.align }
|
|
type = "striped"
|
|
stripe_count = 1
|
|
stripes = [
|
|
"pv0", {self.start // self.align }
|
|
]
|
|
}}
|
|
}}"""
|
|
|
|
class InternalPV:
|
|
def __init__(self, images=[], align=(1 << 20), creation_time=0):
|
|
self.pvid = ID()
|
|
self.vgid = ID()
|
|
self.align = align
|
|
self.creation_time = creation_time
|
|
|
|
self.lvs = []
|
|
self.dsize = 0
|
|
for img in images:
|
|
lv = Linear(self.dsize, align, img)
|
|
self.lvs.append(lv)
|
|
self.dsize += lv.asize
|
|
|
|
# Reserve `align` bytes for primary metadata area, the size
|
|
# used by all LVs, and another `aligned` bytes for the backup
|
|
# metadata area
|
|
self.size = align + self.dsize + align
|
|
|
|
def write(self, dst):
|
|
for i in range(4):
|
|
dst.write(self._label(i))
|
|
|
|
dst.write(b"\0" * 0x800)
|
|
|
|
dst.write(self._metadata(True))
|
|
|
|
for lv in self.lvs:
|
|
lv.sendfile(dst.fileno())
|
|
|
|
dst.write(self._metadata(False))
|
|
|
|
def _label(self, sector: int):
|
|
headfmt, tailfmt = "<8s Q I", "<I8s 32sQ QQ QQ QQ QQ QQ II"
|
|
padsize = 512 - struct.calcsize(headfmt) - struct.calcsize(tailfmt)
|
|
|
|
tail = struct.pack(tailfmt,
|
|
32, b"LVM2 001",
|
|
self.pvid.bytes, self.size,
|
|
# Data area (size=0 ~= all available space (?))
|
|
self.align, 0,
|
|
0, 0,
|
|
# Primary metadata area
|
|
0x1000, self.align - 0x1000,
|
|
# Backup metadata area
|
|
self.align + self.dsize, self.align,
|
|
0, 0,
|
|
# Supported extensions (Unsure which
|
|
# extensions this maps to, but it makes the
|
|
# LVM tooling happy :))
|
|
2, 1) + b"\0" * padsize
|
|
|
|
head = struct.pack(headfmt, b"LABELONE", sector, crc32(tail))
|
|
|
|
return head + tail
|
|
|
|
def _metadata(self, primary):
|
|
headfmt, tailfmt = "<I", "<16sI QQ QQII QQII"
|
|
padsize = 512 - struct.calcsize(headfmt) - struct.calcsize(tailfmt)
|
|
|
|
offs = 0x1000 if primary else (self.align + self.dsize)
|
|
size = self.align - 0x1000 if primary else self.align
|
|
|
|
txt = self._meta_txt()
|
|
txtpadsize = size - 512 - len(txt)
|
|
|
|
tail = struct.pack(tailfmt,
|
|
b" LVM2 x[5A%r0N*>", 1,
|
|
offs, size,
|
|
512, len(txt), crc32(txt), 0,
|
|
0, 0, 0, 0) + b"\0" * padsize
|
|
|
|
head = struct.pack(headfmt, crc32(tail))
|
|
|
|
return head + tail + txt + bytes([0] * txtpadsize)
|
|
|
|
def _meta_txt(self):
|
|
return f"""internal {{
|
|
id = "{self.vgid.dashed()}"
|
|
seqno = 1
|
|
status = ["RESIZEABLE", "READ", "WRITE"]
|
|
extent_size = {self.align >> 9}
|
|
max_pv = 0
|
|
max_lv = 0
|
|
metadata_copies = 2
|
|
|
|
physical_volumes {{
|
|
pv0 {{
|
|
id = "{self.pvid.dashed()}"
|
|
dev = "/dev/internal"
|
|
status = ["ALLOCATABLE"]
|
|
dev_size = {self.size >> 9}
|
|
pe_start = {self.align >> 9}
|
|
pe_count = {self.dsize // self.align }
|
|
}}
|
|
}}
|
|
|
|
logical_volumes {{
|
|
{"\n".join([lv.meta() for lv in self.lvs])}
|
|
}}
|
|
}}
|
|
|
|
contents = "Text Format Volume Group"
|
|
version = 1
|
|
description = "Internal image storage"
|
|
creation_host = "infix-build-system"
|
|
creation_time = {self.creation_time}
|
|
""".encode()
|
|
|
|
|
|
def main():
|
|
import argparse
|
|
import datetime
|
|
import sys
|
|
|
|
parser = argparse.ArgumentParser(description="Create LVM PV containing a set of images")
|
|
parser.add_argument("-T", "--creation-time", metavar="TIMESTAMP",
|
|
default=int(datetime.datetime.now().timestamp()))
|
|
parser.add_argument("images", metavar="IMAGE", nargs="*")
|
|
args = parser.parse_args()
|
|
|
|
random.seed(args.creation_time)
|
|
|
|
pv = InternalPV(args.images, creation_time=args.creation_time)
|
|
pv.write(sys.stdout.buffer)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|