From 93e1c60460e5b5a1d6a91c62e5ab5e0d281f5fcf Mon Sep 17 00:00:00 2001 From: Christian Kellner Date: Tue, 29 Oct 2019 19:19:32 +0100 Subject: [PATCH] api: new host side API to be used by the container Introduce an osbuild API that can be used by the container to talk to the osbuild host. It currently supports one method 'setup-stdio' which should be used by the container to setup its standard input/ output so the stages can transparently do i/o with the osbuild host via stdio. The input data (args) is written to a temp-file backed buffer. The output is either the host's stdout directly or another temp-file backed buffer; the latter is re-opened (via /proc/self/fd) to get another file-descriptor for the container, so in theory the host and the container could do i/o to the same buffer independently. --- osbuild/api.py | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 osbuild/api.py diff --git a/osbuild/api.py b/osbuild/api.py new file mode 100644 index 00000000..098bed42 --- /dev/null +++ b/osbuild/api.py @@ -0,0 +1,75 @@ +import array +import asyncio +import json +import os +import tempfile +import threading +import sys + + +from . import remoteloop + + +class API: + def __init__(self, sock, args, interactive): + self.sock = sock + self.input = args + self.interactive = interactive + self._output = None + self.event_loop = asyncio.new_event_loop() + self.event_loop.add_reader(self.sock, self._dispatch) + self.thread = threading.Thread(target=self._run_event_loop) + + @property + def output(self): + return self._output and self._output.read() + + def _prepare_input(self): + with tempfile.TemporaryFile() as fd: + fd.write(json.dumps(self.input).encode('utf-8')) + # re-open the file to get a read-only file descriptor + return open(f"/proc/self/fd/{fd.fileno()}", "r") + + def _prepare_output(self): + if self.interactive: + return os.fdopen(os.dup(sys.stdout.fileno()), 'w') + out = tempfile.TemporaryFile(mode="wb") + fd = os.open(f"/proc/self/fd/{out.fileno()}", os.O_RDONLY|os.O_CLOEXEC) + self._output = os.fdopen(fd) + return out + + def _setup_stdio(self, addr): + with self._prepare_input() as stdin, \ + self._prepare_output() as stdout: + msg = {} + fds = array.array("i") + fds.append(stdin.fileno()) + msg['stdin'] = 0 + fds.append(stdout.fileno()) + msg['stdout'] = 1 + fds.append(stdout.fileno()) + msg['stderr'] = 2 + remoteloop.dump_fds(self.sock, msg, fds, addr=addr) + + def __del__(self): + self.sock.close() + + def _dispatch(self): + msg, addr = self.sock.recvfrom(1024) + args = json.loads(msg) + if args["method"] == 'setup-stdio': + self._setup_stdio(addr) + + def _run_event_loop(self): + # Set the thread-local event loop + asyncio.set_event_loop(self.event_loop) + # Run event loop until stopped + self.event_loop.run_forever() + + def __enter__(self): + self.thread.start() + return self + + def __exit__(self, *args): + self.event_loop.call_soon_threadsafe(self.event_loop.stop) + self.thread.join()