1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
|
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()
when defined(linux):
# no unlink access on Linux
gssock.close(unlink = false)
else:
gssock.close()
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
)
|