405 lines
12 KiB
C
405 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 <signal.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( ¶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;
|
|
}
|