wine-wine/dlls/ntdll/unix/process.c

404 lines
12 KiB
C

/*
* 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 <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
#endif
#ifdef HAVE_SYS_TIMES_H
# include <sys/times.h>
#endif
#include <sys/types.h>
#ifdef HAVE_SYS_WAIT_H
# include <sys/wait.h>
#endif
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#ifdef __APPLE__
# include <CoreFoundation/CoreFoundation.h>
# include <pthread.h>
#endif
#ifdef HAVE_MACH_MACH_H
# include <mach/mach.h>
#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( &params->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( &params->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;
}