From 412555e0cdcd16439db56f6bd6ea56cedcda0883 Mon Sep 17 00:00:00 2001 From: Alexandre Julliard Date: Fri, 12 Jun 2020 09:26:50 +0200 Subject: [PATCH] ntdll: Move fork and exec support to the Unix library. Signed-off-by: Alexandre Julliard --- dlls/ntdll/Makefile.in | 1 + dlls/ntdll/process.c | 381 +------------------------------ dlls/ntdll/unix/env.c | 124 ++++++++++ dlls/ntdll/unix/loader.c | 8 +- dlls/ntdll/unix/process.c | 403 +++++++++++++++++++++++++++++++++ dlls/ntdll/unix/unix_private.h | 9 + dlls/ntdll/unixlib.h | 12 +- 7 files changed, 553 insertions(+), 385 deletions(-) create mode 100644 dlls/ntdll/unix/process.c diff --git a/dlls/ntdll/Makefile.in b/dlls/ntdll/Makefile.in index 24e76236ad5..93d1609c69f 100644 --- a/dlls/ntdll/Makefile.in +++ b/dlls/ntdll/Makefile.in @@ -53,6 +53,7 @@ C_SRCS = \ unix/debug.c \ unix/env.c \ unix/loader.c \ + unix/process.c \ unix/server.c \ unix/signal_arm.c \ unix/signal_arm64.c \ diff --git a/dlls/ntdll/process.c b/dlls/ntdll/process.c index 5b9e5638ef3..0f91668b36e 100644 --- a/dlls/ntdll/process.c +++ b/dlls/ntdll/process.c @@ -840,133 +840,6 @@ NTSTATUS WINAPI NtSuspendProcess( HANDLE handle ) } -/*********************************************************************** - * build_argv - * - * Build an argv array from a command-line. - * 'reserved' is the number of args to reserve before the first one. - */ -static char **build_argv( const UNICODE_STRING *cmdlineW, int reserved ) -{ - int argc; - char **argv; - char *arg, *s, *d, *cmdline; - int in_quotes, bcount, len; - - len = cmdlineW->Length / sizeof(WCHAR); - if (!(cmdline = RtlAllocateHeap( GetProcessHeap(), 0, len * 3 + 1 ))) return NULL; - len = ntdll_wcstoumbs( cmdlineW->Buffer, len, cmdline, len * 3, FALSE ); - cmdline[len++] = 0; - - argc = reserved + 1; - bcount = 0; - in_quotes = 0; - s = cmdline; - while (1) - { - if (*s == '\0' || ((*s == ' ' || *s == '\t') && !in_quotes)) - { - /* space */ - argc++; - /* skip the remaining spaces */ - while (*s == ' ' || *s == '\t') s++; - if (*s == '\0') break; - bcount = 0; - continue; - } - else if (*s == '\\') bcount++; /* '\', count them */ - else if ((*s == '"') && ((bcount & 1) == 0)) - { - if (in_quotes && s[1] == '"') s++; - else - { - /* unescaped '"' */ - in_quotes = !in_quotes; - bcount = 0; - } - } - else bcount = 0; /* a regular character */ - s++; - } - if (!(argv = RtlAllocateHeap( GetProcessHeap(), 0, argc * sizeof(*argv) + len ))) - { - RtlFreeHeap( GetProcessHeap(), 0, cmdline ); - return NULL; - } - - arg = d = s = (char *)(argv + argc); - memcpy( d, cmdline, len ); - bcount = 0; - in_quotes = 0; - argc = reserved; - while (*s) - { - if ((*s == ' ' || *s == '\t') && !in_quotes) - { - /* Close the argument and copy it */ - *d = 0; - argv[argc++] = arg; - /* skip the remaining spaces */ - do - { - s++; - } while (*s == ' ' || *s == '\t'); - - /* Start with a new argument */ - arg = d = s; - bcount = 0; - } - else if (*s == '\\') - { - *d++ = *s++; - bcount++; - } - else if (*s == '"') - { - if ((bcount & 1) == 0) - { - /* Preceded by an even number of '\', this is half that - * number of '\', plus a '"' which we discard. - */ - d -= bcount/2; - s++; - if (in_quotes && *s == '"') - { - *d++ = '"'; - s++; - } - else in_quotes = !in_quotes; - } - else - { - /* Preceded by an odd number of '\', this is half that - * number of '\' followed by a '"' - */ - d = d - bcount / 2 - 1; - *d++ = '"'; - s++; - } - bcount = 0; - } - else - { - /* a regular character */ - *d++ = *s++; - bcount = 0; - } - } - if (*arg) - { - *d = '\0'; - argv[argc++] = arg; - } - argv[argc] = NULL; - - RtlFreeHeap( GetProcessHeap(), 0, cmdline ); - return argv; -} - - static inline const WCHAR *get_params_string( const RTL_USER_PROCESS_PARAMETERS *params, const UNICODE_STRING *str ) { @@ -1035,136 +908,6 @@ static startup_info_t *create_startup_info( const RTL_USER_PROCESS_PARAMETERS *p } -#ifdef __APPLE__ -/*********************************************************************** - * terminate_main_thread - * - * On some versions of Mac OS X, the execve system call fails with - * ENOTSUP if the process has multiple threads. Wine is always multi- - * threaded on Mac OS X because it specifically reserves the main thread - * for use by the system frameworks (see apple_main_thread() in - * libs/wine/loader.c). So, when we need to exec without first forking, - * we need to terminate the main thread first. We do this by installing - * a custom run loop source onto the main run loop and signaling it. - * The source's "perform" callback is pthread_exit and it will be - * executed on the main thread, terminating it. - * - * Returns TRUE if there's still hope the main thread has terminated or - * will soon. Return FALSE if we've given up. - */ -static BOOL terminate_main_thread(void) -{ - static int delayms; - - if (!delayms) - { - CFRunLoopSourceContext source_context = { 0 }; - CFRunLoopSourceRef source; - - source_context.perform = pthread_exit; - if (!(source = CFRunLoopSourceCreate( NULL, 0, &source_context ))) - return FALSE; - - CFRunLoopAddSource( CFRunLoopGetMain(), source, kCFRunLoopCommonModes ); - CFRunLoopSourceSignal( source ); - CFRunLoopWakeUp( CFRunLoopGetMain() ); - CFRelease( source ); - - delayms = 20; - } - - if (delayms > 1000) - return FALSE; - - usleep(delayms * 1000); - delayms *= 2; - - return TRUE; -} -#endif - - -/*********************************************************************** - * set_stdio_fd - */ -static void set_stdio_fd( int stdin_fd, int stdout_fd ) -{ - int fd = -1; - - if (stdin_fd == -1 || stdout_fd == -1) - { - fd = open( "/dev/null", O_RDWR ); - if (stdin_fd == -1) stdin_fd = fd; - if (stdout_fd == -1) stdout_fd = fd; - } - - dup2( stdin_fd, 0 ); - dup2( stdout_fd, 1 ); - if (fd != -1) close( fd ); -} - - -/*********************************************************************** - * spawn_loader - */ -static NTSTATUS spawn_loader( const RTL_USER_PROCESS_PARAMETERS *params, int socketfd, - const char *unixdir, char *winedebug, const pe_image_info_t *pe_info ) -{ - const int is_child_64bit = (pe_info->cpu == CPU_x86_64 || pe_info->cpu == CPU_ARM64); - pid_t pid; - int stdin_fd = -1, stdout_fd = -1; - char **argv; - NTSTATUS status = STATUS_SUCCESS; - - argv = build_argv( ¶ms->CommandLine, 2 ); - - wine_server_handle_to_fd( params->hStdInput, FILE_READ_DATA, &stdin_fd, NULL ); - wine_server_handle_to_fd( params->hStdOutput, FILE_WRITE_DATA, &stdout_fd, NULL ); - - if (!(pid = fork())) /* child */ - { - if (!(pid = fork())) /* grandchild */ - { - if (params->ConsoleFlags || - params->ConsoleHandle == (HANDLE)1 /* KERNEL32_CONSOLE_ALLOC */ || - (params->hStdInput == INVALID_HANDLE_VALUE && params->hStdOutput == INVALID_HANDLE_VALUE)) - { - setsid(); - set_stdio_fd( -1, -1 ); /* close stdin and stdout */ - } - else set_stdio_fd( stdin_fd, stdout_fd ); - - if (stdin_fd != -1) close( stdin_fd ); - if (stdout_fd != -1) close( stdout_fd ); - - if (winedebug) putenv( winedebug ); - if (unixdir) chdir( unixdir ); - - unix_funcs->exec_wineloader( argv, socketfd, is_child_64bit, - pe_info->base, pe_info->base + pe_info->map_size ); - _exit(1); - } - - _exit(pid == -1); - } - - if (pid != -1) - { - /* reap child */ - pid_t wret; - do { - wret = waitpid(pid, NULL, 0); - } while (wret < 0 && errno == EINTR); - } - else status = FILE_GetNtStatus(); - - if (stdin_fd != -1) close( stdin_fd ); - if (stdout_fd != -1) close( stdout_fd ); - RtlFreeHeap( GetProcessHeap(), 0, argv ); - return status; -} - - /*************************************************************************** * is_builtin_path */ @@ -1404,9 +1147,6 @@ static char *get_unix_curdir( const RTL_USER_PROCESS_PARAMETERS *params ) */ static NTSTATUS fork_and_exec( UNICODE_STRING *path, const RTL_USER_PROCESS_PARAMETERS *params ) { - pid_t pid; - int fd[2], stdin_fd = -1, stdout_fd = -1; - char **argv, **envp; char *unixdir; ANSI_STRING unix_name; NTSTATUS status; @@ -1414,79 +1154,8 @@ static NTSTATUS fork_and_exec( UNICODE_STRING *path, const RTL_USER_PROCESS_PARA status = wine_nt_to_unix_file_name( path, &unix_name, FILE_OPEN, FALSE ); if (status) return status; -#ifdef HAVE_PIPE2 - if (pipe2( fd, O_CLOEXEC ) == -1) -#endif - { - if (pipe(fd) == -1) - { - RtlFreeAnsiString( &unix_name ); - return STATUS_TOO_MANY_OPENED_FILES; - } - fcntl( fd[0], F_SETFD, FD_CLOEXEC ); - fcntl( fd[1], F_SETFD, FD_CLOEXEC ); - } - - wine_server_handle_to_fd( params->hStdInput, FILE_READ_DATA, &stdin_fd, NULL ); - wine_server_handle_to_fd( params->hStdOutput, FILE_WRITE_DATA, &stdout_fd, NULL ); - - argv = build_argv( ¶ms->CommandLine, 0 ); - envp = build_envp( params->Environment ); unixdir = get_unix_curdir( params ); - - if (!(pid = fork())) /* child */ - { - if (!(pid = fork())) /* grandchild */ - { - close( fd[0] ); - - if (params->ConsoleFlags || - params->ConsoleHandle == (HANDLE)1 /* KERNEL32_CONSOLE_ALLOC */ || - (params->hStdInput == INVALID_HANDLE_VALUE && params->hStdOutput == INVALID_HANDLE_VALUE)) - { - setsid(); - set_stdio_fd( -1, -1 ); /* close stdin and stdout */ - } - else set_stdio_fd( stdin_fd, stdout_fd ); - - if (stdin_fd != -1) close( stdin_fd ); - if (stdout_fd != -1) close( stdout_fd ); - - /* Reset signals that we previously set to SIG_IGN */ - signal( SIGPIPE, SIG_DFL ); - - if (unixdir) chdir( unixdir ); - - if (argv && envp) execve( unix_name.Buffer, argv, envp ); - } - - if (pid <= 0) /* grandchild if exec failed or child if fork failed */ - { - status = FILE_GetNtStatus(); - write( fd[1], &status, sizeof(status) ); - _exit(1); - } - - _exit(0); /* child if fork succeeded */ - } - close( fd[1] ); - - if (pid != -1) - { - /* reap child */ - pid_t wret; - do { - wret = waitpid(pid, NULL, 0); - } while (wret < 0 && errno == EINTR); - read( fd[0], &status, sizeof(status) ); /* if we read something, exec or second fork failed */ - } - else status = FILE_GetNtStatus(); - - close( fd[0] ); - if (stdin_fd != -1) close( stdin_fd ); - if (stdout_fd != -1) close( stdout_fd ); - RtlFreeHeap( GetProcessHeap(), 0, argv ); - RtlFreeHeap( GetProcessHeap(), 0, envp ); + status = unix_funcs->fork_and_exec( unix_name.Buffer, unixdir, params ); RtlFreeHeap( GetProcessHeap(), 0, unixdir ); RtlFreeAnsiString( &unix_name ); return status; @@ -1503,7 +1172,6 @@ NTSTATUS restart_process( RTL_USER_PROCESS_PARAMETERS *params, NTSTATUS status ) static const WCHAR comW[] = {'.','c','o','m',0}; static const WCHAR pifW[] = {'.','p','i','f',0}; - int socketfd[2]; WCHAR *p, *cmdline; UNICODE_STRING strW; pe_image_info_t pe_info; @@ -1548,49 +1216,7 @@ NTSTATUS restart_process( RTL_USER_PROCESS_PARAMETERS *params, NTSTATUS status ) return status; } - /* exec the new process */ - - if (socketpair( PF_UNIX, SOCK_STREAM, 0, socketfd ) == -1) return STATUS_TOO_MANY_OPENED_FILES; -#ifdef SO_PASSCRED - else - { - int enable = 1; - setsockopt( socketfd[0], SOL_SOCKET, SO_PASSCRED, &enable, sizeof(enable) ); - } -#endif - wine_server_send_fd( socketfd[1] ); - close( socketfd[1] ); - - SERVER_START_REQ( exec_process ) - { - req->socket_fd = socketfd[1]; - req->cpu = pe_info.cpu; - status = wine_server_call( req ); - } - SERVER_END_REQ; - - if (!status) - { - const int is_child_64bit = (pe_info.cpu == CPU_x86_64 || pe_info.cpu == CPU_ARM64); - char **argv = build_argv( &strW, 2 ); - if (argv) - { - do - { - status = unix_funcs->exec_wineloader( argv, socketfd[0], is_child_64bit, - pe_info.base, pe_info.base + pe_info.map_size ); - } -#ifdef __APPLE__ - while (errno == ENOTSUP && terminate_main_thread()); -#else - while (0); -#endif - RtlFreeHeap( GetProcessHeap(), 0, argv ); - } - else status = STATUS_NO_MEMORY; - } - close( socketfd[0] ); - return status; + return unix_funcs->exec_process( &strW, &pe_info ); } @@ -1743,7 +1369,8 @@ NTSTATUS WINAPI NtCreateUserProcess( HANDLE *process_handle_ptr, HANDLE *thread_ /* create the child process */ - if ((status = spawn_loader( params, socketfd[0], unixdir, winedebug, &pe_info ))) goto done; + if ((status = unix_funcs->spawn_process( params, socketfd[0], unixdir, winedebug, &pe_info ))) + goto done; close( socketfd[0] ); socketfd[0] = -1; diff --git a/dlls/ntdll/unix/env.c b/dlls/ntdll/unix/env.c index 2d6fe6ec966..c2674be2c62 100644 --- a/dlls/ntdll/unix/env.c +++ b/dlls/ntdll/unix/env.c @@ -408,6 +408,14 @@ static void init_unix_codepage(void) #endif /* __APPLE__ || __ANDROID__ */ +static inline SIZE_T get_env_length( const WCHAR *env ) +{ + const WCHAR *end = env; + while (*end) end += wcslen(end) + 1; + return end + 1 - env; +} + + /*********************************************************************** * is_special_env_var * @@ -446,6 +454,122 @@ DWORD ntdll_umbstowcs( const char *src, DWORD srclen, WCHAR *dst, DWORD dstlen ) } +/****************************************************************** + * ntdll_wcstoumbs + */ +int ntdll_wcstoumbs( const WCHAR *src, DWORD srclen, char *dst, DWORD dstlen, BOOL strict ) +{ + DWORD i, reslen; + + if (unix_table.CodePage) + { + if (unix_table.DBCSOffsets) + { + const unsigned short *uni2cp = unix_table.WideCharTable; + for (i = dstlen; srclen && i; i--, srclen--, src++) + { + unsigned short ch = uni2cp[*src]; + if (ch >> 8) + { + if (strict && unix_table.DBCSOffsets[unix_table.DBCSOffsets[ch >> 8] + (ch & 0xff)] != *src) + return -1; + if (i == 1) break; /* do not output a partial char */ + i--; + *dst++ = ch >> 8; + } + else + { + if (unix_table.MultiByteTable[ch] != *src) return -1; + *dst++ = (char)ch; + } + } + reslen = dstlen - i; + } + else + { + const unsigned char *uni2cp = unix_table.WideCharTable; + reslen = min( srclen, dstlen ); + for (i = 0; i < reslen; i++) + { + unsigned char ch = uni2cp[src[i]]; + if (strict && unix_table.MultiByteTable[ch] != src[i]) return -1; + dst[i] = ch; + } + } + } + else RtlUnicodeToUTF8N( dst, dstlen, &reslen, src, srclen * sizeof(WCHAR) ); + + return reslen; +} + + +/*********************************************************************** + * build_envp + * + * Build the environment of a new child process. + */ +char **build_envp( const WCHAR *envW ) +{ + static const char * const unix_vars[] = { "PATH", "TEMP", "TMP", "HOME" }; + char **envp; + char *env, *p; + int count = 1, length, lenW; + unsigned int i; + + lenW = get_env_length( envW ); + if (!(env = malloc( lenW * 3 ))) return NULL; + length = ntdll_wcstoumbs( envW, lenW, env, lenW * 3, FALSE ); + + for (p = env; *p; p += strlen(p) + 1, count++) + if (is_special_env_var( p )) length += 4; /* prefix it with "WINE" */ + + for (i = 0; i < ARRAY_SIZE( unix_vars ); i++) + { + if (!(p = getenv(unix_vars[i]))) continue; + length += strlen(unix_vars[i]) + strlen(p) + 2; + count++; + } + + if ((envp = malloc( count * sizeof(*envp) + length ))) + { + char **envptr = envp; + char *dst = (char *)(envp + count); + + /* some variables must not be modified, so we get them directly from the unix env */ + for (i = 0; i < ARRAY_SIZE( unix_vars ); i++) + { + if (!(p = getenv( unix_vars[i] ))) continue; + *envptr++ = strcpy( dst, unix_vars[i] ); + strcat( dst, "=" ); + strcat( dst, p ); + dst += strlen(dst) + 1; + } + + /* now put the Windows environment strings */ + for (p = env; *p; p += strlen(p) + 1) + { + if (*p == '=') continue; /* skip drive curdirs, this crashes some unix apps */ + if (!strncmp( p, "WINEPRELOADRESERVE=", sizeof("WINEPRELOADRESERVE=")-1 )) continue; + if (!strncmp( p, "WINELOADERNOEXEC=", sizeof("WINELOADERNOEXEC=")-1 )) continue; + if (!strncmp( p, "WINESERVERSOCKET=", sizeof("WINESERVERSOCKET=")-1 )) continue; + if (is_special_env_var( p )) /* prefix it with "WINE" */ + { + *envptr++ = strcpy( dst, "WINE" ); + strcat( dst, p ); + } + else + { + *envptr++ = strcpy( dst, p ); + } + dst += strlen(dst) + 1; + } + *envptr = 0; + } + free( env ); + return envp; +} + + /*********************************************************************** * set_process_name * diff --git a/dlls/ntdll/unix/loader.c b/dlls/ntdll/unix/loader.c index bb13144f9d5..735303a6725 100644 --- a/dlls/ntdll/unix/loader.c +++ b/dlls/ntdll/unix/loader.c @@ -424,8 +424,8 @@ static NTSTATUS loader_exec( const char *loader, char **argv, int is_child_64bit * * argv[0] and argv[1] must be reserved for the preloader and loader respectively. */ -static NTSTATUS CDECL exec_wineloader( char **argv, int socketfd, int is_child_64bit, - ULONGLONG res_start, ULONGLONG res_end ) +NTSTATUS exec_wineloader( char **argv, int socketfd, int is_child_64bit, + ULONGLONG res_start, ULONGLONG res_end ) { const char *loader = argv0; const char *loader_env = getenv( "WINELOADER" ); @@ -906,7 +906,6 @@ static struct unix_funcs unix_funcs = get_version, get_build_id, get_host_version, - exec_wineloader, map_so_dll, virtual_map_section, virtual_get_system_info, @@ -925,6 +924,9 @@ static struct unix_funcs unix_funcs = exit_thread, exit_process, get_thread_ldt_entry, + spawn_process, + exec_process, + fork_and_exec, wine_server_call, server_send_fd, server_get_unix_fd, diff --git a/dlls/ntdll/unix/process.c b/dlls/ntdll/unix/process.c new file mode 100644 index 00000000000..9f38f042ded --- /dev/null +++ b/dlls/ntdll/unix/process.c @@ -0,0 +1,403 @@ +/* + * NT process handling + * + * Copyright 1996-1998 Marcus Meissner + * Copyright 2018, 2020 Alexandre Julliard + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#if 0 +#pragma makedep unix +#endif + +#include "config.h" +#include "wine/port.h" + +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SYS_SOCKET_H +#include +#endif +#ifdef HAVE_SYS_TIME_H +# include +#endif +#ifdef HAVE_SYS_TIMES_H +# include +#endif +#include +#ifdef HAVE_SYS_WAIT_H +# include +#endif +#ifdef HAVE_UNISTD_H +# include +#endif +#ifdef __APPLE__ +# include +# include +#endif +#ifdef HAVE_MACH_MACH_H +# include +#endif + +#include "ntstatus.h" +#define WIN32_NO_STATUS +#include "windef.h" +#include "winternl.h" +#include "unix_private.h" +#include "wine/exception.h" +#include "wine/server.h" +#include "wine/debug.h" + + +static char **build_argv( const UNICODE_STRING *cmdline, int reserved ) +{ + char **argv, *arg, *src, *dst; + int argc, in_quotes = 0, bcount = 0, len = cmdline->Length / sizeof(WCHAR); + + if (!(src = malloc( len * 3 + 1 ))) return NULL; + len = ntdll_wcstoumbs( cmdline->Buffer, len, src, len * 3, FALSE ); + src[len++] = 0; + + argc = reserved + 2 + len / 2; + argv = malloc( argc * sizeof(*argv) + len ); + arg = dst = (char *)(argv + argc); + argc = reserved; + while (*src) + { + if ((*src == ' ' || *src == '\t') && !in_quotes) + { + /* skip the remaining spaces */ + while (*src == ' ' || *src == '\t') src++; + if (!*src) break; + /* close the argument and copy it */ + *dst++ = 0; + argv[argc++] = arg; + /* start with a new argument */ + arg = dst; + bcount = 0; + } + else if (*src == '\\') + { + *dst++ = *src++; + bcount++; + } + else if (*src == '"') + { + if ((bcount & 1) == 0) + { + /* Preceded by an even number of '\', this is half that + * number of '\', plus a '"' which we discard. + */ + dst -= bcount / 2; + src++; + if (in_quotes && *src == '"') *dst++ = *src++; + else in_quotes = !in_quotes; + } + else + { + /* Preceded by an odd number of '\', this is half that + * number of '\' followed by a '"' + */ + dst -= bcount / 2 + 1; + *dst++ = *src++; + } + bcount = 0; + } + else /* a regular character */ + { + *dst++ = *src++; + bcount = 0; + } + } + *dst = 0; + argv[argc++] = arg; + argv[argc] = NULL; + return argv; +} + + +#ifdef __APPLE__ +/*********************************************************************** + * terminate_main_thread + * + * On some versions of Mac OS X, the execve system call fails with + * ENOTSUP if the process has multiple threads. Wine is always multi- + * threaded on Mac OS X because it specifically reserves the main thread + * for use by the system frameworks (see apple_main_thread() in + * libs/wine/loader.c). So, when we need to exec without first forking, + * we need to terminate the main thread first. We do this by installing + * a custom run loop source onto the main run loop and signaling it. + * The source's "perform" callback is pthread_exit and it will be + * executed on the main thread, terminating it. + * + * Returns TRUE if there's still hope the main thread has terminated or + * will soon. Return FALSE if we've given up. + */ +static BOOL terminate_main_thread(void) +{ + static int delayms; + + if (!delayms) + { + CFRunLoopSourceContext source_context = { 0 }; + CFRunLoopSourceRef source; + + source_context.perform = pthread_exit; + if (!(source = CFRunLoopSourceCreate( NULL, 0, &source_context ))) + return FALSE; + + CFRunLoopAddSource( CFRunLoopGetMain(), source, kCFRunLoopCommonModes ); + CFRunLoopSourceSignal( source ); + CFRunLoopWakeUp( CFRunLoopGetMain() ); + CFRelease( source ); + + delayms = 20; + } + + if (delayms > 1000) + return FALSE; + + usleep(delayms * 1000); + delayms *= 2; + + return TRUE; +} +#endif + + +/*********************************************************************** + * set_stdio_fd + */ +static void set_stdio_fd( int stdin_fd, int stdout_fd ) +{ + int fd = -1; + + if (stdin_fd == -1 || stdout_fd == -1) + { + fd = open( "/dev/null", O_RDWR ); + if (stdin_fd == -1) stdin_fd = fd; + if (stdout_fd == -1) stdout_fd = fd; + } + + dup2( stdin_fd, 0 ); + dup2( stdout_fd, 1 ); + if (fd != -1) close( fd ); +} + + +/*********************************************************************** + * spawn_process + */ +NTSTATUS CDECL spawn_process( const RTL_USER_PROCESS_PARAMETERS *params, int socketfd, + const char *unixdir, char *winedebug, const pe_image_info_t *pe_info ) +{ + const int is_child_64bit = (pe_info->cpu == CPU_x86_64 || pe_info->cpu == CPU_ARM64); + NTSTATUS status = STATUS_SUCCESS; + int stdin_fd = -1, stdout_fd = -1; + pid_t pid; + char **argv; + + server_handle_to_fd( params->hStdInput, FILE_READ_DATA, &stdin_fd, NULL ); + server_handle_to_fd( params->hStdOutput, FILE_WRITE_DATA, &stdout_fd, NULL ); + + if (!(pid = fork())) /* child */ + { + if (!(pid = fork())) /* grandchild */ + { + if (params->ConsoleFlags || + params->ConsoleHandle == (HANDLE)1 /* KERNEL32_CONSOLE_ALLOC */ || + (params->hStdInput == INVALID_HANDLE_VALUE && params->hStdOutput == INVALID_HANDLE_VALUE)) + { + setsid(); + set_stdio_fd( -1, -1 ); /* close stdin and stdout */ + } + else set_stdio_fd( stdin_fd, stdout_fd ); + + if (stdin_fd != -1) close( stdin_fd ); + if (stdout_fd != -1) close( stdout_fd ); + + if (winedebug) putenv( winedebug ); + if (unixdir) chdir( unixdir ); + + argv = build_argv( ¶ms->CommandLine, 2 ); + + exec_wineloader( argv, socketfd, is_child_64bit, + pe_info->base, pe_info->base + pe_info->map_size ); + _exit(1); + } + + _exit(pid == -1); + } + + if (pid != -1) + { + /* reap child */ + pid_t wret; + do { + wret = waitpid(pid, NULL, 0); + } while (wret < 0 && errno == EINTR); + } + else status = STATUS_NO_MEMORY; + + if (stdin_fd != -1) close( stdin_fd ); + if (stdout_fd != -1) close( stdout_fd ); + return status; +} + + +/*********************************************************************** + * exec_process + */ +NTSTATUS CDECL exec_process( const UNICODE_STRING *cmdline, const pe_image_info_t *pe_info ) +{ + const int is_child_64bit = (pe_info->cpu == CPU_x86_64 || pe_info->cpu == CPU_ARM64); + NTSTATUS status; + int socketfd[2]; + char **argv; + + if (socketpair( PF_UNIX, SOCK_STREAM, 0, socketfd ) == -1) return STATUS_TOO_MANY_OPENED_FILES; +#ifdef SO_PASSCRED + else + { + int enable = 1; + setsockopt( socketfd[0], SOL_SOCKET, SO_PASSCRED, &enable, sizeof(enable) ); + } +#endif + server_send_fd( socketfd[1] ); + close( socketfd[1] ); + + SERVER_START_REQ( exec_process ) + { + req->socket_fd = socketfd[1]; + req->cpu = pe_info->cpu; + status = wine_server_call( req ); + } + SERVER_END_REQ; + + if (!status) + { + if (!(argv = build_argv( cmdline, 2 ))) return STATUS_NO_MEMORY; + do + { + status = exec_wineloader( argv, socketfd[0], is_child_64bit, + pe_info->base, pe_info->base + pe_info->map_size ); + } +#ifdef __APPLE__ + while (errno == ENOTSUP && terminate_main_thread()); +#else + while (0); +#endif + free( argv ); + } + close( socketfd[0] ); + return status; +} + + +/*********************************************************************** + * fork_and_exec + * + * Fork and exec a new Unix binary, checking for errors. + */ +NTSTATUS CDECL fork_and_exec( const char *unix_name, const char *unix_dir, + const RTL_USER_PROCESS_PARAMETERS *params ) +{ + pid_t pid; + int fd[2], stdin_fd = -1, stdout_fd = -1; + char **argv, **envp; + NTSTATUS status; + +#ifdef HAVE_PIPE2 + if (pipe2( fd, O_CLOEXEC ) == -1) +#endif + { + if (pipe(fd) == -1) return STATUS_TOO_MANY_OPENED_FILES; + fcntl( fd[0], F_SETFD, FD_CLOEXEC ); + fcntl( fd[1], F_SETFD, FD_CLOEXEC ); + } + + server_handle_to_fd( params->hStdInput, FILE_READ_DATA, &stdin_fd, NULL ); + server_handle_to_fd( params->hStdOutput, FILE_WRITE_DATA, &stdout_fd, NULL ); + + if (!(pid = fork())) /* child */ + { + if (!(pid = fork())) /* grandchild */ + { + close( fd[0] ); + + if (params->ConsoleFlags || + params->ConsoleHandle == (HANDLE)1 /* KERNEL32_CONSOLE_ALLOC */ || + (params->hStdInput == INVALID_HANDLE_VALUE && params->hStdOutput == INVALID_HANDLE_VALUE)) + { + setsid(); + set_stdio_fd( -1, -1 ); /* close stdin and stdout */ + } + else set_stdio_fd( stdin_fd, stdout_fd ); + + if (stdin_fd != -1) close( stdin_fd ); + if (stdout_fd != -1) close( stdout_fd ); + + /* Reset signals that we previously set to SIG_IGN */ + signal( SIGPIPE, SIG_DFL ); + + argv = build_argv( ¶ms->CommandLine, 0 ); + envp = build_envp( params->Environment ); + if (unix_dir) chdir( unix_dir ); + + execve( unix_name, argv, envp ); + } + + if (pid <= 0) /* grandchild if exec failed or child if fork failed */ + { + switch (errno) + { + case EPERM: + case EACCES: status = STATUS_ACCESS_DENIED; break; + case ENOENT: status = STATUS_OBJECT_NAME_NOT_FOUND; break; + case EMFILE: + case ENFILE: status = STATUS_TOO_MANY_OPENED_FILES; break; + case ENOEXEC: + case EINVAL: status = STATUS_INVALID_IMAGE_FORMAT; break; + default: status = STATUS_NO_MEMORY; break; + } + write( fd[1], &status, sizeof(status) ); + _exit(1); + } + _exit(0); /* child if fork succeeded */ + } + close( fd[1] ); + + if (pid != -1) + { + /* reap child */ + pid_t wret; + do { + wret = waitpid(pid, NULL, 0); + } while (wret < 0 && errno == EINTR); + read( fd[0], &status, sizeof(status) ); /* if we read something, exec or second fork failed */ + } + else status = STATUS_NO_MEMORY; + + close( fd[0] ); + if (stdin_fd != -1) close( stdin_fd ); + if (stdout_fd != -1) close( stdout_fd ); + return status; +} diff --git a/dlls/ntdll/unix/unix_private.h b/dlls/ntdll/unix/unix_private.h index cc30761b1e2..1ef2111da19 100644 --- a/dlls/ntdll/unix/unix_private.h +++ b/dlls/ntdll/unix/unix_private.h @@ -114,6 +114,11 @@ extern TEB * CDECL init_threading( int *nb_threads_ptr, struct ldt_copy **ldt_co extern void CDECL DECLSPEC_NORETURN exit_thread( int status ) DECLSPEC_HIDDEN; extern void CDECL DECLSPEC_NORETURN exit_process( int status ) DECLSPEC_HIDDEN; extern NTSTATUS CDECL get_thread_ldt_entry( HANDLE handle, void *data, ULONG len, ULONG *ret_len ) DECLSPEC_HIDDEN; +extern NTSTATUS CDECL spawn_process( const RTL_USER_PROCESS_PARAMETERS *params, int socketfd, + const char *unixdir, char *winedebug, const pe_image_info_t *pe_info ) DECLSPEC_HIDDEN; +extern NTSTATUS CDECL exec_process( const UNICODE_STRING *cmdline, const pe_image_info_t *pe_info ) DECLSPEC_HIDDEN; +extern NTSTATUS CDECL fork_and_exec( const char *unix_name, const char *unix_dir, + const RTL_USER_PROCESS_PARAMETERS *params ) DECLSPEC_HIDDEN; extern const char *data_dir DECLSPEC_HIDDEN; extern const char *build_dir DECLSPEC_HIDDEN; @@ -129,6 +134,10 @@ extern struct _KUSER_SHARED_DATA *user_shared_data DECLSPEC_HIDDEN; extern void init_environment( int argc, char *argv[], char *envp[] ) DECLSPEC_HIDDEN; extern DWORD ntdll_umbstowcs( const char *src, DWORD srclen, WCHAR *dst, DWORD dstlen ) DECLSPEC_HIDDEN; +extern int ntdll_wcstoumbs( const WCHAR *src, DWORD srclen, char *dst, DWORD dstlen, BOOL strict ) DECLSPEC_HIDDEN; +extern char **build_envp( const WCHAR *envW ) DECLSPEC_HIDDEN; +extern NTSTATUS exec_wineloader( char **argv, int socketfd, int is_child_64bit, + ULONGLONG res_start, ULONGLONG res_end ) DECLSPEC_HIDDEN; extern unsigned int server_call_unlocked( void *req_ptr ) DECLSPEC_HIDDEN; extern void server_enter_uninterrupted_section( RTL_CRITICAL_SECTION *cs, sigset_t *sigset ) DECLSPEC_HIDDEN; diff --git a/dlls/ntdll/unixlib.h b/dlls/ntdll/unixlib.h index 54705b7f6d2..1f2d6317544 100644 --- a/dlls/ntdll/unixlib.h +++ b/dlls/ntdll/unixlib.h @@ -28,7 +28,7 @@ struct ldt_copy; struct msghdr; /* increment this when you change the function table */ -#define NTDLL_UNIXLIB_VERSION 40 +#define NTDLL_UNIXLIB_VERSION 41 struct unix_funcs { @@ -180,10 +180,6 @@ struct unix_funcs const char * (CDECL *get_build_id)(void); void (CDECL *get_host_version)( const char **sysname, const char **release ); - /* loader functions */ - NTSTATUS (CDECL *exec_wineloader)( char **argv, int socketfd, int is_child_64bit, - ULONGLONG res_start, ULONGLONG res_end ); - /* virtual memory functions */ NTSTATUS (CDECL *map_so_dll)( const IMAGE_NT_HEADERS *nt_descr, HMODULE module ); NTSTATUS (CDECL *virtual_map_section)( HANDLE handle, PVOID *addr_ptr, unsigned short zero_bits_64, SIZE_T commit_size, @@ -208,6 +204,12 @@ struct unix_funcs void (CDECL *exit_thread)( int status ); void (CDECL *exit_process)( int status ); NTSTATUS (CDECL *get_thread_ldt_entry)( HANDLE handle, void *data, ULONG len, ULONG *ret_len ); + NTSTATUS (CDECL *spawn_process)( const RTL_USER_PROCESS_PARAMETERS *params, int socketfd, + const char *unixdir, char *winedebug, + const pe_image_info_t *pe_info ); + NTSTATUS (CDECL *exec_process)( const UNICODE_STRING *cmdline, const pe_image_info_t *pe_info ); + NTSTATUS (CDECL *fork_and_exec)( const char *unix_name, const char *unix_dir, + const RTL_USER_PROCESS_PARAMETERS *params ); /* server functions */ unsigned int (CDECL *server_call)( void *req_ptr );