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