about summary refs log blame commit diff stats
path: root/src/ips/forkserver.nim
blob: 906d60a4d0f5e83c26c402cced17ab85bcd40ff2 (plain) (tree)
1
2
3
4
5
6
7
8
9
              





                    
                    
                
                     
                 
                   

                    
                       
                         
                   
                   


                     
                                                       










                             
                                                                                                                                                              
                                        









                                     


                                  




                                                                    

                                                     
                                

                            
                                                                               









                                                                       
                                    



















                                                               







                                           











                                                                       

                                                      














                                            


                                           

                                                   




                                                             
                         
                    













































                                                                  
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)