| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183 |
- """
- Helper for running LibreOffice (soffice) in environments where AF_UNIX
- sockets may be blocked (e.g., sandboxed VMs). Detects the restriction
- at runtime and applies an LD_PRELOAD shim if needed.
- Usage:
- from office.soffice import run_soffice, get_soffice_env
- # Option 1 – run soffice directly
- result = run_soffice(["--headless", "--convert-to", "pdf", "input.docx"])
- # Option 2 – get env dict for your own subprocess calls
- env = get_soffice_env()
- subprocess.run(["soffice", ...], env=env)
- """
- import os
- import socket
- import subprocess
- import tempfile
- from pathlib import Path
- def get_soffice_env() -> dict:
- env = os.environ.copy()
- env["SAL_USE_VCLPLUGIN"] = "svp"
- if _needs_shim():
- shim = _ensure_shim()
- env["LD_PRELOAD"] = str(shim)
- return env
- def run_soffice(args: list[str], **kwargs) -> subprocess.CompletedProcess:
- env = get_soffice_env()
- return subprocess.run(["soffice"] + args, env=env, **kwargs)
- _SHIM_SO = Path(tempfile.gettempdir()) / "lo_socket_shim.so"
- def _needs_shim() -> bool:
- try:
- s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
- s.close()
- return False
- except OSError:
- return True
- def _ensure_shim() -> Path:
- if _SHIM_SO.exists():
- return _SHIM_SO
- src = Path(tempfile.gettempdir()) / "lo_socket_shim.c"
- src.write_text(_SHIM_SOURCE)
- subprocess.run(
- ["gcc", "-shared", "-fPIC", "-o", str(_SHIM_SO), str(src), "-ldl"],
- check=True,
- capture_output=True,
- )
- src.unlink()
- return _SHIM_SO
- _SHIM_SOURCE = r"""
- #define _GNU_SOURCE
- #include <dlfcn.h>
- #include <errno.h>
- #include <signal.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <sys/socket.h>
- #include <unistd.h>
- static int (*real_socket)(int, int, int);
- static int (*real_socketpair)(int, int, int, int[2]);
- static int (*real_listen)(int, int);
- static int (*real_accept)(int, struct sockaddr *, socklen_t *);
- static int (*real_close)(int);
- static int (*real_read)(int, void *, size_t);
- /* Per-FD bookkeeping (FDs >= 1024 are passed through unshimmed). */
- static int is_shimmed[1024];
- static int peer_of[1024];
- static int wake_r[1024]; /* accept() blocks reading this */
- static int wake_w[1024]; /* close() writes to this */
- static int listener_fd = -1; /* FD that received listen() */
- __attribute__((constructor))
- static void init(void) {
- real_socket = dlsym(RTLD_NEXT, "socket");
- real_socketpair = dlsym(RTLD_NEXT, "socketpair");
- real_listen = dlsym(RTLD_NEXT, "listen");
- real_accept = dlsym(RTLD_NEXT, "accept");
- real_close = dlsym(RTLD_NEXT, "close");
- real_read = dlsym(RTLD_NEXT, "read");
- for (int i = 0; i < 1024; i++) {
- peer_of[i] = -1;
- wake_r[i] = -1;
- wake_w[i] = -1;
- }
- }
- /* ---- socket ---------------------------------------------------------- */
- int socket(int domain, int type, int protocol) {
- if (domain == AF_UNIX) {
- int fd = real_socket(domain, type, protocol);
- if (fd >= 0) return fd;
- /* socket(AF_UNIX) blocked – fall back to socketpair(). */
- int sv[2];
- if (real_socketpair(domain, type, protocol, sv) == 0) {
- if (sv[0] >= 0 && sv[0] < 1024) {
- is_shimmed[sv[0]] = 1;
- peer_of[sv[0]] = sv[1];
- int wp[2];
- if (pipe(wp) == 0) {
- wake_r[sv[0]] = wp[0];
- wake_w[sv[0]] = wp[1];
- }
- }
- return sv[0];
- }
- errno = EPERM;
- return -1;
- }
- return real_socket(domain, type, protocol);
- }
- /* ---- listen ---------------------------------------------------------- */
- int listen(int sockfd, int backlog) {
- if (sockfd >= 0 && sockfd < 1024 && is_shimmed[sockfd]) {
- listener_fd = sockfd;
- return 0;
- }
- return real_listen(sockfd, backlog);
- }
- /* ---- accept ---------------------------------------------------------- */
- int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) {
- if (sockfd >= 0 && sockfd < 1024 && is_shimmed[sockfd]) {
- /* Block until close() writes to the wake pipe. */
- if (wake_r[sockfd] >= 0) {
- char buf;
- real_read(wake_r[sockfd], &buf, 1);
- }
- errno = ECONNABORTED;
- return -1;
- }
- return real_accept(sockfd, addr, addrlen);
- }
- /* ---- close ----------------------------------------------------------- */
- int close(int fd) {
- if (fd >= 0 && fd < 1024 && is_shimmed[fd]) {
- int was_listener = (fd == listener_fd);
- is_shimmed[fd] = 0;
- if (wake_w[fd] >= 0) { /* unblock accept() */
- char c = 0;
- write(wake_w[fd], &c, 1);
- real_close(wake_w[fd]);
- wake_w[fd] = -1;
- }
- if (wake_r[fd] >= 0) { real_close(wake_r[fd]); wake_r[fd] = -1; }
- if (peer_of[fd] >= 0) { real_close(peer_of[fd]); peer_of[fd] = -1; }
- if (was_listener)
- _exit(0); /* conversion done – exit */
- }
- return real_close(fd);
- }
- """
- if __name__ == "__main__":
- import sys
- result = run_soffice(sys.argv[1:])
- sys.exit(result.returncode)
|