Commit 712ae337 authored by Alexandre Julliard's avatar Alexandre Julliard

kernel32: Move CreateProcess() functions to kernelbase.

parent 4405195b
......@@ -309,13 +309,13 @@
@ stdcall -import CreatePipe(ptr ptr ptr long)
# @ stub CreatePrivateNamespaceA
# @ stub CreatePrivateNamespaceW
@ stdcall CreateProcessA(str str ptr ptr long long ptr str ptr ptr)
@ stdcall CreateProcessAsUserA(long str str ptr ptr long long ptr str ptr ptr)
@ stdcall CreateProcessAsUserW(long wstr wstr ptr ptr long long ptr wstr ptr ptr)
@ stdcall CreateProcessInternalA(long str str ptr ptr long long ptr str ptr ptr ptr)
@ stdcall CreateProcessInternalW(long wstr wstr ptr ptr long long ptr wstr ptr ptr ptr)
@ stdcall -import CreateProcessA(str str ptr ptr long long ptr str ptr ptr)
@ stdcall -import CreateProcessAsUserA(long str str ptr ptr long long ptr str ptr ptr)
@ stdcall -import CreateProcessAsUserW(long wstr wstr ptr ptr long long ptr wstr ptr ptr)
@ stdcall -import CreateProcessInternalA(long str str ptr ptr long long ptr str ptr ptr ptr)
@ stdcall -import CreateProcessInternalW(long wstr wstr ptr ptr long long ptr wstr ptr ptr ptr)
# @ stub CreateProcessInternalWSecure
@ stdcall CreateProcessW(wstr wstr ptr ptr long long ptr wstr ptr ptr)
@ stdcall -import CreateProcessW(wstr wstr ptr ptr long long ptr wstr ptr ptr)
@ stdcall -import CreateRemoteThread(long ptr long ptr long long ptr)
@ stdcall -import CreateRemoteThreadEx(long ptr long ptr ptr long ptr ptr)
@ stdcall CreateSemaphoreA(ptr long long str)
......
......@@ -59,7 +59,6 @@ extern SYSTEM_BASIC_INFORMATION system_info DECLSPEC_HIDDEN;
extern const WCHAR DIR_Windows[] DECLSPEC_HIDDEN;
extern const WCHAR DIR_System[] DECLSPEC_HIDDEN;
extern const WCHAR *DIR_SysWow64 DECLSPEC_HIDDEN;
extern void FILE_SetDosError(void) DECLSPEC_HIDDEN;
extern WCHAR *FILE_name_AtoW( LPCSTR name, BOOL alloc ) DECLSPEC_HIDDEN;
......
......@@ -70,16 +70,12 @@ typedef struct
DWORD dwReserved;
} LOADPARMS32;
static BOOL is_wow64;
static const BOOL is_win64 = (sizeof(void *) > sizeof(int));
HMODULE kernel32_handle = 0;
SYSTEM_BASIC_INFORMATION system_info = { 0 };
const WCHAR DIR_Windows[] = {'C',':','\\','w','i','n','d','o','w','s',0};
const WCHAR DIR_System[] = {'C',':','\\','w','i','n','d','o','w','s',
'\\','s','y','s','t','e','m','3','2',0};
const WCHAR *DIR_SysWow64 = NULL;
/* Process flags */
#define PDB32_DEBUGGED 0x0001 /* Process is being debugged */
......@@ -89,38 +85,6 @@ const WCHAR *DIR_SysWow64 = NULL;
#define PDB32_FILE_APIS_OEM 0x0040 /* File APIs are OEM */
#define PDB32_WIN32S_PROC 0x8000 /* Win32s process */
static const WCHAR exeW[] = {'.','e','x','e',0};
static const WCHAR comW[] = {'.','c','o','m',0};
static const WCHAR batW[] = {'.','b','a','t',0};
static const WCHAR cmdW[] = {'.','c','m','d',0};
static const WCHAR pifW[] = {'.','p','i','f',0};
static WCHAR winevdm[] = {'C',':','\\','w','i','n','d','o','w','s',
'\\','s','y','s','t','e','m','3','2',
'\\','w','i','n','e','v','d','m','.','e','x','e',0};
/***********************************************************************
* find_exe_file
*
* Open an exe file, and return the full name and file handle.
* Returns FALSE if file could not be found.
*/
static BOOL find_exe_file( const WCHAR *name, WCHAR *buffer, int buflen )
{
WCHAR *load_path;
BOOL ret;
if (!set_ntstatus( RtlGetExePath( name, &load_path ))) return FALSE;
TRACE("looking for %s in %s\n", debugstr_w(name), debugstr_w(load_path) );
ret = (SearchPathW( load_path, name, exeW, buflen, buffer, NULL ) ||
/* not found, try without extension in case it is a Unix app */
SearchPathW( load_path, name, NULL, buflen, buffer, NULL ));
RtlReleasePath( load_path );
return ret;
}
/***********************************************************************
* set_library_argv
......@@ -155,22 +119,6 @@ static void set_library_argv( WCHAR **wargv )
}
/***********************************************************************
* init_windows_dirs
*/
static void init_windows_dirs(void)
{
static const WCHAR default_syswow64W[] = {'C',':','\\','w','i','n','d','o','w','s',
'\\','s','y','s','w','o','w','6','4',0};
if (is_win64 || is_wow64) /* SysWow64 is always defined on 64-bit */
{
DIR_SysWow64 = default_syswow64W;
memcpy( winevdm, default_syswow64W, sizeof(default_syswow64W) - sizeof(WCHAR) );
}
}
#ifdef __i386__
extern DWORD call_process_entry( PEB *peb, LPTHREAD_START_ROUTINE entry );
__ASM_GLOBAL_FUNC( call_process_entry,
......@@ -263,11 +211,9 @@ void * CDECL __wine_kernel_init(void)
setbuf(stdout,NULL);
setbuf(stderr,NULL);
kernel32_handle = GetModuleHandleW(kernel32W);
IsWow64Process( GetCurrentProcess(), &is_wow64 );
RtlSetUnhandledExceptionFilter( UnhandledExceptionFilter );
LOCALE_Init();
init_windows_dirs();
convert_old_config();
set_library_argv( __wine_main_wargv );
......@@ -278,474 +224,6 @@ void * CDECL __wine_kernel_init(void)
/***********************************************************************
* create_process_params
*/
static RTL_USER_PROCESS_PARAMETERS *create_process_params( LPCWSTR filename, LPCWSTR cmdline,
LPCWSTR cur_dir, void *env, DWORD flags,
const STARTUPINFOW *startup )
{
RTL_USER_PROCESS_PARAMETERS *params;
UNICODE_STRING imageW, dllpathW, curdirW, cmdlineW, titleW, desktopW, runtimeW, newdirW;
WCHAR imagepath[MAX_PATH];
WCHAR *load_path, *dummy, *envW = env;
if(!GetLongPathNameW( filename, imagepath, MAX_PATH ))
lstrcpynW( imagepath, filename, MAX_PATH );
if(!GetFullPathNameW( imagepath, MAX_PATH, imagepath, NULL ))
lstrcpynW( imagepath, filename, MAX_PATH );
if (env && !(flags & CREATE_UNICODE_ENVIRONMENT)) /* convert environment to unicode */
{
char *e = env;
DWORD lenW;
while (*e) e += strlen(e) + 1;
e++; /* final null */
lenW = MultiByteToWideChar( CP_ACP, 0, env, e - (char *)env, NULL, 0 );
if ((envW = HeapAlloc( GetProcessHeap(), 0, lenW * sizeof(WCHAR) )))
MultiByteToWideChar( CP_ACP, 0, env, e - (char *)env, envW, lenW );
}
newdirW.Buffer = NULL;
if (cur_dir)
{
if (RtlDosPathNameToNtPathName_U( cur_dir, &newdirW, NULL, NULL ))
cur_dir = newdirW.Buffer + 4; /* skip \??\ prefix */
else
cur_dir = NULL;
}
LdrGetDllPath( imagepath, LOAD_WITH_ALTERED_SEARCH_PATH, &load_path, &dummy );
RtlInitUnicodeString( &imageW, imagepath );
RtlInitUnicodeString( &dllpathW, load_path );
RtlInitUnicodeString( &curdirW, cur_dir );
RtlInitUnicodeString( &cmdlineW, cmdline );
RtlInitUnicodeString( &titleW, startup->lpTitle ? startup->lpTitle : imagepath );
RtlInitUnicodeString( &desktopW, startup->lpDesktop );
runtimeW.Buffer = (WCHAR *)startup->lpReserved2;
runtimeW.Length = runtimeW.MaximumLength = startup->cbReserved2;
if (RtlCreateProcessParametersEx( &params, &imageW, &dllpathW, cur_dir ? &curdirW : NULL,
&cmdlineW, envW, &titleW, &desktopW,
NULL, &runtimeW, PROCESS_PARAMS_FLAG_NORMALIZED ))
{
RtlReleasePath( load_path );
if (envW != env) HeapFree( GetProcessHeap(), 0, envW );
return NULL;
}
RtlReleasePath( load_path );
if (flags & CREATE_NEW_PROCESS_GROUP) params->ConsoleFlags = 1;
if (flags & CREATE_NEW_CONSOLE) params->ConsoleHandle = KERNEL32_CONSOLE_ALLOC;
if (startup->dwFlags & STARTF_USESTDHANDLES)
{
params->hStdInput = startup->hStdInput;
params->hStdOutput = startup->hStdOutput;
params->hStdError = startup->hStdError;
}
else if (flags & DETACHED_PROCESS)
{
params->hStdInput = INVALID_HANDLE_VALUE;
params->hStdOutput = INVALID_HANDLE_VALUE;
params->hStdError = INVALID_HANDLE_VALUE;
}
else
{
params->hStdInput = NtCurrentTeb()->Peb->ProcessParameters->hStdInput;
params->hStdOutput = NtCurrentTeb()->Peb->ProcessParameters->hStdOutput;
params->hStdError = NtCurrentTeb()->Peb->ProcessParameters->hStdError;
}
if (flags & CREATE_NEW_CONSOLE)
{
/* this is temporary (for console handles). We have no way to control that the handle is invalid in child process otherwise */
if (is_console_handle(params->hStdInput)) params->hStdInput = INVALID_HANDLE_VALUE;
if (is_console_handle(params->hStdOutput)) params->hStdOutput = INVALID_HANDLE_VALUE;
if (is_console_handle(params->hStdError)) params->hStdError = INVALID_HANDLE_VALUE;
}
else
{
if (is_console_handle(params->hStdInput)) params->hStdInput = (HANDLE)((UINT_PTR)params->hStdInput & ~3);
if (is_console_handle(params->hStdOutput)) params->hStdOutput = (HANDLE)((UINT_PTR)params->hStdOutput & ~3);
if (is_console_handle(params->hStdError)) params->hStdError = (HANDLE)((UINT_PTR)params->hStdError & ~3);
}
params->dwX = startup->dwX;
params->dwY = startup->dwY;
params->dwXSize = startup->dwXSize;
params->dwYSize = startup->dwYSize;
params->dwXCountChars = startup->dwXCountChars;
params->dwYCountChars = startup->dwYCountChars;
params->dwFillAttribute = startup->dwFillAttribute;
params->dwFlags = startup->dwFlags;
params->wShowWindow = startup->wShowWindow;
if (envW != env) HeapFree( GetProcessHeap(), 0, envW );
return params;
}
/***********************************************************************
* create_nt_process
*/
static NTSTATUS create_nt_process( LPSECURITY_ATTRIBUTES psa, LPSECURITY_ATTRIBUTES tsa,
BOOL inherit, DWORD flags, RTL_USER_PROCESS_PARAMETERS *params,
RTL_USER_PROCESS_INFORMATION *info )
{
NTSTATUS status;
UNICODE_STRING nameW;
if (!params->ImagePathName.Buffer[0]) return STATUS_OBJECT_PATH_NOT_FOUND;
status = RtlDosPathNameToNtPathName_U_WithStatus( params->ImagePathName.Buffer, &nameW, NULL, NULL );
if (!status)
{
params->DebugFlags = flags; /* hack, cf. RtlCreateUserProcess implementation */
status = RtlCreateUserProcess( &nameW, OBJ_CASE_INSENSITIVE, params,
psa ? psa->lpSecurityDescriptor : NULL,
tsa ? tsa->lpSecurityDescriptor : NULL,
0, inherit, 0, 0, info );
RtlFreeUnicodeString( &nameW );
}
return status;
}
/***********************************************************************
* create_vdm_process
*
* Create a new VDM process for a 16-bit or DOS application.
*/
static NTSTATUS create_vdm_process( LPSECURITY_ATTRIBUTES psa, LPSECURITY_ATTRIBUTES tsa,
BOOL inherit, DWORD flags, RTL_USER_PROCESS_PARAMETERS *params,
RTL_USER_PROCESS_INFORMATION *info )
{
static const WCHAR argsW[] = {'%','s',' ','-','-','a','p','p','-','n','a','m','e',' ','"','%','s','"',' ','%','s',0};
NTSTATUS status;
WCHAR *new_cmd_line;
new_cmd_line = HeapAlloc(GetProcessHeap(), 0,
(strlenW(params->ImagePathName.Buffer) +
strlenW(params->CommandLine.Buffer) +
strlenW(winevdm) + 16) * sizeof(WCHAR));
if (!new_cmd_line) return STATUS_NO_MEMORY;
sprintfW( new_cmd_line, argsW, winevdm, params->ImagePathName.Buffer, params->CommandLine.Buffer );
RtlInitUnicodeString( &params->ImagePathName, winevdm );
RtlInitUnicodeString( &params->CommandLine, new_cmd_line );
status = create_nt_process( psa, tsa, inherit, flags, params, info );
HeapFree( GetProcessHeap(), 0, new_cmd_line );
return status;
}
/***********************************************************************
* create_cmd_process
*
* Create a new cmd shell process for a .BAT file.
*/
static NTSTATUS create_cmd_process( LPSECURITY_ATTRIBUTES psa, LPSECURITY_ATTRIBUTES tsa,
BOOL inherit, DWORD flags, RTL_USER_PROCESS_PARAMETERS *params,
RTL_USER_PROCESS_INFORMATION *info )
{
static const WCHAR argsW[] = {'%','s',' ','/','s','/','c',' ','"','%','s','"',0};
static const WCHAR comspecW[] = {'C','O','M','S','P','E','C',0};
static const WCHAR cmdW[] = {'\\','c','m','d','.','e','x','e',0};
WCHAR comspec[MAX_PATH];
WCHAR *newcmdline;
NTSTATUS status;
if (!GetEnvironmentVariableW( comspecW, comspec, ARRAY_SIZE( comspec )))
{
GetSystemDirectoryW( comspec, ARRAY_SIZE( comspec ) - ARRAY_SIZE( cmdW ));
strcatW( comspec, cmdW );
}
if (!(newcmdline = HeapAlloc( GetProcessHeap(), 0,
(strlenW(comspec) + 7 +
strlenW(params->CommandLine.Buffer) + 2) * sizeof(WCHAR))))
return STATUS_NO_MEMORY;
sprintfW( newcmdline, argsW, comspec, params->CommandLine.Buffer );
RtlInitUnicodeString( &params->ImagePathName, comspec );
RtlInitUnicodeString( &params->CommandLine, newcmdline );
status = create_nt_process( psa, tsa, inherit, flags, params, info );
HeapFree( GetProcessHeap(), 0, newcmdline );
return status;
}
/*************************************************************************
* get_file_name
*
* Helper for CreateProcess: retrieve the file name to load from the
* app name and command line. Store the file name in buffer, and
* return a possibly modified command line.
*/
static LPWSTR get_file_name( LPWSTR cmdline, LPWSTR buffer, int buflen )
{
WCHAR *name, *pos, *first_space, *ret = NULL;
const WCHAR *p;
/* first check for a quoted file name */
if ((cmdline[0] == '"') && ((p = strchrW( cmdline + 1, '"' ))))
{
int len = p - cmdline - 1;
/* extract the quoted portion as file name */
if (!(name = HeapAlloc( GetProcessHeap(), 0, (len + 1) * sizeof(WCHAR) ))) return NULL;
memcpy( name, cmdline + 1, len * sizeof(WCHAR) );
name[len] = 0;
if (!find_exe_file( name, buffer, buflen )) goto done;
ret = cmdline; /* no change necessary */
goto done;
}
/* now try the command-line word by word */
if (!(name = HeapAlloc( GetProcessHeap(), 0, (strlenW(cmdline) + 1) * sizeof(WCHAR) )))
return NULL;
pos = name;
p = cmdline;
first_space = NULL;
for (;;)
{
while (*p && *p != ' ' && *p != '\t') *pos++ = *p++;
*pos = 0;
if (find_exe_file( name, buffer, buflen ))
{
ret = cmdline;
break;
}
if (!first_space) first_space = pos;
if (!(*pos++ = *p++)) break;
}
if (!ret)
{
SetLastError( ERROR_FILE_NOT_FOUND );
}
else if (first_space) /* build a new command-line with quotes */
{
static const WCHAR quotesW[] = {'"','%','s','"','%','s',0};
if (!(ret = HeapAlloc( GetProcessHeap(), 0, (strlenW(cmdline) + 3) * sizeof(WCHAR) )))
goto done;
sprintfW( ret, quotesW, name, p );
}
done:
HeapFree( GetProcessHeap(), 0, name );
return ret;
}
/**********************************************************************
* CreateProcessInternalW (KERNEL32.@)
*/
BOOL WINAPI CreateProcessInternalW( HANDLE token, LPCWSTR app_name, LPWSTR cmd_line,
LPSECURITY_ATTRIBUTES process_attr, LPSECURITY_ATTRIBUTES thread_attr,
BOOL inherit, DWORD flags, LPVOID env, LPCWSTR cur_dir,
LPSTARTUPINFOW startup_info, LPPROCESS_INFORMATION info,
HANDLE *new_token )
{
WCHAR name[MAX_PATH];
WCHAR *p, *tidy_cmdline = cmd_line;
RTL_USER_PROCESS_PARAMETERS *params = NULL;
RTL_USER_PROCESS_INFORMATION rtl_info;
NTSTATUS status;
/* Process the AppName and/or CmdLine to get module name and path */
TRACE("app %s cmdline %s\n", debugstr_w(app_name), debugstr_w(cmd_line) );
if (token) FIXME("Creating a process with a token is not yet implemented\n");
if (new_token) FIXME("No support for returning created process token\n");
if (app_name)
{
if (!cmd_line || !cmd_line[0]) /* no command-line, create one */
{
static const WCHAR quotesW[] = {'"','%','s','"',0};
if (!(tidy_cmdline = HeapAlloc( GetProcessHeap(), 0, (strlenW(app_name)+3) * sizeof(WCHAR) )))
return FALSE;
sprintfW( tidy_cmdline, quotesW, app_name );
}
}
else
{
if (!(tidy_cmdline = get_file_name( cmd_line, name, ARRAY_SIZE(name) ))) return FALSE;
app_name = name;
}
/* Warn if unsupported features are used */
if (flags & (IDLE_PRIORITY_CLASS | HIGH_PRIORITY_CLASS | REALTIME_PRIORITY_CLASS |
CREATE_DEFAULT_ERROR_MODE | CREATE_NO_WINDOW |
PROFILE_USER | PROFILE_KERNEL | PROFILE_SERVER))
WARN("(%s,...): ignoring some flags in %x\n", debugstr_w(app_name), flags);
if (cur_dir)
{
DWORD attr = GetFileAttributesW( cur_dir );
if (attr == INVALID_FILE_ATTRIBUTES || !(attr & FILE_ATTRIBUTE_DIRECTORY))
{
status = STATUS_NOT_A_DIRECTORY;
goto done;
}
}
info->hThread = info->hProcess = 0;
info->dwProcessId = info->dwThreadId = 0;
if (!(params = create_process_params( app_name, tidy_cmdline, cur_dir, env, flags, startup_info )))
{
status = STATUS_NO_MEMORY;
goto done;
}
status = create_nt_process( process_attr, thread_attr, inherit, flags, params, &rtl_info );
switch (status)
{
case STATUS_SUCCESS:
break;
case STATUS_INVALID_IMAGE_WIN_16:
case STATUS_INVALID_IMAGE_NE_FORMAT:
case STATUS_INVALID_IMAGE_PROTECT:
TRACE( "starting %s as Win16/DOS binary\n", debugstr_w(app_name) );
status = create_vdm_process( process_attr, thread_attr, inherit, flags, params, &rtl_info );
break;
case STATUS_INVALID_IMAGE_NOT_MZ:
/* check for .com or .bat extension */
if (!(p = strrchrW( app_name, '.' ))) break;
if (!strcmpiW( p, comW ) || !strcmpiW( p, pifW ))
{
TRACE( "starting %s as DOS binary\n", debugstr_w(app_name) );
status = create_vdm_process( process_attr, thread_attr, inherit, flags, params, &rtl_info );
}
else if (!strcmpiW( p, batW ) || !strcmpiW( p, cmdW ))
{
TRACE( "starting %s as batch binary\n", debugstr_w(app_name) );
status = create_cmd_process( process_attr, thread_attr, inherit, flags, params, &rtl_info );
}
break;
}
if (!status)
{
info->hProcess = rtl_info.Process;
info->hThread = rtl_info.Thread;
info->dwProcessId = HandleToUlong( rtl_info.ClientId.UniqueProcess );
info->dwThreadId = HandleToUlong( rtl_info.ClientId.UniqueThread );
if (!(flags & CREATE_SUSPENDED)) NtResumeThread( rtl_info.Thread, NULL );
TRACE( "started process pid %04x tid %04x\n", info->dwProcessId, info->dwThreadId );
}
done:
RtlDestroyProcessParameters( params );
if (tidy_cmdline != cmd_line) HeapFree( GetProcessHeap(), 0, tidy_cmdline );
return set_ntstatus( status );
}
/**********************************************************************
* CreateProcessInternalA (KERNEL32.@)
*/
BOOL WINAPI CreateProcessInternalA( HANDLE token, LPCSTR app_name, LPSTR cmd_line,
LPSECURITY_ATTRIBUTES process_attr, LPSECURITY_ATTRIBUTES thread_attr,
BOOL inherit, DWORD flags, LPVOID env, LPCSTR cur_dir,
LPSTARTUPINFOA startup_info, LPPROCESS_INFORMATION info,
HANDLE *new_token )
{
BOOL ret = FALSE;
WCHAR *app_nameW = NULL, *cmd_lineW = NULL, *cur_dirW = NULL;
UNICODE_STRING desktopW, titleW;
STARTUPINFOW infoW;
desktopW.Buffer = NULL;
titleW.Buffer = NULL;
if (app_name && !(app_nameW = FILE_name_AtoW( app_name, TRUE ))) goto done;
if (cmd_line && !(cmd_lineW = FILE_name_AtoW( cmd_line, TRUE ))) goto done;
if (cur_dir && !(cur_dirW = FILE_name_AtoW( cur_dir, TRUE ))) goto done;
if (startup_info->lpDesktop) RtlCreateUnicodeStringFromAsciiz( &desktopW, startup_info->lpDesktop );
if (startup_info->lpTitle) RtlCreateUnicodeStringFromAsciiz( &titleW, startup_info->lpTitle );
memcpy( &infoW, startup_info, sizeof(infoW) );
infoW.lpDesktop = desktopW.Buffer;
infoW.lpTitle = titleW.Buffer;
if (startup_info->lpReserved)
FIXME("StartupInfo.lpReserved is used, please report (%s)\n",
debugstr_a(startup_info->lpReserved));
ret = CreateProcessInternalW( token, app_nameW, cmd_lineW, process_attr, thread_attr,
inherit, flags, env, cur_dirW, &infoW, info, new_token );
done:
HeapFree( GetProcessHeap(), 0, app_nameW );
HeapFree( GetProcessHeap(), 0, cmd_lineW );
HeapFree( GetProcessHeap(), 0, cur_dirW );
RtlFreeUnicodeString( &desktopW );
RtlFreeUnicodeString( &titleW );
return ret;
}
/**********************************************************************
* CreateProcessA (KERNEL32.@)
*/
BOOL WINAPI DECLSPEC_HOTPATCH CreateProcessA( LPCSTR app_name, LPSTR cmd_line, LPSECURITY_ATTRIBUTES process_attr,
LPSECURITY_ATTRIBUTES thread_attr, BOOL inherit,
DWORD flags, LPVOID env, LPCSTR cur_dir,
LPSTARTUPINFOA startup_info, LPPROCESS_INFORMATION info )
{
return CreateProcessInternalA( NULL, app_name, cmd_line, process_attr, thread_attr,
inherit, flags, env, cur_dir, startup_info, info, NULL );
}
/**********************************************************************
* CreateProcessW (KERNEL32.@)
*/
BOOL WINAPI DECLSPEC_HOTPATCH CreateProcessW( LPCWSTR app_name, LPWSTR cmd_line, LPSECURITY_ATTRIBUTES process_attr,
LPSECURITY_ATTRIBUTES thread_attr, BOOL inherit, DWORD flags,
LPVOID env, LPCWSTR cur_dir, LPSTARTUPINFOW startup_info,
LPPROCESS_INFORMATION info )
{
return CreateProcessInternalW( NULL, app_name, cmd_line, process_attr, thread_attr,
inherit, flags, env, cur_dir, startup_info, info, NULL );
}
/**********************************************************************
* CreateProcessAsUserA (KERNEL32.@)
*/
BOOL WINAPI DECLSPEC_HOTPATCH CreateProcessAsUserA( HANDLE token, LPCSTR app_name, LPSTR cmd_line,
LPSECURITY_ATTRIBUTES process_attr,
LPSECURITY_ATTRIBUTES thread_attr,
BOOL inherit, DWORD flags, LPVOID env, LPCSTR cur_dir,
LPSTARTUPINFOA startup_info,
LPPROCESS_INFORMATION info )
{
return CreateProcessInternalA( token, app_name, cmd_line, process_attr, thread_attr,
inherit, flags, env, cur_dir, startup_info, info, NULL );
}
/**********************************************************************
* CreateProcessAsUserW (KERNEL32.@)
*/
BOOL WINAPI DECLSPEC_HOTPATCH CreateProcessAsUserW( HANDLE token, LPCWSTR app_name, LPWSTR cmd_line,
LPSECURITY_ATTRIBUTES process_attr,
LPSECURITY_ATTRIBUTES thread_attr,
BOOL inherit, DWORD flags, LPVOID env, LPCWSTR cur_dir,
LPSTARTUPINFOW startup_info,
LPPROCESS_INFORMATION info )
{
return CreateProcessInternalW( token, app_name, cmd_line, process_attr, thread_attr,
inherit, flags, env, cur_dir, startup_info, info, NULL );
}
/***********************************************************************
* wait_input_idle
*
* Wrapper to call WaitForInputIdle USER function
......
......@@ -205,12 +205,12 @@
@ stdcall CreatePrivateObjectSecurity(ptr ptr ptr long long ptr)
@ stdcall CreatePrivateObjectSecurityEx(ptr ptr ptr ptr long long long ptr)
@ stdcall CreatePrivateObjectSecurityWithMultipleInheritance(ptr ptr ptr ptr long long long long ptr)
@ stdcall CreateProcessA(str str ptr ptr long long ptr str ptr ptr) kernel32.CreateProcessA
@ stdcall CreateProcessAsUserA(long str str ptr ptr long long ptr str ptr ptr) kernel32.CreateProcessAsUserA
@ stdcall CreateProcessAsUserW(long wstr wstr ptr ptr long long ptr wstr ptr ptr) kernel32.CreateProcessAsUserW
@ stdcall CreateProcessInternalA(long str str ptr ptr long long ptr str ptr ptr ptr) kernel32.CreateProcessInternalA
@ stdcall CreateProcessInternalW(long wstr wstr ptr ptr long long ptr wstr ptr ptr ptr) kernel32.CreateProcessInternalW
@ stdcall CreateProcessW(wstr wstr ptr ptr long long ptr wstr ptr ptr) kernel32.CreateProcessW
@ stdcall CreateProcessA(str str ptr ptr long long ptr str ptr ptr)
@ stdcall CreateProcessAsUserA(long str str ptr ptr long long ptr str ptr ptr)
@ stdcall CreateProcessAsUserW(long wstr wstr ptr ptr long long ptr wstr ptr ptr)
@ stdcall CreateProcessInternalA(long str str ptr ptr long long ptr str ptr ptr ptr)
@ stdcall CreateProcessInternalW(long wstr wstr ptr ptr long long ptr wstr ptr ptr ptr)
@ stdcall CreateProcessW(wstr wstr ptr ptr long long ptr wstr ptr ptr)
@ stdcall CreateRemoteThread(long ptr long ptr long long ptr)
@ stdcall CreateRemoteThreadEx(long ptr long ptr ptr long ptr ptr)
@ stdcall CreateRestrictedToken(long long long ptr long ptr long ptr ptr)
......
......@@ -42,6 +42,279 @@ static DWORD shutdown_priority = 0x280;
***********************************************************************/
/***********************************************************************
* find_exe_file
*/
static BOOL find_exe_file( const WCHAR *name, WCHAR *buffer, DWORD buflen )
{
WCHAR *load_path;
BOOL ret;
if (!set_ntstatus( RtlGetExePath( name, &load_path ))) return FALSE;
TRACE( "looking for %s in %s\n", debugstr_w(name), debugstr_w(load_path) );
ret = (SearchPathW( load_path, name, L".exe", buflen, buffer, NULL ) ||
/* not found, try without extension in case it is a Unix app */
SearchPathW( load_path, name, NULL, buflen, buffer, NULL ));
RtlReleasePath( load_path );
return ret;
}
/*************************************************************************
* get_file_name
*
* Helper for CreateProcess: retrieve the file name to load from the
* app name and command line. Store the file name in buffer, and
* return a possibly modified command line.
*/
static WCHAR *get_file_name( WCHAR *cmdline, WCHAR *buffer, DWORD buflen )
{
WCHAR *name, *pos, *first_space, *ret = NULL;
const WCHAR *p;
/* first check for a quoted file name */
if (cmdline[0] == '"' && (p = wcschr( cmdline + 1, '"' )))
{
int len = p - cmdline - 1;
/* extract the quoted portion as file name */
if (!(name = RtlAllocateHeap( GetProcessHeap(), 0, (len + 1) * sizeof(WCHAR) ))) return NULL;
memcpy( name, cmdline + 1, len * sizeof(WCHAR) );
name[len] = 0;
if (!find_exe_file( name, buffer, buflen )) goto done;
ret = cmdline; /* no change necessary */
goto done;
}
/* now try the command-line word by word */
if (!(name = RtlAllocateHeap( GetProcessHeap(), 0, (lstrlenW(cmdline) + 1) * sizeof(WCHAR) )))
return NULL;
pos = name;
p = cmdline;
first_space = NULL;
for (;;)
{
while (*p && *p != ' ' && *p != '\t') *pos++ = *p++;
*pos = 0;
if (find_exe_file( name, buffer, buflen ))
{
ret = cmdline;
break;
}
if (!first_space) first_space = pos;
if (!(*pos++ = *p++)) break;
}
if (!ret)
{
SetLastError( ERROR_FILE_NOT_FOUND );
}
else if (first_space) /* build a new command-line with quotes */
{
if (!(ret = HeapAlloc( GetProcessHeap(), 0, (lstrlenW(cmdline) + 3) * sizeof(WCHAR) )))
goto done;
swprintf( ret, lstrlenW(cmdline) + 3, L"\"%s\"%s", name, p );
}
done:
RtlFreeHeap( GetProcessHeap(), 0, name );
return ret;
}
/***********************************************************************
* create_process_params
*/
static RTL_USER_PROCESS_PARAMETERS *create_process_params( const WCHAR *filename, const WCHAR *cmdline,
const WCHAR *cur_dir, void *env, DWORD flags,
const STARTUPINFOW *startup )
{
RTL_USER_PROCESS_PARAMETERS *params;
UNICODE_STRING imageW, dllpathW, curdirW, cmdlineW, titleW, desktopW, runtimeW, newdirW;
WCHAR imagepath[MAX_PATH];
WCHAR *load_path, *dummy, *envW = env;
if (!GetLongPathNameW( filename, imagepath, MAX_PATH )) lstrcpynW( imagepath, filename, MAX_PATH );
if (!GetFullPathNameW( imagepath, MAX_PATH, imagepath, NULL )) lstrcpynW( imagepath, filename, MAX_PATH );
if (env && !(flags & CREATE_UNICODE_ENVIRONMENT)) /* convert environment to unicode */
{
char *e = env;
DWORD lenW;
while (*e) e += strlen(e) + 1;
e++; /* final null */
lenW = MultiByteToWideChar( CP_ACP, 0, env, e - (char *)env, NULL, 0 );
if ((envW = RtlAllocateHeap( GetProcessHeap(), 0, lenW * sizeof(WCHAR) )))
MultiByteToWideChar( CP_ACP, 0, env, e - (char *)env, envW, lenW );
}
newdirW.Buffer = NULL;
if (cur_dir)
{
if (RtlDosPathNameToNtPathName_U( cur_dir, &newdirW, NULL, NULL ))
cur_dir = newdirW.Buffer + 4; /* skip \??\ prefix */
else
cur_dir = NULL;
}
LdrGetDllPath( imagepath, LOAD_WITH_ALTERED_SEARCH_PATH, &load_path, &dummy );
RtlInitUnicodeString( &imageW, imagepath );
RtlInitUnicodeString( &dllpathW, load_path );
RtlInitUnicodeString( &curdirW, cur_dir );
RtlInitUnicodeString( &cmdlineW, cmdline );
RtlInitUnicodeString( &titleW, startup->lpTitle ? startup->lpTitle : imagepath );
RtlInitUnicodeString( &desktopW, startup->lpDesktop );
runtimeW.Buffer = (WCHAR *)startup->lpReserved2;
runtimeW.Length = runtimeW.MaximumLength = startup->cbReserved2;
if (RtlCreateProcessParametersEx( &params, &imageW, &dllpathW, cur_dir ? &curdirW : NULL,
&cmdlineW, envW, &titleW, &desktopW,
NULL, &runtimeW, PROCESS_PARAMS_FLAG_NORMALIZED ))
{
RtlReleasePath( load_path );
if (envW != env) RtlFreeHeap( GetProcessHeap(), 0, envW );
return NULL;
}
RtlReleasePath( load_path );
if (flags & CREATE_NEW_PROCESS_GROUP) params->ConsoleFlags = 1;
if (flags & CREATE_NEW_CONSOLE) params->ConsoleHandle = (HANDLE)1; /* KERNEL32_CONSOLE_ALLOC */
if (startup->dwFlags & STARTF_USESTDHANDLES)
{
params->hStdInput = startup->hStdInput;
params->hStdOutput = startup->hStdOutput;
params->hStdError = startup->hStdError;
}
else if (flags & DETACHED_PROCESS)
{
params->hStdInput = INVALID_HANDLE_VALUE;
params->hStdOutput = INVALID_HANDLE_VALUE;
params->hStdError = INVALID_HANDLE_VALUE;
}
else
{
params->hStdInput = NtCurrentTeb()->Peb->ProcessParameters->hStdInput;
params->hStdOutput = NtCurrentTeb()->Peb->ProcessParameters->hStdOutput;
params->hStdError = NtCurrentTeb()->Peb->ProcessParameters->hStdError;
}
if (flags & CREATE_NEW_CONSOLE)
{
/* this is temporary (for console handles). We have no way to control that the handle is invalid in child process otherwise */
if (is_console_handle(params->hStdInput)) params->hStdInput = INVALID_HANDLE_VALUE;
if (is_console_handle(params->hStdOutput)) params->hStdOutput = INVALID_HANDLE_VALUE;
if (is_console_handle(params->hStdError)) params->hStdError = INVALID_HANDLE_VALUE;
}
else
{
if (is_console_handle(params->hStdInput)) params->hStdInput = (HANDLE)((UINT_PTR)params->hStdInput & ~3);
if (is_console_handle(params->hStdOutput)) params->hStdOutput = (HANDLE)((UINT_PTR)params->hStdOutput & ~3);
if (is_console_handle(params->hStdError)) params->hStdError = (HANDLE)((UINT_PTR)params->hStdError & ~3);
}
params->dwX = startup->dwX;
params->dwY = startup->dwY;
params->dwXSize = startup->dwXSize;
params->dwYSize = startup->dwYSize;
params->dwXCountChars = startup->dwXCountChars;
params->dwYCountChars = startup->dwYCountChars;
params->dwFillAttribute = startup->dwFillAttribute;
params->dwFlags = startup->dwFlags;
params->wShowWindow = startup->wShowWindow;
if (envW != env) RtlFreeHeap( GetProcessHeap(), 0, envW );
return params;
}
/***********************************************************************
* create_nt_process
*/
static NTSTATUS create_nt_process( SECURITY_ATTRIBUTES *psa, SECURITY_ATTRIBUTES *tsa,
BOOL inherit, DWORD flags, RTL_USER_PROCESS_PARAMETERS *params,
RTL_USER_PROCESS_INFORMATION *info )
{
NTSTATUS status;
UNICODE_STRING nameW;
if (!params->ImagePathName.Buffer[0]) return STATUS_OBJECT_PATH_NOT_FOUND;
status = RtlDosPathNameToNtPathName_U_WithStatus( params->ImagePathName.Buffer, &nameW, NULL, NULL );
if (!status)
{
params->DebugFlags = flags; /* hack, cf. RtlCreateUserProcess implementation */
status = RtlCreateUserProcess( &nameW, OBJ_CASE_INSENSITIVE, params,
psa ? psa->lpSecurityDescriptor : NULL,
tsa ? tsa->lpSecurityDescriptor : NULL,
0, inherit, 0, 0, info );
RtlFreeUnicodeString( &nameW );
}
return status;
}
/***********************************************************************
* create_vdm_process
*/
static NTSTATUS create_vdm_process( SECURITY_ATTRIBUTES *psa, SECURITY_ATTRIBUTES *tsa,
BOOL inherit, DWORD flags, RTL_USER_PROCESS_PARAMETERS *params,
RTL_USER_PROCESS_INFORMATION *info )
{
const WCHAR *winevdm = (is_win64 || is_wow64 ?
L"C:\\windows\\syswow64\\winevdm.exe" :
L"C:\\windows\\system32\\winevdm.exe");
WCHAR *newcmdline;
NTSTATUS status;
UINT len;
len = (lstrlenW(params->ImagePathName.Buffer) + lstrlenW(params->CommandLine.Buffer) +
lstrlenW(winevdm) + 16);
if (!(newcmdline = RtlAllocateHeap( GetProcessHeap(), 0, len * sizeof(WCHAR) )))
return STATUS_NO_MEMORY;
swprintf( newcmdline, len, L"%s --app-name \"%s\" %s",
winevdm, params->ImagePathName.Buffer, params->CommandLine.Buffer );
RtlInitUnicodeString( &params->ImagePathName, winevdm );
RtlInitUnicodeString( &params->CommandLine, newcmdline );
status = create_nt_process( psa, tsa, inherit, flags, params, info );
HeapFree( GetProcessHeap(), 0, newcmdline );
return status;
}
/***********************************************************************
* create_cmd_process
*/
static NTSTATUS create_cmd_process( SECURITY_ATTRIBUTES *psa, SECURITY_ATTRIBUTES *tsa,
BOOL inherit, DWORD flags, RTL_USER_PROCESS_PARAMETERS *params,
RTL_USER_PROCESS_INFORMATION *info )
{
WCHAR comspec[MAX_PATH];
WCHAR *newcmdline;
NTSTATUS status;
UINT len;
if (!GetEnvironmentVariableW( L"COMSPEC", comspec, ARRAY_SIZE( comspec )))
lstrcpyW( comspec, L"C:\\windows\\system32\\cmd.exe" );
len = lstrlenW(comspec) + 7 + lstrlenW(params->CommandLine.Buffer) + 2;
if (!(newcmdline = RtlAllocateHeap( GetProcessHeap(), 0, len * sizeof(WCHAR) )))
return STATUS_NO_MEMORY;
swprintf( newcmdline, len, L"%s /s/c \"%s\"", comspec, params->CommandLine.Buffer );
RtlInitUnicodeString( &params->ImagePathName, comspec );
RtlInitUnicodeString( &params->CommandLine, newcmdline );
status = create_nt_process( psa, tsa, inherit, flags, params, info );
RtlFreeHeap( GetProcessHeap(), 0, newcmdline );
return status;
}
/*********************************************************************
* CloseHandle (kernelbase.@)
*/
......@@ -59,6 +332,212 @@ BOOL WINAPI DECLSPEC_HOTPATCH CloseHandle( HANDLE handle )
}
/**********************************************************************
* CreateProcessAsUserA (kernelbase.@)
*/
BOOL WINAPI DECLSPEC_HOTPATCH CreateProcessAsUserA( HANDLE token, const char *app_name, char *cmd_line,
SECURITY_ATTRIBUTES *process_attr,
SECURITY_ATTRIBUTES *thread_attr,
BOOL inherit, DWORD flags, void *env,
const char *cur_dir, STARTUPINFOA *startup_info,
PROCESS_INFORMATION *info )
{
return CreateProcessInternalA( token, app_name, cmd_line, process_attr, thread_attr,
inherit, flags, env, cur_dir, startup_info, info, NULL );
}
/**********************************************************************
* CreateProcessAsUserW (kernelbase.@)
*/
BOOL WINAPI DECLSPEC_HOTPATCH CreateProcessAsUserW( HANDLE token, const WCHAR *app_name, WCHAR *cmd_line,
SECURITY_ATTRIBUTES *process_attr,
SECURITY_ATTRIBUTES *thread_attr,
BOOL inherit, DWORD flags, void *env,
const WCHAR *cur_dir, STARTUPINFOW *startup_info,
PROCESS_INFORMATION *info )
{
return CreateProcessInternalW( token, app_name, cmd_line, process_attr, thread_attr,
inherit, flags, env, cur_dir, startup_info, info, NULL );
}
/**********************************************************************
* CreateProcessInternalA (kernelbase.@)
*/
BOOL WINAPI DECLSPEC_HOTPATCH CreateProcessInternalA( HANDLE token, const char *app_name, char *cmd_line,
SECURITY_ATTRIBUTES *process_attr,
SECURITY_ATTRIBUTES *thread_attr,
BOOL inherit, DWORD flags, void *env,
const char *cur_dir, STARTUPINFOA *startup_info,
PROCESS_INFORMATION *info, HANDLE *new_token )
{
BOOL ret = FALSE;
WCHAR *app_nameW = NULL, *cmd_lineW = NULL, *cur_dirW = NULL;
UNICODE_STRING desktopW, titleW;
STARTUPINFOW infoW;
desktopW.Buffer = NULL;
titleW.Buffer = NULL;
if (app_name && !(app_nameW = file_name_AtoW( app_name, TRUE ))) goto done;
if (cmd_line && !(cmd_lineW = file_name_AtoW( cmd_line, TRUE ))) goto done;
if (cur_dir && !(cur_dirW = file_name_AtoW( cur_dir, TRUE ))) goto done;
if (startup_info->lpDesktop) RtlCreateUnicodeStringFromAsciiz( &desktopW, startup_info->lpDesktop );
if (startup_info->lpTitle) RtlCreateUnicodeStringFromAsciiz( &titleW, startup_info->lpTitle );
memcpy( &infoW, startup_info, sizeof(infoW) );
infoW.lpDesktop = desktopW.Buffer;
infoW.lpTitle = titleW.Buffer;
ret = CreateProcessInternalW( token, app_nameW, cmd_lineW, process_attr, thread_attr,
inherit, flags, env, cur_dirW, &infoW, info, new_token );
done:
RtlFreeHeap( GetProcessHeap(), 0, app_nameW );
RtlFreeHeap( GetProcessHeap(), 0, cmd_lineW );
RtlFreeHeap( GetProcessHeap(), 0, cur_dirW );
RtlFreeUnicodeString( &desktopW );
RtlFreeUnicodeString( &titleW );
return ret;
}
/**********************************************************************
* CreateProcessInternalW (kernelbase.@)
*/
BOOL WINAPI DECLSPEC_HOTPATCH CreateProcessInternalW( HANDLE token, const WCHAR *app_name, WCHAR *cmd_line,
SECURITY_ATTRIBUTES *process_attr,
SECURITY_ATTRIBUTES *thread_attr,
BOOL inherit, DWORD flags, void *env,
const WCHAR *cur_dir, STARTUPINFOW *startup_info,
PROCESS_INFORMATION *info, HANDLE *new_token )
{
WCHAR name[MAX_PATH];
WCHAR *p, *tidy_cmdline = cmd_line;
RTL_USER_PROCESS_PARAMETERS *params = NULL;
RTL_USER_PROCESS_INFORMATION rtl_info;
NTSTATUS status;
/* Process the AppName and/or CmdLine to get module name and path */
TRACE( "app %s cmdline %s\n", debugstr_w(app_name), debugstr_w(cmd_line) );
if (token) FIXME( "Creating a process with a token is not yet implemented\n" );
if (new_token) FIXME( "No support for returning created process token\n" );
if (app_name)
{
if (!cmd_line || !cmd_line[0]) /* no command-line, create one */
{
if (!(tidy_cmdline = RtlAllocateHeap( GetProcessHeap(), 0, (lstrlenW(app_name)+3) * sizeof(WCHAR) )))
return FALSE;
swprintf( tidy_cmdline, lstrlenW(app_name) + 3, L"\"%s\"", app_name );
}
}
else
{
if (!(tidy_cmdline = get_file_name( cmd_line, name, ARRAY_SIZE(name) ))) return FALSE;
app_name = name;
}
/* Warn if unsupported features are used */
if (flags & (IDLE_PRIORITY_CLASS | HIGH_PRIORITY_CLASS | REALTIME_PRIORITY_CLASS |
CREATE_DEFAULT_ERROR_MODE | CREATE_NO_WINDOW |
PROFILE_USER | PROFILE_KERNEL | PROFILE_SERVER))
WARN( "(%s,...): ignoring some flags in %x\n", debugstr_w(app_name), flags );
if (cur_dir)
{
DWORD attr = GetFileAttributesW( cur_dir );
if (attr == INVALID_FILE_ATTRIBUTES || !(attr & FILE_ATTRIBUTE_DIRECTORY))
{
status = STATUS_NOT_A_DIRECTORY;
goto done;
}
}
info->hThread = info->hProcess = 0;
info->dwProcessId = info->dwThreadId = 0;
if (!(params = create_process_params( app_name, tidy_cmdline, cur_dir, env, flags, startup_info )))
{
status = STATUS_NO_MEMORY;
goto done;
}
status = create_nt_process( process_attr, thread_attr, inherit, flags, params, &rtl_info );
switch (status)
{
case STATUS_SUCCESS:
break;
case STATUS_INVALID_IMAGE_WIN_16:
case STATUS_INVALID_IMAGE_NE_FORMAT:
case STATUS_INVALID_IMAGE_PROTECT:
TRACE( "starting %s as Win16/DOS binary\n", debugstr_w(app_name) );
status = create_vdm_process( process_attr, thread_attr, inherit, flags, params, &rtl_info );
break;
case STATUS_INVALID_IMAGE_NOT_MZ:
/* check for .com or .bat extension */
if (!(p = wcsrchr( app_name, '.' ))) break;
if (!wcsicmp( p, L".com" ) || !wcsicmp( p, L".pif" ))
{
TRACE( "starting %s as DOS binary\n", debugstr_w(app_name) );
status = create_vdm_process( process_attr, thread_attr, inherit, flags, params, &rtl_info );
}
else if (!wcsicmp( p, L".bat" ) || !wcsicmp( p, L".cmd" ))
{
TRACE( "starting %s as batch binary\n", debugstr_w(app_name) );
status = create_cmd_process( process_attr, thread_attr, inherit, flags, params, &rtl_info );
}
break;
}
if (!status)
{
info->hProcess = rtl_info.Process;
info->hThread = rtl_info.Thread;
info->dwProcessId = HandleToUlong( rtl_info.ClientId.UniqueProcess );
info->dwThreadId = HandleToUlong( rtl_info.ClientId.UniqueThread );
if (!(flags & CREATE_SUSPENDED)) NtResumeThread( rtl_info.Thread, NULL );
TRACE( "started process pid %04x tid %04x\n", info->dwProcessId, info->dwThreadId );
}
done:
RtlDestroyProcessParameters( params );
if (tidy_cmdline != cmd_line) HeapFree( GetProcessHeap(), 0, tidy_cmdline );
return set_ntstatus( status );
}
/**********************************************************************
* CreateProcessA (kernelbase.@)
*/
BOOL WINAPI DECLSPEC_HOTPATCH CreateProcessA( const char *app_name, char *cmd_line,
SECURITY_ATTRIBUTES *process_attr,
SECURITY_ATTRIBUTES *thread_attr, BOOL inherit,
DWORD flags, void *env, const char *cur_dir,
STARTUPINFOA *startup_info, PROCESS_INFORMATION *info )
{
return CreateProcessInternalA( NULL, app_name, cmd_line, process_attr, thread_attr,
inherit, flags, env, cur_dir, startup_info, info, NULL );
}
/**********************************************************************
* CreateProcessW (kernelbase.@)
*/
BOOL WINAPI DECLSPEC_HOTPATCH CreateProcessW( const WCHAR *app_name, WCHAR *cmd_line,
SECURITY_ATTRIBUTES *process_attr,
SECURITY_ATTRIBUTES *thread_attr, BOOL inherit, DWORD flags,
void *env, const WCHAR *cur_dir, STARTUPINFOW *startup_info,
PROCESS_INFORMATION *info )
{
return CreateProcessInternalW( NULL, app_name, cmd_line, process_attr, thread_attr,
inherit, flags, env, cur_dir, startup_info, info, NULL );
}
/*********************************************************************
* DuplicateHandle (kernelbase.@)
*/
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment