diff options
author | James Ross-Gowan <rossymiles@gmail.com> | 2014-11-22 17:21:33 +1100 |
---|---|---|
committer | James Ross-Gowan <rossymiles@gmail.com> | 2014-11-22 18:15:13 +1100 |
commit | ef0d1cddb6d1719b60933fd1dc00f938c095c00c (patch) | |
tree | c6ce0882254c12c4bc21e4442ffed382e6ea94b5 /player/lua.c | |
parent | b2d048440472c8b22abd1945e9ca2cfdfd27f2d0 (diff) | |
download | mpv-ef0d1cddb6d1719b60933fd1dc00f938c095c00c.tar.bz2 mpv-ef0d1cddb6d1719b60933fd1dc00f938c095c00c.tar.xz |
lua: subprocess: move to osdep/subprocess-{win,posix}.c
The subprocess code was already split into fairly general functions,
separate from the Lua code. It's getting pretty big though, especially
the Windows-specific parts, so move it into its own files.
Diffstat (limited to 'player/lua.c')
-rw-r--r-- | player/lua.c | 481 |
1 files changed, 3 insertions, 478 deletions
diff --git a/player/lua.c b/player/lua.c index ba60826778..cdcd2a2443 100644 --- a/player/lua.c +++ b/player/lua.c @@ -15,11 +15,6 @@ * with mpv. If not, see <http://www.gnu.org/licenses/>. */ -#ifdef __MINGW32__ -#define _WIN32_WINNT 0x0600 -#include <windows.h> -#endif - #include <assert.h> #include <string.h> #include <strings.h> @@ -47,6 +42,7 @@ #include "options/path.h" #include "misc/bstr.h" #include "misc/json.h" +#include "osdep/subprocess.h" #include "osdep/timer.h" #include "osdep/threads.h" #include "stream/stream.h" @@ -1177,477 +1173,6 @@ static int script_join_path(lua_State *L) return 1; } -typedef void (*read_cb)(void *ctx, char *data, size_t size); - -#ifdef __MINGW32__ -static void write_arg(bstr *cmdline, char *arg) -{ - // If the string doesn't have characters that need to be escaped, it's best - // to leave it alone for the sake of Windows programs that don't process - // quoted args correctly. - if (!strpbrk(arg, " \t\"")) { - bstr_xappend(NULL, cmdline, bstr0(arg)); - return; - } - - // If there are characters that need to be escaped, write a quoted string - bstr_xappend(NULL, cmdline, bstr0("\"")); - - // Escape the argument. To match the behavior of CommandLineToArgvW, - // backslashes are only escaped if they appear before a quote or the end of - // the string. - int num_slashes = 0; - for (int pos = 0; arg[pos]; pos++) { - switch (arg[pos]) { - case '\\': - // Count backslashes that appear in a row - num_slashes++; - break; - case '"': - bstr_xappend(NULL, cmdline, (struct bstr){arg, pos}); - - // Double preceding slashes - for (int i = 0; i < num_slashes; i++) - bstr_xappend(NULL, cmdline, bstr0("\\")); - - // Escape the following quote - bstr_xappend(NULL, cmdline, bstr0("\\")); - - arg += pos; - pos = 0; - num_slashes = 0; - break; - default: - num_slashes = 0; - } - } - - // Write the rest of the argument - bstr_xappend(NULL, cmdline, bstr0(arg)); - - // Double slashes that appear at the end of the string - for (int i = 0; i < num_slashes; i++) - bstr_xappend(NULL, cmdline, bstr0("\\")); - - bstr_xappend(NULL, cmdline, bstr0("\"")); -} - -// Convert an array of arguments to a properly escaped command-line string -static wchar_t *write_cmdline(void *ctx, char **argv) -{ - bstr cmdline = {0}; - - for (int i = 0; argv[i]; i++) { - write_arg(&cmdline, argv[i]); - if (argv[i + 1]) - bstr_xappend(NULL, &cmdline, bstr0(" ")); - } - - wchar_t *wcmdline = mp_from_utf8(ctx, cmdline.start); - talloc_free(cmdline.start); - return wcmdline; -} - -static int create_overlapped_pipe(HANDLE *read, HANDLE *write) -{ - static atomic_ulong counter = ATOMIC_VAR_INIT(0); - - // Generate pipe name - unsigned long id = atomic_fetch_add(&counter, 1); - unsigned pid = GetCurrentProcessId(); - wchar_t buf[36]; - swprintf(buf, sizeof(buf), L"\\\\?\\pipe\\mpv-anon-%08x-%08lx", pid, id); - - // The function for creating anonymous pipes (CreatePipe) can't create - // overlapped pipes, so instead, use a named pipe with a unique name - *read = CreateNamedPipeW(buf, PIPE_ACCESS_INBOUND | - FILE_FLAG_FIRST_PIPE_INSTANCE | FILE_FLAG_OVERLAPPED, - PIPE_TYPE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS, - 1, 0, 4096, 0, NULL); - if (!*read) - goto error; - - // Open the write end of the pipe as a synchronous handle - *write = CreateFileW(buf, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, NULL); - if (*write == INVALID_HANDLE_VALUE) - goto error; - - return 0; -error: - *read = *write = INVALID_HANDLE_VALUE; - return -1; -} - -static void delete_handle_list(void *p) -{ - LPPROC_THREAD_ATTRIBUTE_LIST list = p; - VOID (WINAPI *pDeleteProcThreadAttributeList)(LPPROC_THREAD_ATTRIBUTE_LIST); - - HMODULE kernel32 = GetModuleHandleW(L"kernel32.dll"); - pDeleteProcThreadAttributeList = - (VOID (WINAPI*)(LPPROC_THREAD_ATTRIBUTE_LIST)) - GetProcAddress(kernel32, "DeleteProcThreadAttributeList"); - - if (pDeleteProcThreadAttributeList) - pDeleteProcThreadAttributeList(list); -} - -// Create a PROC_THREAD_ATTRIBUTE_LIST that specifies exactly which handles are -// inherited by the subprocess -static LPPROC_THREAD_ATTRIBUTE_LIST create_handle_list(void *ctx, - HANDLE *handles, int num) -{ - WINBOOL (WINAPI *pInitializeProcThreadAttributeList)( - LPPROC_THREAD_ATTRIBUTE_LIST, DWORD, DWORD, PSIZE_T); - WINBOOL (WINAPI *pUpdateProcThreadAttribute)(LPPROC_THREAD_ATTRIBUTE_LIST, - DWORD, DWORD_PTR, PVOID, SIZE_T, PVOID, PSIZE_T); - - // Load Windows Vista functions, if available - HMODULE kernel32 = GetModuleHandleW(L"kernel32.dll"); - pInitializeProcThreadAttributeList = - (WINBOOL (WINAPI*)(LPPROC_THREAD_ATTRIBUTE_LIST, DWORD, DWORD, PSIZE_T)) - GetProcAddress(kernel32, "InitializeProcThreadAttributeList"); - pUpdateProcThreadAttribute = - (WINBOOL (WINAPI*)(LPPROC_THREAD_ATTRIBUTE_LIST, DWORD, DWORD_PTR, - PVOID, SIZE_T, PVOID, PSIZE_T)) - GetProcAddress(kernel32, "UpdateProcThreadAttribute"); - if (!pInitializeProcThreadAttributeList || !pUpdateProcThreadAttribute) - return NULL; - - // Get required attribute list size - SIZE_T size = 0; - if (!pInitializeProcThreadAttributeList(NULL, 1, 0, &size)) { - if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) - return NULL; - } - - // Allocate attribute list - LPPROC_THREAD_ATTRIBUTE_LIST list = talloc_size(ctx, size); - if (!pInitializeProcThreadAttributeList(list, 1, 0, &size)) - goto error; - talloc_set_destructor(list, delete_handle_list); - - if (!pUpdateProcThreadAttribute(list, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, - handles, num * sizeof(HANDLE), NULL, NULL)) - goto error; - - return list; -error: - talloc_free(list); - return NULL; -} - -// Helper method similar to sparse_poll, skips NULL handles -static int sparse_wait(HANDLE *handles, unsigned num_handles) -{ - unsigned w_num_handles = 0; - HANDLE w_handles[num_handles]; - int map[num_handles]; - - for (unsigned i = 0; i < num_handles; i++) { - if (!handles[i]) - continue; - - w_handles[w_num_handles] = handles[i]; - map[w_num_handles] = i; - w_num_handles++; - } - - if (w_num_handles == 0) - return -1; - DWORD i = WaitForMultipleObjects(w_num_handles, w_handles, FALSE, INFINITE); - i -= WAIT_OBJECT_0; - - if (i >= w_num_handles) - return -1; - return map[i]; -} - -// Wrapper for ReadFile that treats ERROR_IO_PENDING as success -static int async_read(HANDLE file, void *buf, unsigned size, OVERLAPPED* ol) -{ - if (!ReadFile(file, buf, size, NULL, ol)) - return (GetLastError() == ERROR_IO_PENDING) ? 0 : -1; - return 0; -} - -static int subprocess(char **args, struct mp_cancel *cancel, void *ctx, - read_cb on_stdout, read_cb on_stderr, char **error) -{ - wchar_t *tmp = talloc_new(NULL); - int status = -1; - struct { - HANDLE read; - HANDLE write; - OVERLAPPED ol; - char buf[4096]; - read_cb read_cb; - } pipes[2] = { - { .read_cb = on_stdout }, - { .read_cb = on_stderr }, - }; - - // If the function exits before CreateProcess, there was an init error - *error = "init"; - - for (int i = 0; i < 2; i++) { - pipes[i].ol.hEvent = CreateEventW(NULL, TRUE, FALSE, NULL); - if (!pipes[i].ol.hEvent) - goto done; - if (create_overlapped_pipe(&pipes[i].read, &pipes[i].write)) - goto done; - if (!SetHandleInformation(pipes[i].write, HANDLE_FLAG_INHERIT, - HANDLE_FLAG_INHERIT)) - goto done; - } - - // Convert the args array to a UTF-16 Windows command-line string - wchar_t *cmdline = write_cmdline(tmp, args); - - DWORD flags = CREATE_UNICODE_ENVIRONMENT; - PROCESS_INFORMATION pi = {0}; - STARTUPINFOEXW si = { - .StartupInfo = { - .cb = sizeof(si), - .dwFlags = STARTF_USESTDHANDLES, - .hStdInput = NULL, - .hStdOutput = pipes[0].write, - .hStdError = pipes[1].write, - }, - - // Specify which handles are inherited by the subprocess. If this isn't - // specified, the subprocess inherits all inheritable handles, which - // could include handles created by other threads. See: - // http://blogs.msdn.com/b/oldnewthing/archive/2011/12/16/10248328.aspx - .lpAttributeList = create_handle_list(tmp, - (HANDLE[]){ pipes[0].write, pipes[1].write }, 2), - }; - - // PROC_THREAD_ATTRIBUTE_LISTs are only supported in Vista and up. If not - // supported, create_handle_list will return NULL. - if (si.lpAttributeList) - flags |= EXTENDED_STARTUPINFO_PRESENT; - - // If we have a console, the subprocess will automatically attach to it so - // it can receive Ctrl+C events. If we don't have a console, prevent the - // subprocess from creating its own console window by specifying - // CREATE_NO_WINDOW. GetConsoleCP() can be used to reliably determine if we - // have a console or not (Cygwin uses it too.) - if (!GetConsoleCP()) - flags |= CREATE_NO_WINDOW; - - if (!CreateProcessW(NULL, cmdline, NULL, NULL, TRUE, flags, NULL, NULL, - &si.StartupInfo, &pi)) - goto done; - talloc_free(cmdline); - talloc_free(si.lpAttributeList); - CloseHandle(pi.hThread); - - // Init is finished - *error = NULL; - - // List of handles to watch with sparse_wait - HANDLE handles[] = { pipes[0].ol.hEvent, pipes[1].ol.hEvent, pi.hProcess, - cancel ? mp_cancel_get_event(cancel) : NULL }; - - for (int i = 0; i < 2; i++) { - // Close our copy of the write end of the pipes - CloseHandle(pipes[i].write); - pipes[i].write = NULL; - - // Do the first read operation on each pipe - if (async_read(pipes[i].read, pipes[i].buf, 4096, &pipes[i].ol)) { - CloseHandle(pipes[i].read); - handles[i] = pipes[i].read = NULL; - } - } - - DWORD r; - DWORD exit_code; - while (pipes[0].read || pipes[1].read || pi.hProcess) { - int i = sparse_wait(handles, MP_ARRAY_SIZE(handles)); - switch (i) { - case 0: - case 1: - // Complete the read operation on the pipe - if (!GetOverlappedResult(pipes[i].read, &pipes[i].ol, &r, TRUE)) { - CloseHandle(pipes[i].read); - handles[i] = pipes[i].read = NULL; - break; - } - - pipes[i].read_cb(ctx, pipes[i].buf, r); - - // Begin the next read operation on the pipe - if (async_read(pipes[i].read, pipes[i].buf, 4096, &pipes[i].ol)) { - CloseHandle(pipes[i].read); - handles[i] = pipes[i].read = NULL; - } - - break; - case 2: - GetExitCodeProcess(pi.hProcess, &exit_code); - status = exit_code; - - CloseHandle(pi.hProcess); - handles[i] = pi.hProcess = NULL; - break; - case 3: - if (pi.hProcess) { - TerminateProcess(pi.hProcess, 1); - *error = "killed"; - goto done; - } - break; - default: - goto done; - } - } - -done: - for (int i = 0; i < 2; i++) { - if (pipes[i].read) { - // Cancel any pending I/O (if the process was killed) - CancelIo(pipes[i].read); - GetOverlappedResult(pipes[i].read, &pipes[i].ol, &r, TRUE); - CloseHandle(pipes[i].read); - } - if (pipes[i].write) CloseHandle(pipes[i].write); - if (pipes[i].ol.hEvent) CloseHandle(pipes[i].ol.hEvent); - } - if (pi.hProcess) CloseHandle(pi.hProcess); - talloc_free(tmp); - return status; -} -#endif - -#if HAVE_POSIX_SPAWN -#include <spawn.h> -#include <poll.h> -#include <unistd.h> -#include <sys/types.h> -#include <sys/wait.h> -#include <errno.h> -#include <signal.h> - -// Normally, this must be declared manually, but glibc is retarded. -#ifndef __GLIBC__ -extern char **environ; -#endif - -// A silly helper: automatically skips entries with negative FDs -static int sparse_poll(struct pollfd *fds, int num_fds, int timeout) -{ - struct pollfd p_fds[10]; - int map[10]; - if (num_fds > MP_ARRAY_SIZE(p_fds)) - return -1; - int p_num_fds = 0; - for (int n = 0; n < num_fds; n++) { - map[n] = -1; - if (fds[n].fd < 0) - continue; - map[n] = p_num_fds; - p_fds[p_num_fds++] = fds[n]; - } - int r = poll(p_fds, p_num_fds, timeout); - for (int n = 0; n < num_fds; n++) - fds[n].revents = (map[n] < 0 && r >= 0) ? 0 : p_fds[map[n]].revents; - return r; -} - -static int subprocess(char **args, struct mp_cancel *cancel, void *ctx, - read_cb on_stdout, read_cb on_stderr, char **error) -{ - posix_spawn_file_actions_t fa; - bool fa_destroy = false; - int status = -1; - int p_stdout[2] = {-1, -1}; - int p_stderr[2] = {-1, -1}; - pid_t pid = -1; - - if (mp_make_cloexec_pipe(p_stdout) < 0) - goto done; - if (mp_make_cloexec_pipe(p_stderr) < 0) - goto done; - - if (posix_spawn_file_actions_init(&fa)) - goto done; - fa_destroy = true; - // redirect stdout and stderr - if (posix_spawn_file_actions_adddup2(&fa, p_stdout[1], 1)) - goto done; - if (posix_spawn_file_actions_adddup2(&fa, p_stderr[1], 2)) - goto done; - - if (posix_spawnp(&pid, args[0], &fa, NULL, args, environ)) { - pid = -1; - goto done; - } - - close(p_stdout[1]); - p_stdout[1] = -1; - close(p_stderr[1]); - p_stderr[1] = -1; - - int *read_fds[2] = {&p_stdout[0], &p_stderr[0]}; - read_cb read_cbs[2] = {on_stdout, on_stderr}; - - while (p_stdout[0] >= 0 || p_stderr[0] >= 0) { - struct pollfd fds[] = { - {.events = POLLIN, .fd = *read_fds[0]}, - {.events = POLLIN, .fd = *read_fds[1]}, - {.events = POLLIN, .fd = cancel ? mp_cancel_get_fd(cancel) : -1}, - }; - if (sparse_poll(fds, MP_ARRAY_SIZE(fds), -1) < 0 && errno != EINTR) - break; - for (int n = 0; n < 2; n++) { - if (fds[n].revents) { - char buf[4096]; - ssize_t r = read(*read_fds[n], buf, sizeof(buf)); - if (r < 0 && errno == EINTR) - continue; - if (r > 0 && read_cbs[n]) - read_cbs[n](ctx, buf, r); - if (r <= 0) { - close(*read_fds[n]); - *read_fds[n] = -1; - } - } - } - if (fds[2].revents) { - kill(pid, SIGKILL); - break; - } - } - - // Note: it can happen that a child process closes the pipe, but does not - // terminate yet. In this case, we would have to run waitpid() in - // a separate thread and use pthread_cancel(), or use other weird - // and laborious tricks. So this isn't handled yet. - while (waitpid(pid, &status, 0) < 0 && errno == EINTR) {} - -done: - if (fa_destroy) - posix_spawn_file_actions_destroy(&fa); - close(p_stdout[0]); - close(p_stdout[1]); - close(p_stderr[0]); - close(p_stderr[1]); - - if (WIFEXITED(status) && WEXITSTATUS(status) != 127) { - *error = NULL; - status = WEXITSTATUS(status); - } else { - *error = WEXITSTATUS(status) == 127 ? "init" : "killed"; - status = -1; - } - - return status; -} -#endif - #if HAVE_POSIX_SPAWN || defined(__MINGW32__) struct subprocess_cb_ctx { struct mp_log *log; @@ -1711,8 +1236,8 @@ static int script_subprocess(lua_State *L) }; char *error = NULL; - int status = subprocess(args, cancel, &cb_ctx, subprocess_stdout, - subprocess_stderr, &error); + int status = mp_subprocess(args, cancel, &cb_ctx, subprocess_stdout, + subprocess_stderr, &error); lua_newtable(L); // res if (error) { |