import options import streams when defined(posix): import posix import buffer/buffer import config/config import io/loader import io/posixstream import io/request import io/urlfilter import io/window import ips/serialize import ips/serversocket import types/buffersource import types/cookie import utils/twtstr type ForkCommand* = enum FORK_BUFFER, FORK_LOADER, REMOVE_CHILD, LOAD_CONFIG ForkServer* = ref object process*: Pid istream*: Stream ostream*: Stream ForkServerContext = object istream: Stream ostream: Stream children: seq[(Pid, Pid)] proc newFileLoader*(forkserver: ForkServer, defaultHeaders: HeaderList = nil, filter = newURLFilter(default = true), cookiejar: CookieJar = nil): FileLoader = forkserver.ostream.swrite(FORK_LOADER) var defaultHeaders = defaultHeaders if defaultHeaders == nil: new(defaultHeaders) defaultHeaders[] = DefaultHeaders let config = LoaderConfig( defaultHeaders: defaultHeaders, filter: filter, cookiejar: cookiejar ) forkserver.ostream.swrite(config) forkserver.ostream.flush() forkserver.istream.sread(result) proc loadForkServerConfig*(forkserver: ForkServer, config: Config) = forkserver.ostream.swrite(LOAD_CONFIG) forkserver.ostream.swrite(config.getForkServerConfig()) forkserver.ostream.flush() proc removeChild*(forkserver: Forkserver, pid: Pid) = forkserver.ostream.swrite(REMOVE_CHILD) forkserver.ostream.swrite(pid) forkserver.ostream.flush() proc forkLoader(ctx: var ForkServerContext, config: LoaderConfig): FileLoader = var pipefd: array[2, cint] if pipe(pipefd) == -1: raise newException(Defect, "Failed to open pipe.") let pid = fork() if pid == 0: # child process for i in 0 ..< ctx.children.len: ctx.children[i] = (Pid(0), Pid(0)) ctx.children.setLen(0) zeroMem(addr ctx, sizeof(ctx)) discard close(pipefd[0]) # close read runFileLoader(pipefd[1], config) assert false let readfd = pipefd[0] # get read discard close(pipefd[1]) # close write var readf: File if not open(readf, FileHandle(readfd), fmRead): raise newException(Defect, "Failed to open output handle.") assert readf.readChar() == char(0u8) close(readf) discard close(pipefd[0]) return FileLoader(process: pid) proc forkBuffer(ctx: var ForkServerContext): Pid = var source: BufferSource var config: BufferConfig var attrs: WindowAttributes var mainproc: Pid ctx.istream.sread(source) ctx.istream.sread(config) ctx.istream.sread(attrs) ctx.istream.sread(mainproc) let loader = ctx.forkLoader( LoaderConfig( defaultHeaders: config.headers, filter: config.filter, cookiejar: config.cookiejar, referrerpolicy: config.referrerpolicy ) ) let pid = fork() if pid == 0: for i in 0 ..< ctx.children.len: ctx.children[i] = (Pid(0), Pid(0)) ctx.children.setLen(0) zeroMem(addr ctx, sizeof(ctx)) launchBuffer(config, source, attrs, loader, mainproc) assert false ctx.children.add((pid, loader.process)) return pid proc runForkServer() = var ctx: ForkServerContext ctx.istream = newPosixStream(stdin.getFileHandle()) ctx.ostream = newPosixStream(stdout.getFileHandle()) while true: try: var cmd: ForkCommand ctx.istream.sread(cmd) case cmd of REMOVE_CHILD: var pid: Pid ctx.istream.sread(pid) for i in 0 .. ctx.children.high: if ctx.children[i][0] == pid: ctx.children.del(i) break of FORK_BUFFER: ctx.ostream.swrite(ctx.forkBuffer()) of FORK_LOADER: var config: LoaderConfig ctx.istream.sread(config) let loader = ctx.forkLoader(config) ctx.ostream.swrite(loader) ctx.children.add((loader.process, Pid(-1))) of LOAD_CONFIG: var config: ForkServerConfig ctx.istream.sread(config) width_table = makewidthtable(config.ambiguous_double) SocketDirectory = config.tmpdir ctx.ostream.flush() except EOFError: # EOF break ctx.istream.close() ctx.ostream.close() # Clean up when the main process crashed. for childpair in ctx.children: let a = childpair[0] let b = childpair[1] discard kill(cint(a), cint(SIGTERM)) if b != -1: discard kill(cint(b), cint(SIGTERM)) quit(0) proc newForkServer*(): ForkServer = new(result) var pipefd_in: array[2, cint] var pipefd_out: array[2, cint] if pipe(pipefd_in) == -1: raise newException(Defect, "Failed to open input pipe.") if pipe(pipefd_out) == -1: raise newException(Defect, "Failed to open output pipe.") let pid = fork() if pid == -1: raise newException(Defect, "Failed to fork the fork process.") elif pid == 0: # child process let readfd = pipefd_in[0] discard close(pipefd_in[1]) # close write let writefd = pipefd_out[1] discard close(pipefd_out[0]) # close read discard dup2(readfd, stdin.getFileHandle()) discard dup2(writefd, stdout.getFileHandle()) discard close(pipefd_in[0]) discard close(pipefd_out[1]) runForkServer() assert false else: discard close(pipefd_in[0]) # close read discard close(pipefd_out[1]) # close write var readf, writef: File if not open(writef, pipefd_in[1], fmWrite): raise newException(Defect, "Failed to open output handle") if not open(readf, pipefd_out[0], fmRead): raise newException(Defect, "Failed to open input handle") result.ostream = newFileStream(writef) result.istream = newFileStream(readf)