watchman/winbuild/posix_spawn.cpp (403 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #include <folly/String.h> #include <folly/Synchronized.h> #include "watchman/Logging.h" #include "watchman/watchman_stream.h" using namespace watchman; // Maps pid => process handle // This is so that we can wait/poll/query the termination status static folly::Synchronized<std::unordered_map<DWORD, HANDLE>> child_procs; pid_t waitpid(pid_t pid, int* status, int options) { HANDLE h; { auto rlock = child_procs.rlock(); auto it = rlock->find(pid); if (it == rlock->end()) { errno = ESRCH; return -1; } h = it->second; } auto res = WaitForSingleObject(h, options == WNOHANG ? 0 : INFINITE); switch (res) { case WAIT_OBJECT_0: { DWORD exitCode = 0; GetExitCodeProcess(h, &exitCode); *status = int(exitCode); child_procs.wlock()->erase(pid); return pid; } case WAIT_ABANDONED_0: *status = 0; child_procs.wlock()->erase(pid); return pid; case WAIT_TIMEOUT: return 0; default: errno = EINVAL; return -1; } } int posix_spawnattr_init(posix_spawnattr_t* attrp) { *attrp = posix_spawnattr_t(); return 0; } int posix_spawnattr_setflags(posix_spawnattr_t* attrp, short flags) { attrp->flags = flags; return 0; } int posix_spawnattr_getflags(posix_spawnattr_t* attrp, short* flags) { *flags = attrp->flags; return 0; } int posix_spawnattr_setcwd_np(posix_spawnattr_t* attrp, const char* path) { char* path_dup = NULL; if (path) { path_dup = strdup(path); if (!path_dup) { return ENOMEM; } } free(attrp->working_dir); attrp->working_dir = path_dup; return 0; } int posix_spawnattr_destroy(posix_spawnattr_t* attrp) { free(attrp->working_dir); return 0; } int posix_spawn_file_actions_init(posix_spawn_file_actions_t* actions) { *actions = posix_spawn_file_actions_t(); return 0; } int posix_spawn_file_actions_adddup2( posix_spawn_file_actions_t* actions, int fd, int target_fd) { struct _posix_spawn_file_action *acts, *act; acts = (_posix_spawn_file_action*)realloc( actions->acts, (actions->nacts + 1) * sizeof(*acts)); if (!acts) { return ENOMEM; } act = &acts[actions->nacts]; act->action = _posix_spawn_file_action::dup_fd; act->u.source_fd = fd; act->target_fd = target_fd; actions->acts = acts; actions->nacts++; return 0; } int posix_spawn_file_actions_adddup2_handle_np( posix_spawn_file_actions_t* actions, intptr_t handle, int target_fd) { struct _posix_spawn_file_action *acts, *act; acts = (_posix_spawn_file_action*)realloc( actions->acts, (actions->nacts + 1) * sizeof(*acts)); if (!acts) { return ENOMEM; } act = &acts[actions->nacts]; act->action = _posix_spawn_file_action::dup_handle; act->u.dup_local_handle = handle; act->target_fd = target_fd; actions->acts = acts; actions->nacts++; return 0; } int posix_spawn_file_actions_addopen( posix_spawn_file_actions_t* actions, int target_fd, const char* name, int flags, int mode) { struct _posix_spawn_file_action *acts, *act; char* name_dup = strdup(name); if (!name_dup) { return ENOMEM; } acts = (_posix_spawn_file_action*)realloc( actions->acts, (actions->nacts + 1) * sizeof(*acts)); if (!acts) { free(name_dup); return ENOMEM; } act = &acts[actions->nacts]; act->action = _posix_spawn_file_action::open_file; act->target_fd = target_fd; act->u.open_info.name = name_dup; act->u.open_info.flags = flags; act->u.open_info.mode = mode; actions->acts = acts; actions->nacts++; return 0; } int posix_spawn_file_actions_destroy(posix_spawn_file_actions_t* actions) { int i; for (i = 0; i < actions->nacts; i++) { if (actions->acts[i].action != _posix_spawn_file_action::open_file) { continue; } free(actions->acts[i].u.open_info.name); } free(actions->acts); return 0; } #define CMD_EXE_PREFIX "cmd.exe /c \"" static char* build_command_line(char* const argv[]) { int argc = 0, i = 0; size_t size = 0; char *cmdbuf = NULL, *cur = NULL; // Note: includes trailing NUL which we count as the closing quote size = sizeof(CMD_EXE_PREFIX); for (argc = 0; argv[argc] != NULL; argc++) { size += 4 * (strlen(argv[argc]) + 1); } cmdbuf = (char*)malloc(size); if (!cmdbuf) { return NULL; } // Here be dragons. More gory details in http://stackoverflow.com/q/4094699 // Surely not complete here by any means strcpy_s(cmdbuf, size, CMD_EXE_PREFIX); cur = cmdbuf + strlen(CMD_EXE_PREFIX); for (i = 0; i < argc; i++) { int j; char* arg = argv[i]; // Space separated if (i > 0) { *cur = ' '; cur++; } *cur = '"'; cur++; // FIXME: multibyte for (j = 0; arg[j]; j++) { switch (arg[j]) { case '"': strcpy(cur, "\"\"\""); cur += 3; break; default: *cur = arg[j]; cur++; } } *cur = '"'; cur++; } *cur = '"'; cur++; *cur = 0; return cmdbuf; } static char* make_env_block(char* const envp[]) { int i; size_t total_len = 1; /* for final NUL */ char* block = NULL; char* target = NULL; for (i = 0; envp[i]; i++) { total_len += strlen(envp[i]) + 1; } block = (char*)malloc(total_len); if (!block) { return NULL; } target = block; for (i = 0; envp[i]; i++) { size_t len = strlen(envp[i]); // Also copy the NULL memcpy(target, envp[i], len + 1); target += len + 1; } // Final NUL terminator *target = 0; return block; } static int posix_spawn_common( bool search_path, pid_t* pid, const char* path, const posix_spawn_file_actions_t* file_actions, const posix_spawnattr_t* attrp, char* const argv[], char* const envp[]) { auto sinfo = STARTUPINFOEX(); auto sec = SECURITY_ATTRIBUTES(); auto pinfo = PROCESS_INFORMATION(); char* cmdbuf; char* env_block; DWORD create_flags = CREATE_NO_WINDOW | EXTENDED_STARTUPINFO_PRESENT; int ret; int i; HANDLE inherited_handles[3] = {0, 0, 0}; cmdbuf = build_command_line(argv); if (!cmdbuf) { return ENOMEM; } env_block = make_env_block(envp); if (!env_block) { free(cmdbuf); return ENOMEM; } sinfo.StartupInfo.cb = sizeof(sinfo); sinfo.StartupInfo.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; sinfo.StartupInfo.wShowWindow = SW_HIDE; sec.nLength = sizeof(sec); sec.bInheritHandle = TRUE; if (attrp->flags & POSIX_SPAWN_SETPGROUP) { create_flags |= CREATE_NEW_PROCESS_GROUP; } for (i = 0; i < file_actions->nacts; i++) { struct _posix_spawn_file_action* act = &file_actions->acts[i]; HANDLE* target = NULL; switch (act->target_fd) { case 0: target = &sinfo.StartupInfo.hStdInput; break; case 1: target = &sinfo.StartupInfo.hStdOutput; break; case 2: target = &sinfo.StartupInfo.hStdError; break; } if (!target) { logf(ERR, "posix_spawn: can't target fd outside range [0-2]\n"); ret = ENOSYS; goto done; } if (act->action != _posix_spawn_file_action::open_file) { // Process a dup(2) action DWORD err; if (*target) { CloseHandle(*target); *target = INVALID_HANDLE_VALUE; } if (act->action == _posix_spawn_file_action::dup_fd) { HANDLE src = NULL; switch (act->u.source_fd) { case 0: src = sinfo.StartupInfo.hStdInput; break; case 1: src = sinfo.StartupInfo.hStdOutput; break; case 2: src = sinfo.StartupInfo.hStdError; break; } if (!src) { src = (HANDLE)_get_osfhandle(act->u.source_fd); } act->u.dup_local_handle = intptr_t(src); } if (!DuplicateHandle( GetCurrentProcess(), (HANDLE)act->u.dup_local_handle, GetCurrentProcess(), target, 0, TRUE, DUPLICATE_SAME_ACCESS)) { err = GetLastError(); logf( ERR, "posix_spawn: failed to duplicate handle: {}\n", win32_strerror(err)); ret = map_win32_err(err); goto done; } } else { // Process an open(2) action auto h = w_handle_open( act->u.open_info.name, act->u.open_info.flags & ~O_CLOEXEC); if (!h) { ret = errno; logf( ERR, "posix_spawn: failed to open {} into target fd {}: {}\n", act->u.open_info.name, act->target_fd, folly::errnoStr(ret)); goto done; } if (*target) { CloseHandle(*target); } *target = (HANDLE)h.release(); } } if (!sinfo.StartupInfo.hStdInput) { sinfo.StartupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); } if (!sinfo.StartupInfo.hStdOutput) { sinfo.StartupInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); } if (!sinfo.StartupInfo.hStdError) { sinfo.StartupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE); } // Ensure that we only pass the stdio handles to the child. { SIZE_T size = 0; inherited_handles[0] = sinfo.StartupInfo.hStdInput; inherited_handles[1] = sinfo.StartupInfo.hStdOutput; inherited_handles[2] = sinfo.StartupInfo.hStdError; sinfo.lpAttributeList = NULL; InitializeProcThreadAttributeList(NULL, 1, 0, &size); sinfo.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)malloc(size); InitializeProcThreadAttributeList(sinfo.lpAttributeList, 1, 0, &size); UpdateProcThreadAttribute( sinfo.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, inherited_handles, 3 * sizeof(HANDLE), NULL, NULL); } if (!CreateProcess( search_path ? NULL : path, cmdbuf, &sec, &sec, TRUE, create_flags, env_block, attrp->working_dir, &sinfo.StartupInfo, &pinfo)) { logf( ERR, "CreateProcess: `{}`: (cwd={}) {}\n", cmdbuf, attrp->working_dir ? attrp->working_dir : "<process cwd>", win32_strerror(GetLastError())); ret = EACCES; } else { *pid = (pid_t)pinfo.dwProcessId; // Record the pid -> handle mapping for later wait/reap child_procs.wlock()->emplace(pinfo.dwProcessId, pinfo.hProcess); CloseHandle(pinfo.hThread); ret = 0; } free(sinfo.lpAttributeList); done: free(cmdbuf); free(env_block); // If we manufactured any handles, close them out now if (sinfo.StartupInfo.hStdInput != GetStdHandle(STD_INPUT_HANDLE)) { CloseHandle(sinfo.StartupInfo.hStdInput); } if (sinfo.StartupInfo.hStdOutput != GetStdHandle(STD_OUTPUT_HANDLE)) { CloseHandle(sinfo.StartupInfo.hStdOutput); } if (sinfo.StartupInfo.hStdError != GetStdHandle(STD_ERROR_HANDLE)) { CloseHandle(sinfo.StartupInfo.hStdError); } return ret; } int posix_spawn( pid_t* pid, const char* path, const posix_spawn_file_actions_t* file_actions, const posix_spawnattr_t* attrp, char* const argv[], char* const envp[]) { return posix_spawn_common(false, pid, path, file_actions, attrp, argv, envp); } int posix_spawnp( pid_t* pid, const char* file, const posix_spawn_file_actions_t* file_actions, const posix_spawnattr_t* attrp, char* const argv[], char* const envp[]) { return posix_spawn_common(true, pid, file, file_actions, attrp, argv, envp); }