1 /*
2 * This file is part of mpv.
3 *
4 * mpv is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * mpv is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include <windows.h>
19 #include <string.h>
20
21 #include "osdep/subprocess.h"
22
23 #include "osdep/io.h"
24 #include "osdep/windows_utils.h"
25
26 #include "mpv_talloc.h"
27 #include "common/common.h"
28 #include "stream/stream.h"
29 #include "misc/bstr.h"
30 #include "misc/thread_tools.h"
31
32 // Internal CRT FD flags
33 #define FOPEN (0x01)
34 #define FPIPE (0x08)
35 #define FDEV (0x40)
36
write_arg(bstr * cmdline,char * arg)37 static void write_arg(bstr *cmdline, char *arg)
38 {
39 // Empty args must be represented as an empty quoted string
40 if (!arg[0]) {
41 bstr_xappend(NULL, cmdline, bstr0("\"\""));
42 return;
43 }
44
45 // If the string doesn't have characters that need to be escaped, it's best
46 // to leave it alone for the sake of Windows programs that don't process
47 // quoted args correctly.
48 if (!strpbrk(arg, " \t\"")) {
49 bstr_xappend(NULL, cmdline, bstr0(arg));
50 return;
51 }
52
53 // If there are characters that need to be escaped, write a quoted string
54 bstr_xappend(NULL, cmdline, bstr0("\""));
55
56 // Escape the argument. To match the behavior of CommandLineToArgvW,
57 // backslashes are only escaped if they appear before a quote or the end of
58 // the string.
59 int num_slashes = 0;
60 for (int pos = 0; arg[pos]; pos++) {
61 switch (arg[pos]) {
62 case '\\':
63 // Count consecutive backslashes
64 num_slashes++;
65 break;
66 case '"':
67 // Write the argument up to the point before the quote
68 bstr_xappend(NULL, cmdline, (struct bstr){arg, pos});
69 arg += pos;
70 pos = 0;
71
72 // Double backslashes preceding the quote
73 for (int i = 0; i < num_slashes; i++)
74 bstr_xappend(NULL, cmdline, bstr0("\\"));
75 num_slashes = 0;
76
77 // Escape the quote itself
78 bstr_xappend(NULL, cmdline, bstr0("\\"));
79 break;
80 default:
81 num_slashes = 0;
82 }
83 }
84
85 // Write the rest of the argument
86 bstr_xappend(NULL, cmdline, bstr0(arg));
87
88 // Double backslashes at the end of the argument
89 for (int i = 0; i < num_slashes; i++)
90 bstr_xappend(NULL, cmdline, bstr0("\\"));
91
92 bstr_xappend(NULL, cmdline, bstr0("\""));
93 }
94
95 // Convert an array of arguments to a properly escaped command-line string
write_cmdline(void * ctx,char * argv0,char ** args)96 static wchar_t *write_cmdline(void *ctx, char *argv0, char **args)
97 {
98 // argv0 should always be quoted. Otherwise, arguments may be interpreted as
99 // part of the program name. Also, it can't contain escape sequences.
100 bstr cmdline = {0};
101 bstr_xappend_asprintf(NULL, &cmdline, "\"%s\"", argv0);
102
103 if (args) {
104 for (int i = 0; args[i]; i++) {
105 bstr_xappend(NULL, &cmdline, bstr0(" "));
106 write_arg(&cmdline, args[i]);
107 }
108 }
109
110 wchar_t *wcmdline = mp_from_utf8(ctx, cmdline.start);
111 talloc_free(cmdline.start);
112 return wcmdline;
113 }
114
delete_handle_list(void * p)115 static void delete_handle_list(void *p)
116 {
117 LPPROC_THREAD_ATTRIBUTE_LIST list = p;
118 DeleteProcThreadAttributeList(list);
119 }
120
121 // Create a PROC_THREAD_ATTRIBUTE_LIST that specifies exactly which handles are
122 // inherited by the subprocess
create_handle_list(void * ctx,HANDLE * handles,int num)123 static LPPROC_THREAD_ATTRIBUTE_LIST create_handle_list(void *ctx,
124 HANDLE *handles, int num)
125 {
126 // Get required attribute list size
127 SIZE_T size = 0;
128 if (!InitializeProcThreadAttributeList(NULL, 1, 0, &size)) {
129 if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
130 return NULL;
131 }
132
133 // Allocate attribute list
134 LPPROC_THREAD_ATTRIBUTE_LIST list = talloc_size(ctx, size);
135 if (!InitializeProcThreadAttributeList(list, 1, 0, &size))
136 goto error;
137 talloc_set_destructor(list, delete_handle_list);
138
139 if (!UpdateProcThreadAttribute(list, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
140 handles, num * sizeof(HANDLE), NULL, NULL))
141 goto error;
142
143 return list;
144 error:
145 talloc_free(list);
146 return NULL;
147 }
148
149 // Helper method similar to sparse_poll, skips NULL handles
sparse_wait(HANDLE * handles,unsigned num_handles)150 static int sparse_wait(HANDLE *handles, unsigned num_handles)
151 {
152 unsigned w_num_handles = 0;
153 HANDLE w_handles[MP_SUBPROCESS_MAX_FDS + 2];
154 int map[MP_SUBPROCESS_MAX_FDS + 2];
155 if (num_handles > MP_ARRAY_SIZE(w_handles))
156 return -1;
157
158 for (unsigned i = 0; i < num_handles; i++) {
159 if (!handles[i])
160 continue;
161
162 w_handles[w_num_handles] = handles[i];
163 map[w_num_handles] = i;
164 w_num_handles++;
165 }
166
167 if (w_num_handles == 0)
168 return -1;
169 DWORD i = WaitForMultipleObjects(w_num_handles, w_handles, FALSE, INFINITE);
170 i -= WAIT_OBJECT_0;
171
172 if (i >= w_num_handles)
173 return -1;
174 return map[i];
175 }
176
177 // Wrapper for ReadFile that treats ERROR_IO_PENDING as success
async_read(HANDLE file,void * buf,unsigned size,OVERLAPPED * ol)178 static int async_read(HANDLE file, void *buf, unsigned size, OVERLAPPED* ol)
179 {
180 if (!ReadFile(file, buf, size, NULL, ol))
181 return (GetLastError() == ERROR_IO_PENDING) ? 0 : -1;
182 return 0;
183 }
184
is_valid_handle(HANDLE h)185 static bool is_valid_handle(HANDLE h)
186 {
187 // _get_osfhandle can return -2 "when the file descriptor is not associated
188 // with a stream"
189 return h && h != INVALID_HANDLE_VALUE && (intptr_t)h != -2;
190 }
191
convert_environ(void * ctx,char ** env)192 static wchar_t *convert_environ(void *ctx, char **env)
193 {
194 // Environment size in wchar_ts, including the trailing NUL
195 size_t env_size = 1;
196
197 for (int i = 0; env[i]; i++) {
198 int count = MultiByteToWideChar(CP_UTF8, 0, env[i], -1, NULL, 0);
199 if (count <= 0)
200 abort();
201 env_size += count;
202 }
203
204 wchar_t *ret = talloc_array(ctx, wchar_t, env_size);
205 size_t pos = 0;
206
207 for (int i = 0; env[i]; i++) {
208 int count = MultiByteToWideChar(CP_UTF8, 0, env[i], -1,
209 ret + pos, env_size - pos);
210 if (count <= 0)
211 abort();
212 pos += count;
213 }
214
215 return ret;
216 }
217
mp_subprocess2(struct mp_subprocess_opts * opts,struct mp_subprocess_result * res)218 void mp_subprocess2(struct mp_subprocess_opts *opts,
219 struct mp_subprocess_result *res)
220 {
221 wchar_t *tmp = talloc_new(NULL);
222 DWORD r;
223
224 HANDLE share_hndls[MP_SUBPROCESS_MAX_FDS] = {0};
225 int share_hndl_count = 0;
226 HANDLE wait_hndls[MP_SUBPROCESS_MAX_FDS + 2] = {0};
227 int wait_hndl_count = 0;
228
229 struct {
230 HANDLE handle;
231 bool handle_close;
232 char crt_flags;
233
234 HANDLE read;
235 OVERLAPPED read_ol;
236 char *read_buf;
237 } fd_data[MP_SUBPROCESS_MAX_FDS] = {0};
238
239 // The maximum target FD is limited because FDs have to fit in two sparse
240 // arrays in STARTUPINFO.lpReserved2, which has a maximum size of 65535
241 // bytes. The first four bytes are the handle count, followed by one byte
242 // per handle for flags, and an intptr_t per handle for the HANDLE itself.
243 static const int crt_fd_max = (65535 - sizeof(int)) / (1 + sizeof(intptr_t));
244 int crt_fd_count = 0;
245
246 // If the function exits before CreateProcess, there was an init error
247 *res = (struct mp_subprocess_result){ .error = MP_SUBPROCESS_EINIT };
248
249 STARTUPINFOEXW si = {
250 .StartupInfo = {
251 .cb = sizeof si,
252 .dwFlags = STARTF_USESTDHANDLES | STARTF_FORCEOFFFEEDBACK,
253 },
254 };
255
256 for (int n = 0; n < opts->num_fds; n++) {
257 if (opts->fds[n].fd >= crt_fd_max) {
258 // Target FD is too big to fit in the CRT FD array
259 res->error = MP_SUBPROCESS_EUNSUPPORTED;
260 goto done;
261 }
262
263 if (opts->fds[n].fd >= crt_fd_count)
264 crt_fd_count = opts->fds[n].fd + 1;
265
266 if (opts->fds[n].src_fd >= 0) {
267 HANDLE src_handle = (HANDLE)_get_osfhandle(opts->fds[n].src_fd);
268
269 // Invalid handles are just ignored. This is because sometimes the
270 // standard handles are invalid in Windows, like in GUI processes.
271 // In this case mp_subprocess2 callers should still be able to
272 // blindly forward the standard FDs.
273 if (!is_valid_handle(src_handle))
274 continue;
275
276 DWORD type = GetFileType(src_handle);
277 bool is_console_handle = false;
278 switch (type & 0xff) {
279 case FILE_TYPE_DISK:
280 fd_data[n].crt_flags = FOPEN;
281 break;
282 case FILE_TYPE_CHAR:
283 fd_data[n].crt_flags = FOPEN | FDEV;
284 is_console_handle = GetConsoleMode(src_handle, &(DWORD){0});
285 break;
286 case FILE_TYPE_PIPE:
287 fd_data[n].crt_flags = FOPEN | FPIPE;
288 break;
289 case FILE_TYPE_UNKNOWN:
290 continue;
291 }
292
293 if (is_console_handle) {
294 // Some Windows versions have bugs when duplicating console
295 // handles, or when adding console handles to the CreateProcess
296 // handle list, so just use the handle directly for now. Console
297 // handles treat inheritance weirdly, so this should still work.
298 fd_data[n].handle = src_handle;
299 } else {
300 // Instead of making the source handle inheritable, just
301 // duplicate it to an inheritable handle
302 if (!DuplicateHandle(GetCurrentProcess(), src_handle,
303 GetCurrentProcess(), &fd_data[n].handle, 0,
304 TRUE, DUPLICATE_SAME_ACCESS))
305 goto done;
306 fd_data[n].handle_close = true;
307
308 share_hndls[share_hndl_count++] = fd_data[n].handle;
309 }
310
311 } else if (opts->fds[n].on_read && !opts->detach) {
312 fd_data[n].read_ol.hEvent = CreateEventW(NULL, TRUE, FALSE, NULL);
313 if (!fd_data[n].read_ol.hEvent)
314 goto done;
315
316 struct w32_create_anon_pipe_opts o = {
317 .server_flags = PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED,
318 .client_inheritable = true,
319 };
320 if (!mp_w32_create_anon_pipe(&fd_data[n].read, &fd_data[n].handle, &o))
321 goto done;
322 fd_data[n].handle_close = true;
323
324 wait_hndls[n] = fd_data[n].read_ol.hEvent;
325 wait_hndl_count++;
326
327 fd_data[n].crt_flags = FOPEN | FPIPE;
328 fd_data[n].read_buf = talloc_size(tmp, 4096);
329
330 share_hndls[share_hndl_count++] = fd_data[n].handle;
331
332 } else {
333 DWORD access;
334 if (opts->fds[n].fd == 0) {
335 access = FILE_GENERIC_READ;
336 } else if (opts->fds[n].fd <= 2) {
337 access = FILE_GENERIC_WRITE | FILE_READ_ATTRIBUTES;
338 } else {
339 access = FILE_GENERIC_READ | FILE_GENERIC_WRITE;
340 }
341
342 SECURITY_ATTRIBUTES sa = {
343 .nLength = sizeof sa,
344 .bInheritHandle = TRUE,
345 };
346 fd_data[n].crt_flags = FOPEN | FDEV;
347 fd_data[n].handle = CreateFileW(L"NUL", access,
348 FILE_SHARE_READ | FILE_SHARE_WRITE,
349 &sa, OPEN_EXISTING, 0, NULL);
350 fd_data[n].handle_close = true;
351 }
352
353 switch (opts->fds[n].fd) {
354 case 0:
355 si.StartupInfo.hStdInput = fd_data[n].handle;
356 break;
357 case 1:
358 si.StartupInfo.hStdOutput = fd_data[n].handle;
359 break;
360 case 2:
361 si.StartupInfo.hStdError = fd_data[n].handle;
362 break;
363 }
364 }
365
366 // Convert the UTF-8 environment into a UTF-16 Windows environment block
367 wchar_t *env = NULL;
368 if (opts->env)
369 env = convert_environ(tmp, opts->env);
370
371 // Convert the args array to a UTF-16 Windows command-line string
372 char **args = opts->args && opts->args[0] ? &opts->args[1] : 0;
373 wchar_t *cmdline = write_cmdline(tmp, opts->exe, args);
374
375 // Get pointers to the arrays in lpReserved2. This is an undocumented data
376 // structure used by MSVCRT (and other frameworks and runtimes) to emulate
377 // FD inheritance. The format is unofficially documented here:
378 // https://www.catch22.net/tuts/undocumented-createprocess
379 si.StartupInfo.cbReserved2 = sizeof(int) + crt_fd_count * (1 + sizeof(intptr_t));
380 si.StartupInfo.lpReserved2 = talloc_size(tmp, si.StartupInfo.cbReserved2);
381 char *crt_buf_flags = si.StartupInfo.lpReserved2 + sizeof(int);
382 char *crt_buf_hndls = crt_buf_flags + crt_fd_count;
383
384 memcpy(si.StartupInfo.lpReserved2, &crt_fd_count, sizeof(int));
385
386 // Fill the handle array with INVALID_HANDLE_VALUE, for unassigned handles
387 for (int n = 0; n < crt_fd_count; n++) {
388 HANDLE h = INVALID_HANDLE_VALUE;
389 memcpy(crt_buf_hndls + n * sizeof(intptr_t), &h, sizeof(intptr_t));
390 }
391
392 for (int n = 0; n < opts->num_fds; n++) {
393 crt_buf_flags[opts->fds[n].fd] = fd_data[n].crt_flags;
394 memcpy(crt_buf_hndls + opts->fds[n].fd * sizeof(intptr_t),
395 &fd_data[n].handle, sizeof(intptr_t));
396 }
397
398 DWORD flags = CREATE_UNICODE_ENVIRONMENT | EXTENDED_STARTUPINFO_PRESENT;
399 PROCESS_INFORMATION pi = {0};
400
401 // Specify which handles are inherited by the subprocess. If this isn't
402 // specified, the subprocess inherits all inheritable handles, which could
403 // include handles created by other threads. See:
404 // http://blogs.msdn.com/b/oldnewthing/archive/2011/12/16/10248328.aspx
405 si.lpAttributeList = create_handle_list(tmp, share_hndls, share_hndl_count);
406
407 // If we have a console, the subprocess will automatically attach to it so
408 // it can receive Ctrl+C events. If we don't have a console, prevent the
409 // subprocess from creating its own console window by specifying
410 // CREATE_NO_WINDOW. GetConsoleCP() can be used to reliably determine if we
411 // have a console or not (Cygwin uses it too.)
412 if (!GetConsoleCP())
413 flags |= CREATE_NO_WINDOW;
414
415 if (!CreateProcessW(NULL, cmdline, NULL, NULL, TRUE, flags, env, NULL,
416 &si.StartupInfo, &pi))
417 goto done;
418 talloc_free(cmdline);
419 talloc_free(env);
420 talloc_free(si.StartupInfo.lpReserved2);
421 talloc_free(si.lpAttributeList);
422 CloseHandle(pi.hThread);
423
424 for (int n = 0; n < opts->num_fds; n++) {
425 if (fd_data[n].handle_close && is_valid_handle(fd_data[n].handle))
426 CloseHandle(fd_data[n].handle);
427 fd_data[n].handle = NULL;
428
429 if (fd_data[n].read) {
430 // Do the first read operation on each pipe
431 if (async_read(fd_data[n].read, fd_data[n].read_buf, 4096,
432 &fd_data[n].read_ol))
433 {
434 CloseHandle(fd_data[n].read);
435 wait_hndls[n] = fd_data[n].read = NULL;
436 wait_hndl_count--;
437 }
438 }
439 }
440
441 if (opts->detach) {
442 res->error = MP_SUBPROCESS_OK;
443 goto done;
444 }
445
446 res->error = MP_SUBPROCESS_EGENERIC;
447
448 wait_hndls[MP_SUBPROCESS_MAX_FDS] = pi.hProcess;
449 wait_hndl_count++;
450
451 if (opts->cancel)
452 wait_hndls[MP_SUBPROCESS_MAX_FDS + 1] = mp_cancel_get_event(opts->cancel);
453
454 DWORD exit_code;
455 while (wait_hndl_count) {
456 int n = sparse_wait(wait_hndls, MP_ARRAY_SIZE(wait_hndls));
457
458 if (n >= 0 && n < MP_SUBPROCESS_MAX_FDS) {
459 // Complete the read operation on the pipe
460 if (!GetOverlappedResult(fd_data[n].read, &fd_data[n].read_ol, &r, TRUE)) {
461 CloseHandle(fd_data[n].read);
462 wait_hndls[n] = fd_data[n].read = NULL;
463 wait_hndl_count--;
464 } else {
465 opts->fds[n].on_read(opts->fds[n].on_read_ctx,
466 fd_data[n].read_buf, r);
467
468 // Begin the next read operation on the pipe
469 if (async_read(fd_data[n].read, fd_data[n].read_buf, 4096,
470 &fd_data[n].read_ol))
471 {
472 CloseHandle(fd_data[n].read);
473 wait_hndls[n] = fd_data[n].read = NULL;
474 wait_hndl_count--;
475 }
476 }
477
478 } else if (n == MP_SUBPROCESS_MAX_FDS) { // pi.hProcess
479 GetExitCodeProcess(pi.hProcess, &exit_code);
480 res->exit_status = exit_code;
481
482 CloseHandle(pi.hProcess);
483 wait_hndls[n] = pi.hProcess = NULL;
484 wait_hndl_count--;
485
486 } else if (n == MP_SUBPROCESS_MAX_FDS + 1) { // opts.cancel
487 if (pi.hProcess) {
488 TerminateProcess(pi.hProcess, 1);
489 res->error = MP_SUBPROCESS_EKILLED_BY_US;
490 goto done;
491 }
492 } else {
493 goto done;
494 }
495 }
496
497 res->error = MP_SUBPROCESS_OK;
498
499 done:
500 for (int n = 0; n < opts->num_fds; n++) {
501 if (is_valid_handle(fd_data[n].read)) {
502 // Cancel any pending I/O (if the process was killed)
503 CancelIo(fd_data[n].read);
504 GetOverlappedResult(fd_data[n].read, &fd_data[n].read_ol, &r, TRUE);
505 CloseHandle(fd_data[n].read);
506 }
507 if (fd_data[n].handle_close && is_valid_handle(fd_data[n].handle))
508 CloseHandle(fd_data[n].handle);
509 if (fd_data[n].read_ol.hEvent)
510 CloseHandle(fd_data[n].read_ol.hEvent);
511 }
512 if (pi.hProcess)
513 CloseHandle(pi.hProcess);
514 talloc_free(tmp);
515 }
516