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

                    
                         
                
                     
                      
                    
                     
 

                       
    

                                                           

                          
                        
                        
                         

                            

                        

                      

                   
 
                                                                     
                                        

                          
                                               
                  
                  
                
 
                                                                    
                                        
                          

                                                   
 
                                                     
                                        

                           
 
                                                                        

                                                                       
                                        


                          


                          
                                               
                    

                    
 






                                                                            
                                                                        


                                                      

                    


                   
                
                                                        


                                         
        
                                   




                                                                            

                              
             
                  




                                                               

                          

                          
            
 
                                                                         

                          


                                

                 


                       


                                                      

                    
                  

                                                         
              

                   
                                                        
                          
                                 

                                 
                                  
                                         



                                                                             
                                               
                                     
                              
                                   
                                                         

                                      
               
                                                         





                                            
                                  
                               

                            


                          
     
        
                                                                    
                                 



                                                                            

                              
             
                  

                                        
                        
                     
             
                       

            
                      
                                   

                                                   

                                                    
   
                          

             











                                        
                                         





                                          
                                         



                               

                                 

                                                                   
                    

           

                      
                                           

                                            


                                   


                                                       



                                                             

                                                            

                    




                                                                  
                
                                             
                                             



                                             

                                                 
                                               

                                
                                
                   
                  


                                              
                                              

                                               
                      
                                            
                                             
                      
     
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
    )