import os
from pathlib import Path
import shutil
import tempfile
from xml.sax.saxutils import escape as xml_escape
SOCKET_PATH_MAX = 107
DBUS_SOCKET_PATH_MAX = 99
DBUS_DIR_SOCKET = 'dbus-' + 'X' * 10
INDICATOR_SOCKET = 'computer-use-linux-indicator.sock'
TEMPDIR_NAME = 'tmp' + 'X' * 8
def socket_tempdir(prefix, sockets, bus_dir=None, dir=None):
return tempfile.TemporaryDirectory(prefix=prefix, dir=socket_base(prefix, sockets, bus_dir, dir))
def socket_base(prefix, sockets, bus_dir=None, dir=None):
name = prefix + 'X' * 8
checks = [(path, SOCKET_PATH_MAX, False) for path in sockets]
if bus_dir is not None:
checks.append((os.path.join(bus_dir, DBUS_DIR_SOCKET), DBUS_SOCKET_PATH_MAX, True))
def overflow(base):
for relative, limit, plain in checks:
path = os.path.join(base, name, relative)
length = len(os.fsencode(path))
if length > limit:
return f'{path} is {length} bytes, over the {limit}-byte limit'
if plain and not dbus_plain(path):
return f'{path} has a byte that a D-Bus address escapes, and zbus does not decode escapes'
return None
base = tempfile.gettempdir() if dir is None else os.fspath(dir)
first = overflow(base)
if first is None:
return base
runtime = os.environ.get('XDG_RUNTIME_DIR', '')
if os.path.isabs(runtime) and os.path.isdir(runtime):
second = overflow(runtime)
if second is None:
return runtime
fallback = f'under XDG_RUNTIME_DIR, {second}'
elif runtime:
fallback = f'XDG_RUNTIME_DIR ({runtime}) is not an absolute directory'
else:
fallback = 'XDG_RUNTIME_DIR is unset'
remedy = 'Set TMPDIR' if dir is None else f'Move {base}'
raise SystemExit(f'No usable Unix socket path: {first}; {fallback}. '
f'{remedy} or XDG_RUNTIME_DIR to a path that fits.')
def dbus_plain(path):
return all(bytes([byte]).isalnum() or byte in b'-_/.\\*' for byte in os.fsencode(path))
def private_bus_config(path, listen_dir, services=()):
if not dbus_plain(listen_dir):
raise SystemExit(f'{listen_dir} has a byte that a D-Bus address escapes; '
'place the fixture with socket_tempdir(bus_dir=...).')
path = Path(path)
servicedir = ''
if services:
target = path.with_name(path.stem + '-services')
target.mkdir(mode=0o700)
data_dirs = os.environ.get('XDG_DATA_DIRS') or '/usr/local/share:/usr/share'
for name in services:
sources = (Path(base, 'dbus-1', 'services', name + '.service')
for base in data_dirs.split(':') if base)
source = next((source for source in sources if source.is_file()), None)
if source is None:
raise SystemExit(f'No D-Bus service file for {name} under XDG_DATA_DIRS ({data_dirs}).')
shutil.copyfile(source, target / source.name)
servicedir = f'<servicedir>{xml_escape(str(target))}</servicedir>'
path.write_text('<busconfig><type>session</type>'
f'<listen>unix:dir={os.fsdecode(listen_dir)}</listen>{servicedir}'
'<policy context="default"><allow own="*"/>'
'<allow send_destination="*"/><allow receive_sender="*"/>'
'</policy></busconfig>')
return path