scrcpy/app/src/sys/win/process.c
Romain Vimont 01d6d8c41f Expose flags for process execution
Let the caller decide if stdout and stderr must be inherited on process
creation, i.e. if stdout and stderr of the child process should be
printed in the scrcpy console.

This allows to get output and errors for specific adb commands depending
on the context.
2021-11-26 09:41:11 +01:00

248 lines
6.8 KiB
C

#include "util/process.h"
#include <processthreadsapi.h>
#include <assert.h>
#include "util/log.h"
#include "util/str.h"
#define CMD_MAX_LEN 8192
static bool
build_cmd(char *cmd, size_t len, const char *const argv[]) {
// Windows command-line parsing is WTF:
// <http://daviddeley.com/autohotkey/parameters/parameters.htm#WINPASS>
// only make it work for this very specific program
// (don't handle escaping nor quotes)
size_t ret = sc_str_join(cmd, argv, ' ', len);
if (ret >= len) {
LOGE("Command too long (%" SC_PRIsizet " chars)", len - 1);
return false;
}
return true;
}
enum sc_process_result
sc_process_execute_p(const char *const argv[], HANDLE *handle, unsigned flags,
HANDLE *pin, HANDLE *pout, HANDLE *perr) {
bool inherit_stdout = !pout && !(flags & SC_PROCESS_NO_STDOUT);
bool inherit_stderr = !perr && !(flags & SC_PROCESS_NO_STDERR);
// Add 1 per non-NULL pointer
unsigned handle_count = !!pin
+ (pout || inherit_stdout)
+ (perr || inherit_stderr);
enum sc_process_result ret = SC_PROCESS_ERROR_GENERIC;
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
HANDLE stdin_read_handle;
HANDLE stdout_write_handle;
HANDLE stderr_write_handle;
if (pin) {
if (!CreatePipe(&stdin_read_handle, pin, &sa, 0)) {
perror("pipe");
return SC_PROCESS_ERROR_GENERIC;
}
if (!SetHandleInformation(*pin, HANDLE_FLAG_INHERIT, 0)) {
LOGE("SetHandleInformation stdin failed");
goto error_close_stdin;
}
}
if (pout) {
if (!CreatePipe(pout, &stdout_write_handle, &sa, 0)) {
perror("pipe");
goto error_close_stdin;
}
if (!SetHandleInformation(*pout, HANDLE_FLAG_INHERIT, 0)) {
LOGE("SetHandleInformation stdout failed");
goto error_close_stdout;
}
}
if (perr) {
if (!CreatePipe(perr, &stderr_write_handle, &sa, 0)) {
perror("pipe");
goto error_close_stdout;
}
if (!SetHandleInformation(*perr, HANDLE_FLAG_INHERIT, 0)) {
LOGE("SetHandleInformation stderr failed");
goto error_close_stderr;
}
}
STARTUPINFOEXW si;
PROCESS_INFORMATION pi;
memset(&si, 0, sizeof(si));
si.StartupInfo.cb = sizeof(si);
HANDLE handles[3];
LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList = NULL;
if (handle_count) {
si.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
unsigned i = 0;
if (pin) {
si.StartupInfo.hStdInput = stdin_read_handle;
handles[i++] = si.StartupInfo.hStdInput;
}
if (pout || inherit_stdout) {
si.StartupInfo.hStdOutput = pout ? stdout_write_handle
: GetStdHandle(STD_OUTPUT_HANDLE);
handles[i++] = si.StartupInfo.hStdOutput;
}
if (perr || inherit_stderr) {
si.StartupInfo.hStdError = perr ? stderr_write_handle
: GetStdHandle(STD_ERROR_HANDLE);
handles[i++] = si.StartupInfo.hStdError;
}
SIZE_T size;
// Call it once to know the required buffer size
BOOL ok =
InitializeProcThreadAttributeList(NULL, 1, 0, &size)
|| GetLastError() == ERROR_INSUFFICIENT_BUFFER;
if (!ok) {
goto error_close_stderr;
}
lpAttributeList = malloc(size);
if (!lpAttributeList) {
LOG_OOM();
goto error_close_stderr;
}
ok = InitializeProcThreadAttributeList(lpAttributeList, 1, 0, &size);
if (!ok) {
free(lpAttributeList);
goto error_close_stderr;
}
ok = UpdateProcThreadAttribute(lpAttributeList, 0,
PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
handles, handle_count * sizeof(HANDLE),
NULL, NULL);
if (!ok) {
goto error_free_attribute_list;
}
si.lpAttributeList = lpAttributeList;
}
char *cmd = malloc(CMD_MAX_LEN);
if (!cmd || !build_cmd(cmd, CMD_MAX_LEN, argv)) {
LOG_OOM();
goto error_free_attribute_list;
}
wchar_t *wide = sc_str_to_wchars(cmd);
free(cmd);
if (!wide) {
LOG_OOM();
goto error_free_attribute_list;
}
BOOL bInheritHandles = handle_count > 0;
// DETACHED_PROCESS to disable stdin, stdout and stderr
DWORD dwCreationFlags = handle_count > 0 ? EXTENDED_STARTUPINFO_PRESENT
: DETACHED_PROCESS;
BOOL ok = CreateProcessW(NULL, wide, NULL, NULL, bInheritHandles,
dwCreationFlags, NULL, NULL, &si.StartupInfo, &pi);
free(wide);
if (!ok) {
if (GetLastError() == ERROR_FILE_NOT_FOUND) {
ret = SC_PROCESS_ERROR_MISSING_BINARY;
}
goto error_free_attribute_list;
}
if (lpAttributeList) {
DeleteProcThreadAttributeList(lpAttributeList);
free(lpAttributeList);
}
// These handles are used by the child process, close them for this process
if (pin) {
CloseHandle(stdin_read_handle);
}
if (pout) {
CloseHandle(stdout_write_handle);
}
if (perr) {
CloseHandle(stderr_write_handle);
}
*handle = pi.hProcess;
return SC_PROCESS_SUCCESS;
error_free_attribute_list:
if (lpAttributeList) {
DeleteProcThreadAttributeList(lpAttributeList);
free(lpAttributeList);
}
error_close_stderr:
if (perr) {
CloseHandle(*perr);
CloseHandle(stderr_write_handle);
}
error_close_stdout:
if (pout) {
CloseHandle(*pout);
CloseHandle(stdout_write_handle);
}
error_close_stdin:
if (pin) {
CloseHandle(*pin);
CloseHandle(stdin_read_handle);
}
return ret;
}
bool
sc_process_terminate(HANDLE handle) {
return TerminateProcess(handle, 1);
}
sc_exit_code
sc_process_wait(HANDLE handle, bool close) {
DWORD code;
if (WaitForSingleObject(handle, INFINITE) != WAIT_OBJECT_0
|| !GetExitCodeProcess(handle, &code)) {
// could not wait or retrieve the exit code
code = SC_EXIT_CODE_NONE;
}
if (close) {
CloseHandle(handle);
}
return code;
}
void
sc_process_close(HANDLE handle) {
bool closed = CloseHandle(handle);
assert(closed);
(void) closed;
}
ssize_t
sc_pipe_read(HANDLE pipe, char *data, size_t len) {
DWORD r;
if (!ReadFile(pipe, data, len, &r, NULL)) {
return -1;
}
return r;
}
void
sc_pipe_close(HANDLE pipe) {
if (!CloseHandle(pipe)) {
LOGW("Cannot close pipe");
}
}