Skip to content

Commit c67e179

Browse files
committed
Add fast xva_bridge.py script
Signed-off-by: Tu Dinh <[email protected]>
1 parent 2f30ecc commit c67e179

File tree

1 file changed

+125
-0
lines changed

1 file changed

+125
-0
lines changed

scripts/xva_bridge.py

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
#!/usr/bin/env python3
2+
3+
# Tested on libarchive-c==5.3. Due to our use of library internals, may not work on other versions of libarchive-c.
4+
5+
import argparse
6+
import io
7+
import logging
8+
from xml.dom import minidom
9+
10+
import libarchive
11+
import libarchive.ffi
12+
13+
class XvaHeaderMember:
14+
def __init__(self, member: minidom.Element):
15+
self.member = member
16+
17+
def get_name(self):
18+
for child in self.member.childNodes:
19+
if child.nodeType == minidom.Node.ELEMENT_NODE and child.tagName == "name" and child.firstChild:
20+
return child.firstChild.nodeValue
21+
return None
22+
23+
def get_value(self):
24+
for child in self.member.childNodes:
25+
if child.nodeType == minidom.Node.ELEMENT_NODE and child.tagName == "value" and child.firstChild:
26+
return child.firstChild.nodeValue
27+
return None
28+
29+
def set_value(self, value: str):
30+
for child in self.member.childNodes:
31+
if child.nodeType == minidom.Node.ELEMENT_NODE and child.tagName == "value" and child.firstChild:
32+
child.firstChild.nodeValue = value # type: ignore
33+
return None
34+
35+
36+
class XvaHeader:
37+
def __init__(self, header_bytes: bytes):
38+
self.xml = minidom.parseString(header_bytes.decode())
39+
40+
def members(self):
41+
for member in self.xml.getElementsByTagName("member"):
42+
if member.nodeType == minidom.Node.ELEMENT_NODE:
43+
yield XvaHeaderMember(member)
44+
45+
def get_bridge(self):
46+
for member in self.members():
47+
member_name = member.get_name()
48+
if member_name == "bridge":
49+
return member.get_value()
50+
raise ValueError("Could not find bridge value in XVA header")
51+
52+
def set_bridge(self, bridge: str):
53+
for member in self.members():
54+
member_name = member.get_name()
55+
if member_name == "bridge":
56+
member.set_value(bridge)
57+
return
58+
raise ValueError("Could not find bridge value in XVA header")
59+
60+
61+
if __name__ == "__main__":
62+
parser = argparse.ArgumentParser()
63+
parser.add_argument("xva")
64+
parser.add_argument(
65+
"--set-bridge", help="New bridge value of format `xenbr0|xapi[:9]|...`; omit this option to show current bridge"
66+
)
67+
parser.add_argument(
68+
"--compression",
69+
choices=["zstd", "gzip"],
70+
default="zstd",
71+
help="Compression mode of new XVA when setting bridge value (default: zstd)",
72+
)
73+
parser.add_argument("-o", "--output")
74+
parser.add_argument("-v", "--verbose", action="store_true")
75+
args = parser.parse_args()
76+
77+
if args.verbose:
78+
logging.getLogger().setLevel(logging.DEBUG)
79+
else:
80+
logging.getLogger().setLevel(logging.INFO)
81+
82+
with libarchive.file_reader(args.xva, "tar") as input_file:
83+
logging.debug(f"Input filters: {', '.join(filter.decode() for filter in input_file.filter_names)}")
84+
85+
entry_iter = iter(input_file)
86+
87+
header_entry = next(entry_iter)
88+
if header_entry.pathname != "ova.xml":
89+
raise ValueError("Unexpected header entry name")
90+
with io.BytesIO() as header_writer:
91+
for block in header_entry.get_blocks():
92+
header_writer.write(block)
93+
header_bytes = header_writer.getvalue()
94+
95+
logging.debug(f"Header is {len(header_bytes)} bytes")
96+
97+
header = XvaHeader(header_bytes)
98+
bridge = header.get_bridge()
99+
logging.info(f"Found bridge {bridge}")
100+
101+
if args.set_bridge:
102+
output_path = args.output
103+
if not output_path:
104+
output_path = args.xva + ".new"
105+
logging.info(f"Output path: {output_path}")
106+
107+
header.set_bridge(args.set_bridge)
108+
new_header_bytes = header.xml.toxml().encode()
109+
110+
with libarchive.file_writer(output_path, "pax_restricted", args.compression) as output_file:
111+
output_file.add_file_from_memory(
112+
"ova.xml", len(new_header_bytes), new_header_bytes, permission=0o400, uid=0, gid=0
113+
)
114+
115+
for entry in entry_iter:
116+
logging.debug(f"Copying {entry.pathname}: {entry.size} bytes")
117+
new_entry = libarchive.ArchiveEntry(entry.header_codec, perm=0o400, uid=0, gid=0)
118+
for attr in ["filetype", "pathname", "size"]:
119+
setattr(new_entry, attr, getattr(entry, attr))
120+
121+
# ArchiveEntry doesn't expose block copying, so write the entry manually via the FFI interface
122+
libarchive.ffi.write_header(output_file._pointer, new_entry._entry_p)
123+
for block in entry.get_blocks():
124+
libarchive.ffi.write_data(output_file._pointer, block, len(block))
125+
libarchive.ffi.write_finish_entry(output_file._pointer)

0 commit comments

Comments
 (0)