/
tasksandbox.pas
137 lines (116 loc) · 4.55 KB
/
tasksandbox.pas
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
{ --------------------------------------------------------------------------
godaemon
Task sandbox unit
- Deals with the task running in the sandbox
Copyright (c) Michael Nixon 2015.
Please see the LICENSE file for licensing information.
-------------------------------------------------------------------------- }
{ --------------------------------------------------------------------------
-------------------------------------------------------------------------- }
unit tasksandbox;
interface
uses baseunix, unix, unixutil, sockets, sysutils, classes, logger, lsignal;
{ Non-class functions and procedures }
procedure RunTaskSandbox;
{ --------------------------------------------------------------------------
-------------------------------------------------------------------------- }
implementation
uses btime, settings, strutils, process, sandbox, ipcpipe;
{ --------------------------------------------------------------------------
This is the procedure that is called inside the child task sandbox once
the sandbox has been created.
Our job is to run the task and stream the output back to the parent
godaemon process using a pipe that has been setup for us. We must never
return to the caller.
If the task fails, we halt (and the parent deals with destroying the
sandbox and creating a new one).
We can communicate with the parent daemon using the sandboxControl pipe.
Any exceptions that occur here are sent to the controlling daemon.
-------------------------------------------------------------------------- }
procedure RunTaskSandbox;
const
OUTPUT_BUFFER_SIZE = 1024;
CHECK_INTERVAL_MS = 250;
funcname = 'RunTaskSandbox(): ';
SIGNAL_TERM = 'TERM';
SIGNAL_HUP = 'HUP';
SIGNAL_FORCE = 'FORCE';
var
task: tprocess;
outputbuffer: array[0..OUTPUT_BUFFER_SIZE - 1] of char;
bytesAvailable, bytesRead: longint;
i: longint;
sandboxControl: tipcPipeEndpoint;
sandboxMessage: ansistring;
begin
sandboxControl := _pipes.controlPipe.GetChildEndpoint;
{ Change to the directory the process lives in as some programs will not be
able to find their configuration files/etc otherwise. This wont affect the
caller because we will have forked by now (unless we are running in the
foreground, but we don't care about that) }
chdir(_settings.daemonPath);
task := tprocess.Create(nil);
task.Options := [poUsePipes, poStderrToOutPut];
task.Executable := _settings.daemonFilename;
task.InheritHandles := false;
for i := 0 to _settings.params.count - 1 do begin
task.Parameters.Add(_settings.params.strings[i]);
end;
for i := 0 to _settings.environment.count - 1 do begin
task.Environment.Add(_settings.environment.strings[i]);
end;
{ Pass a special environment variable to the task - this is used to stop
godaemon from running itself }
task.Environment.Add('GODAEMON=TASK');
{ Start the task }
try
task.Execute;
except
on e: exception do begin
sandboxControl.SendString('tprocess exception: ' + e.message);
halt;
end;
end;
{ Wait on the task - keep waiting as long as the task runs, or there is
output to be read }
while task.Running or (task.Output.NumBytesAvailable > 0) do begin
{ Check the control pipe for control requests }
if sandboxControl.Pump then begin
if sandboxControl.GetString(sandboxMessage) then begin
if task.Running then begin
if sandboxMessage = SIGNAL_TERM then begin
FPKill(task.ProcessID, SIGTERM);
end;
if sandboxMessage = SIGNAL_HUP then begin
FPKill(task.ProcessID, SIGHUP);
end;
if sandboxMessage = SIGNAL_FORCE then begin
FPKill(task.ProcessID, SIGKILL);
end;
end;
end;
end;
{ If the task has written anything to stdout/stderr, consume it }
bytesAvailable := task.Output.NumBytesAvailable;
if bytesAvailable > 0 then begin
if bytesAvailable > OUTPUT_BUFFER_SIZE then begin
bytesAvailable := OUTPUT_BUFFER_SIZE;
end;
bytesRead := task.Output.Read(outputbuffer[0], bytesAvailable);
if _settings.daemonCaptureOutput then begin
{ Send output to parent }
_pipes.sandboxWritePipe.write(outputbuffer[0], bytesRead);
end;
{ We don't sleep here because we want to consume data as quickly as
possible if there is still data to consume }
end else begin
{ Throttle our checks }
sleep(CHECK_INTERVAL_MS);
end;
end;
{ The process MUST stop here as we are the child sandbox }
ExitCode := task.ExitStatus;
task.Free;
halt;
end;
end.