]> git.cworth.org Git - vogl/blob - src/voglcore/vogl_applauncher.cpp
Don't attempt to build gltests nor OGLSuperBible directories.
[vogl] / src / voglcore / vogl_applauncher.cpp
1 /**************************************************************************
2  *
3  * Copyright 2013-2014 RAD Game Tools and Valve Software
4  * All Rights Reserved.
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22  * THE SOFTWARE.
23  *
24  **************************************************************************/
25
26 #include <stdio.h>
27 #include <fcntl.h>
28 #include <assert.h>
29 #include <sys/wait.h>
30 #include "vogl_applauncher.h"
31 #include "base/posix/eintr_wrapper.h"
32
33 extern char **__environ;
34
35 namespace vogl
36 {
37
38 app_launcher::app_launcher()
39 {
40     m_status_code = STATUS_NOTLAUNCHED;
41     m_status = 0;
42     m_child_pid = -1;
43
44     m_stdout = NULL;
45     m_stderr = NULL;
46
47     memset(m_stdout_pipe, -1, sizeof(m_stdout_pipe));
48     memset(m_stderr_pipe, -1, sizeof(m_stderr_pipe));
49 }
50
51 app_launcher::~app_launcher()
52 {
53     stop();
54 }
55
56 sigset_t
57 set_signal_mask(const sigset_t& sigmask_new)
58 {
59     sigset_t sigmask_old;
60     pthread_sigmask(SIG_SETMASK, &sigmask_new, &sigmask_old);
61     return sigmask_old;
62 }
63
64 bool
65 app_launcher::run(const char *file, const char *const argv[], const char *const envp[], uint flags)
66 {
67     assert(!is_running());
68
69     stop();
70     m_status_code = STATUS_NOTLAUNCHED;
71     m_status = 0;
72
73     const char *argv_def[2];
74     if (!argv)
75     {
76         argv_def[0] = file;
77         argv_def[1] = NULL;
78         argv = argv_def;
79     }
80
81     if (!envp)
82     {
83         envp = __environ;
84     }
85
86     vogl::vector<char *> new_envp;
87     if (flags & RUN_REMOVE_LD_PRELOAD_ENV)
88     {
89         // Remove LD_PRELOAD. Need to do this to keep glxinfo from preloading us again (for example).
90         static const char ld_preload_str[] = "LD_PRELOAD=";
91         static const size_t ld_preload_len = sizeof(ld_preload_str) - 1;
92
93         for(int i = 0; environ[i]; i++)
94         {
95             if (strncmp(ld_preload_str, environ[i], ld_preload_len))
96                 new_envp.push_back(environ[i]);
97         }
98
99         new_envp.push_back(NULL);
100         envp = new_envp.get_ptr();
101     }
102
103     // [0]: read end, [1]: write end
104     // Use pipe2 with O_CLOEXEC? O_NONBLK?
105     int ret_stdout = pipe(m_stdout_pipe);
106     int ret_stderr = pipe(m_stderr_pipe);
107     if ((ret_stdout < 0) || (ret_stderr < 0))
108         return false;
109
110     sigset_t sigset_full;
111     sigfillset(&sigset_full);
112     const sigset_t sigmask_orig = set_signal_mask(sigset_full);
113
114     // Careful: fork() can return pid of a script...
115     m_child_pid = fork();
116
117     if (m_child_pid != 0)
118     {
119         // Restore sigmask for parent.
120         set_signal_mask(sigmask_orig);
121     }
122
123     if (m_child_pid < 0)
124     {
125         assert(0);
126         m_status_code = STATUS_ERROR;
127         m_status = m_child_pid;
128         return false;
129     }
130     else if (m_child_pid == 0)
131     {
132         // According to chromium LaunchProcess(), readline processes can block forever
133         //  unless we set stdin to /dev/null.
134         int null_fd = HANDLE_EINTR(open("/dev/null", O_RDONLY));
135         if (null_fd < 0)
136             _exit(127);
137
138         int new_stdin = HANDLE_EINTR(dup2(null_fd, STDIN_FILENO));
139         ret_stdout = HANDLE_EINTR(dup2(m_stdout_pipe[1], STDOUT_FILENO));
140         ret_stderr = HANDLE_EINTR(dup2(m_stderr_pipe[1], STDERR_FILENO));
141         if ((new_stdin < 0) || (ret_stdout < 0) || (ret_stderr < 0))
142             _exit(127);
143
144         close(m_stdout_pipe[0]); // close read pipe
145         close(m_stderr_pipe[0]); // close read pipe
146         m_stdout_pipe[0] = -1;
147         m_stderr_pipe[0] = -1;
148
149         // exec file
150         execvpe(file, (char * const *)argv, (char * const *)envp);
151         _exit(127);
152     }
153
154     close(m_stdout_pipe[1]);
155     close(m_stderr_pipe[1]);
156     m_stdout_pipe[1] = -1;
157     m_stderr_pipe[1] = -1;
158
159     m_status_code = STATUS_RUNNING;
160     return true;
161 }
162
163 bool
164 app_launcher::update_status(bool wait)
165 {
166     if (m_status_code != STATUS_RUNNING)
167         return false;
168
169     assert(m_child_pid >= 0);
170
171     int options = WEXITED; // WSTOPPED: wait for children stopped by delivery of signal.
172     if(!wait)
173         options |= WNOHANG;
174
175     siginfo_t signalinfo;
176     int ret = waitid(P_PID, m_child_pid, &signalinfo, options);
177
178     if (ret == -1)
179     {
180         m_status_code = STATUS_ERROR;
181         m_status = ret;
182         assert(0);
183         return false;
184     }
185     else
186     {
187         // If we ever care about signals and whatnot:
188         //  signalinfo.si_signo;    // Signal number.
189         //  signalinfo.si_code;     // Signal code: CLD_EXITED, CLD_DUMPED, CLD_KILLED.
190         //  signalinfo.si_errno;    // Error number associated with signal.
191         //  signalinfo.si_status;   // Exit value.
192         switch(signalinfo.si_code)
193         {
194         default:
195             assert(0);
196         case CLD_EXITED:
197             m_status_code = STATUS_EXITED;
198             m_status = signalinfo.si_status;
199             break;
200         case CLD_KILLED:
201             m_status_code = STATUS_KILLED;
202             m_status = 0;
203             break;
204         case CLD_DUMPED:
205             m_status_code = STATUS_DUMPED;
206             m_status = 0;
207             break;
208         }
209     }
210
211     return true;
212 }
213
214 void
215 app_launcher::get_status(STATUS_CODE *code, int *status, bool wait)
216 {
217     update_status(wait);
218
219     *code = m_status_code;
220     *status = m_status;
221 }
222
223 bool
224 app_launcher::is_running()
225 {
226     update_status(false);
227
228     return (m_status_code == STATUS_RUNNING);
229 }
230
231 bool
232 app_launcher::stop()
233 {
234     update_status(true);
235     if (m_status_code == STATUS_RUNNING)
236     {
237         kill(m_child_pid, SIGTERM);
238
239         update_status(true);
240         if (m_status_code == STATUS_RUNNING)
241         {
242             kill(m_child_pid, SIGKILL);
243             update_status(true);
244         }
245     }
246     assert(m_status_code != STATUS_RUNNING);
247
248     if (m_stdout)
249     {
250         fclose(m_stdout);
251         m_stdout = NULL;
252     }
253     if (m_stderr)
254     {
255         fclose(m_stderr);
256         m_stderr = NULL;
257     }
258
259     for (int i = 0; i < 2; i++)
260     {
261         if (m_stdout_pipe[i] >= 0)
262         {
263             close(m_stdout_pipe[i]);
264             m_stdout_pipe[i] = -1;
265         }
266         if (m_stderr_pipe[i] >= 0)
267         {
268             close(m_stderr_pipe[i]);
269             m_stderr_pipe[i] = -1;
270         }
271     }
272
273     return true;
274 }
275
276 bool
277 app_launcher::get_output(int pipe, vogl::dynamic_string &output, size_t max_output)
278 {
279     if (pipe != -1)
280     {
281         if (max_output <= 0)
282             return true;
283
284         for(;;)
285         {
286             char buf[4096];
287             size_t nbytes = max_output;
288             if (nbytes >= sizeof(buf))
289                 nbytes = sizeof(buf);
290
291             // Try to read in nbytes. read returns 0:end of file, -1:error.
292             ssize_t length = HANDLE_EINTR(read(pipe, buf, nbytes));
293             if (length < 0)
294                 return false;
295
296             max_output -= length;
297             output.append(buf, (uint)length);
298
299             if ((length != (ssize_t)nbytes) || (max_output <= 0))
300                 return true;
301         }
302     }
303
304     return false;
305 }
306
307 bool
308 app_launcher::get_stdout(vogl::dynamic_string &output, size_t max_output)
309 {
310     return get_output(m_stdout_pipe[0], output, max_output);
311 }
312
313 bool
314 app_launcher::get_stderr(vogl::dynamic_string &output, size_t max_output)
315 {
316     return get_output(m_stdout_pipe[1], output, max_output);
317 }
318
319 bool
320 app_launcher::get_line(char **line_stdout, size_t *line_stdout_size, char **line_stderr, size_t *line_stderr_size)
321 {
322     if (!m_stdout)
323     {
324         m_stdout = fdopen(m_stdout_pipe[0], "r");
325         setvbuf(m_stdout, NULL, _IONBF, 0);
326     }
327     if (!m_stderr)
328     {
329         m_stderr = fdopen(m_stderr_pipe[0], "r");
330         setvbuf(m_stderr, NULL, _IONBF, 0);
331     }
332     if (!m_stdout || !m_stderr)
333     {
334         assert(0);
335         return false;
336     }
337
338     *line_stdout_size = 0;
339     *line_stderr_size = 0;
340
341     fd_set set;
342     FD_ZERO(&set);
343     FD_SET(m_stdout_pipe[0], &set);
344     FD_SET(m_stderr_pipe[0], &set);
345
346     bool got_line = false;
347     struct timeval timeout;
348     timeout.tv_sec = 0;
349     timeout.tv_usec = 0;
350
351     int ret = select(FD_SETSIZE, &set, NULL, NULL, &timeout);
352     if (ret < 0)
353     {
354         assert(0);
355         return false;
356     }
357     if (!feof(m_stdout) && FD_ISSET(m_stdout_pipe[0], &set))
358     {
359         getline(line_stdout, line_stdout_size, m_stdout);
360         got_line = true;
361     }
362     if (!feof(m_stderr) && FD_ISSET(m_stderr_pipe[0], &set))
363     {
364         getline(line_stderr, line_stderr_size, m_stderr);
365         got_line = true;
366     }
367
368     return got_line;
369 }
370
371 } // namespace vogl