mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-10-01 02:56:41 +00:00
117 lines
3.4 KiB
Python
117 lines
3.4 KiB
Python
#!/usr/bin/env python3
|
|||
|
|
"""Clone a FUSE mount from an LXC namespace into the Proxmox host namespace."""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import ctypes
|
||
|
|
import os
|
||
|
|
import platform
|
||
|
|
import sys
|
||
|
|
|
||
|
|
|
||
|
|
AT_FDCWD = -100
|
||
|
|
AT_EMPTY_PATH = 0x1000
|
||
|
|
AT_RECURSIVE = 0x8000
|
||
|
|
CLONE_NEWNS = 0x00020000
|
||
|
|
MOVE_MOUNT_F_EMPTY_PATH = 0x00000004
|
||
|
|
MOUNT_ATTR_RDONLY = 0x00000001
|
||
|
|
OPEN_TREE_CLONE = 1
|
||
|
|
|
||
|
|
SYSCALLS = {
|
||
|
|
"x86_64": (428, 429, 442),
|
||
|
|
"amd64": (428, 429, 442),
|
||
|
|
"aarch64": (428, 429, 442),
|
||
|
|
"arm64": (428, 429, 442),
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
class MountAttr(ctypes.Structure):
|
||
|
|
_fields_ = [
|
||
|
|
("attr_set", ctypes.c_uint64),
|
||
|
|
("attr_clr", ctypes.c_uint64),
|
||
|
|
("propagation", ctypes.c_uint64),
|
||
|
|
("userns_fd", ctypes.c_uint64),
|
||
|
|
]
|
||
|
|
|
||
|
|
|
||
|
|
def fail(step: str) -> None:
|
||
|
|
error = ctypes.get_errno()
|
||
|
|
raise OSError(error, f"{step}: {os.strerror(error)}")
|
||
|
|
|
||
|
|
|
||
|
|
def main() -> int:
|
||
|
|
if len(sys.argv) != 5 or sys.argv[4] not in {"rw", "ro"}:
|
||
|
|
print(f"usage: {sys.argv[0]} PID SOURCE TARGET rw|ro", file=sys.stderr)
|
||
|
|
return 2
|
||
|
|
machine = platform.machine().lower()
|
||
|
|
if machine not in SYSCALLS:
|
||
|
|
print(f"unsupported host architecture: {machine}", file=sys.stderr)
|
||
|
|
return 2
|
||
|
|
open_tree_nr, move_mount_nr, mount_setattr_nr = SYSCALLS[machine]
|
||
|
|
pid, source, target, mode = sys.argv[1:]
|
||
|
|
libc = ctypes.CDLL(None, use_errno=True)
|
||
|
|
libc.syscall.restype = ctypes.c_long
|
||
|
|
libc.setns.argtypes = (ctypes.c_int, ctypes.c_int)
|
||
|
|
libc.setns.restype = ctypes.c_int
|
||
|
|
|
||
|
|
host_ns = os.open("/proc/self/ns/mnt", os.O_RDONLY | os.O_CLOEXEC)
|
||
|
|
host_root = os.open("/", os.O_PATH | os.O_DIRECTORY | os.O_CLOEXEC)
|
||
|
|
ct_ns = os.open(f"/proc/{pid}/ns/mnt", os.O_RDONLY | os.O_CLOEXEC)
|
||
|
|
ct_root = os.open(f"/proc/{pid}/root", os.O_PATH | os.O_DIRECTORY | os.O_CLOEXEC)
|
||
|
|
try:
|
||
|
|
if libc.setns(ct_ns, CLONE_NEWNS) != 0:
|
||
|
|
fail("enter container namespace")
|
||
|
|
os.fchdir(ct_root)
|
||
|
|
os.chroot(".")
|
||
|
|
os.chdir("/")
|
||
|
|
tree = libc.syscall(
|
||
|
|
open_tree_nr,
|
||
|
|
AT_FDCWD,
|
||
|
|
os.fsencode(source),
|
||
|
|
OPEN_TREE_CLONE | os.O_CLOEXEC,
|
||
|
|
)
|
||
|
|
if tree < 0:
|
||
|
|
fail("clone source mount tree")
|
||
|
|
try:
|
||
|
|
if mode == "ro":
|
||
|
|
attributes = MountAttr(attr_set=MOUNT_ATTR_RDONLY)
|
||
|
|
result = libc.syscall(
|
||
|
|
mount_setattr_nr,
|
||
|
|
tree,
|
||
|
|
ctypes.c_char_p(b""),
|
||
|
|
AT_EMPTY_PATH | AT_RECURSIVE,
|
||
|
|
ctypes.byref(attributes),
|
||
|
|
ctypes.sizeof(attributes),
|
||
|
|
)
|
||
|
|
if result != 0:
|
||
|
|
fail("make cloned mount tree read-only")
|
||
|
|
if libc.setns(host_ns, CLONE_NEWNS) != 0:
|
||
|
|
fail("return to host namespace")
|
||
|
|
os.fchdir(host_root)
|
||
|
|
os.chroot(".")
|
||
|
|
os.chdir("/")
|
||
|
|
result = libc.syscall(
|
||
|
|
move_mount_nr,
|
||
|
|
tree,
|
||
|
|
ctypes.c_char_p(b""),
|
||
|
|
AT_FDCWD,
|
||
|
|
os.fsencode(target),
|
||
|
|
MOVE_MOUNT_F_EMPTY_PATH,
|
||
|
|
)
|
||
|
|
if result != 0:
|
||
|
|
fail("publish mount tree")
|
||
|
|
finally:
|
||
|
|
os.close(tree)
|
||
|
|
finally:
|
||
|
|
for descriptor in (ct_root, ct_ns, host_root, host_ns):
|
||
|
|
os.close(descriptor)
|
||
|
|
return 0
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
try:
|
||
|
|
raise SystemExit(main())
|
||
|
|
except OSError as exc:
|
||
|
|
print(exc, file=sys.stderr)
|
||
|
|
raise SystemExit(1)
|