import std/options import std/os import std/posix import std/selectors import std/tables import config/config import html/formdata import io/bufreader import io/bufwriter import io/dynstream import io/posixstream import io/serversocket import io/socketstream import io/stdio import loader/loader import server/buffer import types/urimethodmap import types/url import types/winattrs import utils/proctitle import utils/sandbox import utils/strwidth import chagashi/charset type ForkCommand = enum fcForkBuffer, fcForkLoader, fcRemoveChild, fcLoadConfig ForkServer* = ref object istream: PosixStream ostream: PosixStream estream*: PosixStream ForkServerContext = object istream: PosixStream ostream: PosixStream children: seq[int] loaderPid: int sockDirFd: int sockDir: string proc forkLoader*(forkserver: ForkServer; config: LoaderConfig): int = forkserver.ostream.withPacketWriter w: w.swrite(fcForkLoader) w.swrite(config) var r = forkserver.istream.initPacketReader() var process: int r.sread(process) return process proc loadForkServerConfig*(forkserver: ForkServer; config: Config) = forkserver.ostream.withPacketWriter w: w.swrite(fcLoadConfig) w.swrite(config.external.sockdir) w.swrite(config.display.double_width_ambiguous) proc removeChild*(forkserver: ForkServer; pid: int) = forkserver.ostream.withPacketWriter w: w.swrite(fcRemoveChild) w.swrite(pid) proc forkBuffer*(forkserver: ForkServer; config: BufferConfig; url: URL; attrs: WindowAttributes; ishtml: bool; charsetStack: seq[Charset]): int = forkserver.ostream.withPacketWriter w: w.swrite(fcForkBuffer) w.swrite(config) w.swrite(url) w.swrite(attrs) w.swrite(ishtml) w.swrite(charsetStack) var r = forkserver.istream.initPacketReader() var bufferPid: int r.sread(bufferPid) return bufferPid proc trapSIGINT() = # trap SIGINT, so e.g. an external editor receiving an interrupt in the # same process group can't just kill the process # Note that the main process normally quits on interrupt (thus terminating # all child processes as well). setControlCHook(proc() {.noconv.} = discard) proc forkLoader(ctx: var ForkServerContext; config: LoaderConfig): int = var pipefd: array[2, cint] if pipe(pipefd) == -1: raise newException(Defect, "Failed to open pipe.") stdout.flushFile() stderr.flushFile() let pid = fork() if pid == 0: # child process trapSIGINT() for i in 0 ..< ctx.children.len: ctx.children[i] = 0 ctx.children.setLen(0) zeroMem(addr ctx, sizeof(ctx)) discard close(pipefd[0]) # close read try: setProcessTitle("cha loader") runFileLoader(pipefd[1], config) except CatchableError: let e = getCurrentException() # taken from system/excpt.nim let msg = e.getStackTrace() & "Error: unhandled exception: " & e.msg & " [" & $e.name & "]\n" stderr.write(msg) quit(1) doAssert 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.") let c = readf.readChar() assert c == char(0u8) close(readf) discard close(pipefd[0]) return pid proc forkBuffer(ctx: var ForkServerContext; r: var BufferedReader): int = var config: BufferConfig var url: URL var attrs: WindowAttributes var ishtml: bool var charsetStack: seq[Charset] r.sread(config) r.sread(url) r.sread(attrs) r.sread(ishtml) r.sread(charsetStack) var pipefd: array[2, cint] if pipe(pipefd) == -1: raise newException(Defect, "Failed to open pipe.") stdout.flushFile() stderr.flushFile() let pid = fork() if pid == -1: raise newException(Defect, "Failed to fork process.") if pid == 0: # child process trapSIGINT() for i in 0 ..< ctx.children.len: ctx.children[i] = 0 ctx.children.setLen(0) let loaderPid = ctx.loaderPid let sockDir = ctx.sockDir let sockDirFd = ctx.sockDirFd zeroMem(addr ctx, sizeof(ctx)) discard close(pipefd[0]) # close read closeStdin() closeStdout() # must call before entering the sandbox, or capsicum cries because of Nim # calling sysctl # also lets us deny sysctl call with pledge let selector = newSelector[int]() setBufferProcessTitle(url) let pid = getCurrentProcessId() let ssock = initServerSocket(sockDir, sockDirFd, pid) let ps = newPosixStream(pipefd[1]) ps.write(char(0)) ps.sclose() urandom = newPosixStream("/dev/urandom", O_RDONLY, 0) let pstream = ssock.acceptSocketStream() gssock = ssock gpstream = pstream onSignal SIGTERM: discard sig gpstream.sclose() gssock.close(unlink = false) enterBufferSandbox(sockDir) let loader = FileLoader( process: loaderPid, clientPid: pid, sockDir: sockDir, sockDirFd: sockDirFd ) try: launchBuffer(config, url, attrs, ishtml, charsetStack, loader, ssock, pstream, selector) except CatchableError: let e = getCurrentException() # taken from system/excpt.nim let msg = e.getStackTrace() & "Error: unhandled exception: " & e.msg & " [" & $e.name & "]\n" stderr.write(msg) quit(1) doAssert false discard close(pipefd[1]) # close write let ps = newPosixStream(pipefd[0]) let c = ps.sreadChar() assert c == char(0) ps.sclose() ctx.children.add(pid) return pid proc runForkServer() = setProcessTitle("cha forkserver") var ctx = ForkServerContext( istream: newPosixStream(stdin.getFileHandle()), ostream: newPosixStream(stdout.getFileHandle()), sockDirFd: -1 ) signal(SIGCHLD, SIG_IGN) while true: try: ctx.istream.withPacketReader r: var cmd: ForkCommand r.sread(cmd) case cmd of fcRemoveChild: var pid: int r.sread(pid) let i = ctx.children.find(pid) if i != -1: ctx.children.del(i) of fcForkBuffer: let r = ctx.forkBuffer(r) ctx.ostream.withPacketWriter w: w.swrite(r) of fcForkLoader: assert ctx.loaderPid == 0 var config: LoaderConfig r.sread(config) let pid = ctx.forkLoader(config) ctx.ostream.withPacketWriter w: w.swrite(pid) ctx.loaderPid = pid ctx.children.add(pid) of fcLoadConfig: r.sread(ctx.sockDir) r.sread(isCJKAmbiguous) when defined(freebsd): ctx.sockDirFd = open(cstring(ctx.sockDir), O_DIRECTORY) except EOFError: # EOF break ctx.istream.sclose() ctx.ostream.sclose() # Clean up when the main process crashed. for child in ctx.children: discard kill(cint(child), cint(SIGTERM)) quit(0) proc newForkServer*(): ForkServer = var pipefd_in: array[2, cint] # stdin in forkserver var pipefd_out: array[2, cint] # stdout in forkserver var pipefd_err: array[2, cint] # stderr in forkserver 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.") if pipe(pipefd_err) == -1: raise newException(Defect, "Failed to open error pipe.") stdout.flushFile() stderr.flushFile() let pid = fork() if pid == -1: raise newException(Defect, "Failed to fork the fork process.") elif pid == 0: # child process trapSIGINT() discard close(pipefd_in[1]) # close write discard close(pipefd_out[0]) # close read discard close(pipefd_err[0]) # close read let readfd = pipefd_in[0] let writefd = pipefd_out[1] let errfd = pipefd_err[1] discard dup2(readfd, stdin.getFileHandle()) discard dup2(writefd, stdout.getFileHandle()) discard dup2(errfd, stderr.getFileHandle()) discard close(pipefd_in[0]) discard close(pipefd_out[1]) discard close(pipefd_err[1]) runForkServer() doAssert false else: discard close(pipefd_in[0]) # close read discard close(pipefd_out[1]) # close write discard close(pipefd_err[1]) # close write let estream = newPosixStream(pipefd_err[0]) estream.setBlocking(false) return ForkServer( ostream: newPosixStream(pipefd_in[1]), istream: newPosixStream(pipefd_out[0]), estream: estream )