const char* environment_variable( const char* var )
{
#if FOUNDATION_PLATFORM_WINDOWS
	unsigned int required;
	wchar_t* key = wstring_allocate_from_string( var, 0 );
	wchar_t val[FOUNDATION_MAX_PATHLEN]; val[0] = 0;
	if( ( required = GetEnvironmentVariableW( key, val, FOUNDATION_MAX_PATHLEN ) ) > FOUNDATION_MAX_PATHLEN )
	{
		wchar_t* val_local = memory_allocate( sizeof( wchar_t ) * ( required + 2 ), 0, MEMORY_TEMPORARY );
		val_local[0] = 0;
		GetEnvironmentVariableW( key, val_local, required + 1 );
		if( _environment_var )
			string_deallocate( _environment_var );
		_environment_var = string_allocate_from_wstring( val_local, 0 );
		memory_deallocate( val_local );
	}
	else
	{
		if( _environment_var )
			string_deallocate( _environment_var );
		_environment_var = string_allocate_from_wstring( val, 0 );
	}
	wstring_deallocate( key );
	return _environment_var;
#elif FOUNDATION_PLATFORM_POSIX
	return getenv( var );
#else
#  error Not implemented
#endif
}
void environment_set_current_working_directory( const char* path )
{
	if( !path )
		return;
	log_debugf( "Setting current working directory to: %s", path );
#if FOUNDATION_PLATFORM_WINDOWS
	{
		wchar_t* wpath = wstring_allocate_from_string( path, 0 );
		if( !SetCurrentDirectoryW( wpath ) )
			log_warnf( WARNING_SUSPICIOUS, "Unable to set working directory: %ls", wpath );
		wstring_deallocate( wpath );
	}
#elif FOUNDATION_PLATFORM_POSIX
	if( chdir( path ) < 0 )
		log_warnf( WARNING_SYSTEM_CALL_FAIL, "Unable to set working directory: %s", path );
#else
#  error Not implemented
#endif
	_environment_current_working_dir[0] = 0;
}
Beispiel #3
0
int process_spawn( process_t* proc )
{
	static char unescaped[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.:/\\";
	int i, num_args;
	int size;
#if FOUNDATION_PLATFORM_WINDOWS
	wchar_t* wcmdline;
	wchar_t* wwd;
	char* cmdline = 0;
#endif

	if( !proc )
		return PROCESS_INVALID_ARGS;
	
	proc->code = PROCESS_INVALID_ARGS;

	if( !string_length( proc->path ) )
		return proc->code;

	//Always escape path on Windows platforms
#if FOUNDATION_PLATFORM_POSIX
	if( string_find_first_not_of( proc->path, unescaped, 0 ) != STRING_NPOS )
#endif
	{
		if( proc->path[0] != '"' )
			proc->path = string_prepend( proc->path, "\"" );
		if( proc->path[ string_length( proc->path ) - 1 ] != '"' )
			proc->path = string_append( proc->path, "\"" );
	}

	size = array_size( proc->args );
	for( i = 0, num_args = 0; i < size; ++i )
	{
		char* arg = proc->args[i];
		
		if( !string_length( arg ) )
			continue;
		
		++num_args;
		
		if( string_find_first_not_of( arg, unescaped, 0 ) != STRING_NPOS )
		{
			if( arg[0] != '"' )
			{
				//Check if we need to escape " characters
				unsigned int pos = string_find( arg, '"', 0 );
				while( pos != STRING_NPOS )
				{
					if( arg[ pos - 1 ] != '\\' )
					{
						char* escarg = string_substr( arg, 0, pos );
						char* left = string_substr( arg, pos, STRING_NPOS );
						escarg = string_append( escarg, "\\" );
						escarg = string_append( escarg, left );
						string_deallocate( left );
						string_deallocate( arg );
						arg = escarg;
					}
					pos = string_find( arg, '"', pos + 2 );
				}
				arg = string_prepend( arg, "\"" );
				arg = string_append( arg, "\"" );

				proc->args[i] = arg;
			}
		}
	}

#if FOUNDATION_PLATFORM_WINDOWS

#  ifndef SEE_MASK_NOASYNC
#    define SEE_MASK_NOASYNC           0x00000100
#  endif

	if( !( proc->flags & PROCESS_WINDOWS_USE_SHELLEXECUTE ) ) //Don't prepend exe path to parameters if using ShellExecute
		cmdline = string_clone( proc->path );
	
	//Build command line string
	for( i = 0; i < size; ++i )
	{
		char* arg = proc->args[i];
		
		if( !string_length( arg ) )
			continue;

		if( cmdline )
			cmdline = string_append( cmdline, " " );
		cmdline = string_append( cmdline, arg );
	}
	
	if( !string_length( proc->wd ) )
		proc->wd = string_clone( environment_current_working_directory() );

	wcmdline = wstring_allocate_from_string( cmdline, 0 );
	wwd = wstring_allocate_from_string( proc->wd, 0 );
	
	if( proc->flags & PROCESS_WINDOWS_USE_SHELLEXECUTE )
	{
		SHELLEXECUTEINFOW sei;
		wchar_t* wverb;
		wchar_t* wpath;

		wverb = ( proc->verb && string_length( proc->verb ) ) ? wstring_allocate_from_string( proc->verb, 0 ) : 0;
		wpath = wstring_allocate_from_string( proc->path, 0 );

		ZeroMemory( &sei, sizeof( sei ) );

		sei.cbSize          = sizeof(SHELLEXECUTEINFOW);
		sei.hwnd            = 0;
		sei.fMask           = SEE_MASK_NOASYNC | SEE_MASK_FLAG_NO_UI | SEE_MASK_NOCLOSEPROCESS;
		sei.lpVerb          = wverb;
		sei.lpFile          = wpath;
		sei.lpParameters    = wcmdline;
		sei.lpDirectory     = wwd;
		sei.nShow           = SW_SHOWNORMAL;

		if( !( proc->flags & PROCESS_CONSOLE ) )
			sei.fMask      |= SEE_MASK_NO_CONSOLE;

		if( proc->flags & PROCESS_STDSTREAMS )
			log_warnf( 0, WARNING_UNSUPPORTED, "Unable to redirect standard in/out through pipes when using ShellExecute for process spawning" );

		log_debugf( 0, "Spawn process (ShellExecute): %s %s", proc->path, cmdline );

		if( !ShellExecuteExW( &sei ) )
		{
			log_warnf( 0, WARNING_SYSTEM_CALL_FAIL, "Unable to spawn process (ShellExecute) for executable '%s': %s", proc->path, system_error_message( GetLastError() ) );
		}
		else
		{
			proc->hp   = sei.hProcess;
			proc->ht   = 0;
			proc->code = 0;
		}

		wstring_deallocate( wverb );
		wstring_deallocate( wpath );
	}
	else
	{
		STARTUPINFOW si;
		PROCESS_INFORMATION pi;
		BOOL inherit_handles = FALSE;

		memset( &si, 0, sizeof( si ) );
		memset( &pi, 0, sizeof( pi ) );
		si.cb = sizeof( si );

		if( proc->flags & PROCESS_STDSTREAMS )
		{
			proc->pipeout = pipe_allocate();
			proc->pipein = pipe_allocate();

			si.dwFlags |= STARTF_USESTDHANDLES;
			si.hStdOutput = pipe_write_handle( proc->pipeout );
			si.hStdError = pipe_write_handle( proc->pipeout );
			si.hStdInput = pipe_read_handle( proc->pipein );

			//Don't inherit wrong ends of pipes
			SetHandleInformation( pipe_read_handle( proc->pipeout ), HANDLE_FLAG_INHERIT, 0 );
			SetHandleInformation( pipe_write_handle( proc->pipein ), HANDLE_FLAG_INHERIT, 0 );

			inherit_handles = TRUE;
		}

		log_debugf( 0, "Spawn process (CreateProcess): %s %s", proc->path, cmdline );

		if( !CreateProcessW( 0/*wpath*/, wcmdline, 0, 0, inherit_handles, ( proc->flags & PROCESS_CONSOLE ) ? CREATE_NEW_CONSOLE : 0, 0, wwd, &si, &pi ) )
		{
			log_warnf( 0, WARNING_SYSTEM_CALL_FAIL, "Unable to spawn process (CreateProcess) for executable '%s': %s", proc->path, system_error_message( GetLastError() ) );

			stream_deallocate( proc->pipeout );
			stream_deallocate( proc->pipein );

			proc->pipeout = 0;
			proc->pipein = 0;
		}
		else
		{
			proc->hp = pi.hProcess;
			proc->ht = pi.hThread;
			proc->code = 0;
		}

		if( proc->pipeout )
			pipe_close_write( proc->pipeout );
		if( proc->pipein )
			pipe_close_read( proc->pipein );
	}

	wstring_deallocate( wcmdline );
	wstring_deallocate( wwd );
	string_deallocate( cmdline );
	
	if( proc->code < 0 )
		return proc->code; //Error

#endif

#if FOUNDATION_PLATFORM_MACOSX
	
	if( proc->flags & PROCESS_OSX_USE_OPENAPPLICATION )
	{
		proc->pid = 0;
		
		LSApplicationParameters params;
		ProcessSerialNumber psn;
		FSRef* fsref = memory_allocate( 0, sizeof( FSRef ), 0, MEMORY_TEMPORARY | MEMORY_ZERO_INITIALIZED );
		
		memset( &params, 0, sizeof( LSApplicationParameters ) );
		memset( &psn, 0, sizeof( ProcessSerialNumber ) );
		
		char* pathstripped = string_strip( string_clone( proc->path ), "\"" );
		
		OSStatus status = 0;
		status = FSPathMakeRef( (uint8_t*)pathstripped, fsref, 0 );
		if( status < 0 )
		{
			pathstripped = string_append( pathstripped, ".app" );
			status = FSPathMakeRef( (uint8_t*)pathstripped, fsref, 0 );
		}
		
		CFStringRef* args = 0;
		for( i = 0, size = array_size( proc->args ); i < size; ++i ) //App gets executable path automatically, don't include
			array_push( args, CFStringCreateWithCString( 0, proc->args[i], kCFStringEncodingUTF8 ) );
		
		CFArrayRef argvref = CFArrayCreate( 0, (const void**)args, (CFIndex)array_size( args ), 0 );
		
		params.flags = kLSLaunchDefaults;
		params.application = fsref;
		params.argv = argvref;

		log_debugf( 0, "Spawn process (LSOpenApplication): %s", pathstripped );
		
		status = LSOpenApplication( &params, &psn );
		if( status != 0 )
		{
			proc->code = status;
			log_warnf( 0, WARNING_BAD_DATA, "Unable to spawn process for executable '%s': %s", proc->path, system_error_message( status ) );
		}
		
		CFRelease( argvref );
		for( i = 0, size = array_size( args ); i < size; ++i )
			CFRelease( args[i] );
		
		memory_deallocate( fsref );
		string_deallocate( pathstripped );
		
		if( status == 0 )
		{
			pid_t pid = 0;
			GetProcessPID( &psn, &pid );
			
			proc->pid = pid;

			//Always "detached" with LSOpenApplication, not a child process at all
			//Setup a kqueue to watch when process terminates so we can emulate a wait
			proc->kq = kqueue();
			if( proc->kq < 0 )
			{
				log_warnf( 0, WARNING_SYSTEM_CALL_FAIL, "Unable to create kqueue for process watch: %s (%d)", proc->kq, system_error_message( proc->kq ) );
				proc->kq = 0;
			}
			else
			{
				struct kevent changes;
				EV_SET( &changes, (pid_t)pid, EVFILT_PROC, EV_ADD | EV_RECEIPT, NOTE_EXIT, 0, 0 );
				int ret = kevent( proc->kq, &changes, 1, &changes, 1, 0 );
				if( ret != 1 )
				{
					log_warnf( 0, WARNING_SYSTEM_CALL_FAIL, "Unable to setup kqueue for process watch, failed to add event to kqueue (%d)", ret );
					close( proc->kq );
					proc->kq = 0;
				}
			}
		}
		
		goto exit;
	}
#endif

#if FOUNDATION_PLATFORM_POSIX

	//Insert executable arg at start and null ptr at end
	int argc = array_size( proc->args ) + 1;
	array_grow( proc->args, 2 );
	for( int arg = argc - 1; arg > 0; --arg )
		proc->args[arg] = proc->args[arg-1];
	proc->args[0] = string_clone( proc->path );
	proc->args[argc] = 0;

	if( proc->flags & PROCESS_STDSTREAMS )
	{
		proc->pipeout = pipe_allocate();
		proc->pipein = pipe_allocate();
	}
	
	proc->pid = 0;	
	pid_t pid = fork();

	if( pid == 0 )
	{
		//Child
		if( string_length( proc->wd ) )
		{
			log_debugf( 0, "Spawned child process, setting working directory to %s", proc->wd );
			environment_set_current_working_directory( proc->wd );
		}

		log_debugf( 0, "Child process executing: %s", proc->path );

		if( proc->flags & PROCESS_STDSTREAMS )
		{
			pipe_close_read( proc->pipeout );
			dup2( pipe_write_fd( proc->pipeout ), STDOUT_FILENO );

			pipe_close_write( proc->pipein );
			dup2( pipe_read_fd( proc->pipein ), STDIN_FILENO );
		}

		int code = execv( proc->path, proc->args );
		if( code < 0 ) //Will always be true since this point will never be reached if execve() is successful
			log_warnf( 0, WARNING_BAD_DATA, "Child process failed execve() : %s : %s", proc->path, system_error_message( errno ) );
		
		//Error
		process_exit( -1 );
	}

	if( pid > 0 )
	{		
		log_debugf( 0, "Child process forked, pid %d", pid );

		proc->pid = pid;

		if( proc->pipeout )
			pipe_close_write( proc->pipeout );
		if( proc->pipein )
			pipe_close_read( proc->pipein );
		
		if( proc->flags & PROCESS_DETACHED )
		{
			int cstatus = 0;
			pid_t err = waitpid( pid, &cstatus, WNOHANG );
			if( err == 0 )
			{
				//TODO: Ugly wait to make sure process spawned correctly
				thread_sleep( 500 );
				err = waitpid( pid, &cstatus, WNOHANG );
			}
			if( err > 0 )
			{
				//Process exited, check code
				proc->code = (int)((char)WEXITSTATUS( cstatus ));
				log_debugf( 0, "Child process returned: %d", proc->code );
				return proc->code;
			}
		}
	}
	else
	{
		//Error
		proc->code = errno;
		log_warnf( 0, WARNING_BAD_DATA, "Unable to spawn process: %s : %s", proc->path, system_error_message( proc->code ) );

		if( proc->pipeout )
			stream_deallocate( proc->pipeout );
		if( proc->pipein )
			stream_deallocate( proc->pipein );

		proc->pipeout = 0;
		proc->pipein = 0;
		
		return proc->code;
	}	

#endif

#if !FOUNDATION_PLATFORM_WINDOWS && !FOUNDATION_PLATFORM_POSIX
	FOUNDATION_ASSERT_FAIL( "Process spawning not supported on platform" );
#endif
	
#if FOUNDATION_PLATFORM_MACOSX
exit:
#endif

	if( proc->flags & PROCESS_DETACHED )
		return PROCESS_STILL_ACTIVE;

	return process_wait( proc );
}
Beispiel #4
0
DECLARE_TEST( string, initialize )
{
	{
		char* nullstr1 = string_allocate( 0 );
		char* nullstr2 = string_allocate( 1 );
		char* nullstr3 = string_allocate( 10 );

		EXPECT_EQ( 0U, string_length( nullstr1 ) );
		EXPECT_EQ( 0U, string_length( nullstr2 ) );
		EXPECT_EQ( 0U, string_length( nullstr3 ) );
		EXPECT_EQ( 0, strcmp( nullstr1, "" ) );
		EXPECT_EQ( 0, strcmp( nullstr2, "" ) );
		EXPECT_EQ( 0, strcmp( nullstr3, "" ) );	

		string_deallocate( nullstr1 );
		string_deallocate( nullstr2 );
		string_deallocate( nullstr3 );
	}
	{
		char teststr1[] = "test";
		char teststr2[] = "testing long string with more than 16 characters";
		char teststr3[] = "\x01\x02\x03\x04\x05\x06\x07\x08\x90\x01\x02\x03\x04\x05\x06\x07\x08\x90\x01\x02\x03\x04\x05\x06\x07\x08\x90\x01\x02\x03\x04\x05\x06\x07\x08\x90\x01\x02\x03\x04\x05\x06\x07\x08\x90\x01\x02\x03\x04\x05\x06\x07\x08\x09";

		char* str1 = string_clone( teststr1 );
		char* str2 = string_substr( teststr1, 0, 3 );
		char* str3 = string_substr( teststr1, 0, 4 );
		char* str4 = string_substr( teststr1, 0, 32 );
		char* str5 = string_substr( teststr1, 0, STRING_NPOS );

		char* str6 = string_clone( teststr2 );
		char* str7 = string_substr( teststr2, 0, 3 );
		char* str8 = string_substr( teststr2, 0, 20 );
		char* str9 = string_substr( teststr2, 0, STRING_NPOS );

		char* str10 = string_clone( teststr3 );
		char* str11 = string_substr( teststr3, 0, 3 );
		char* str12 = string_substr( teststr3, 0, 20 );
		char* str13 = string_substr( teststr3, 0, STRING_NPOS );

		EXPECT_EQ( 0, strcmp( str1, teststr1 ) );
		EXPECT_EQ( 0, strcmp( str2, "tes" ) );
		EXPECT_EQ( 0, strcmp( str3, teststr1 ) );
		EXPECT_EQ( 0, strcmp( str4, teststr1 ) );
		EXPECT_EQ( 0, strcmp( str5, teststr1 ) );

		EXPECT_EQ( 0, strcmp( str6, teststr2 ) );
		EXPECT_EQ( 0, strcmp( str7, "tes" ) );
		EXPECT_EQ( 0, strcmp( str8, "testing long string " ) );
		EXPECT_EQ( 0, strcmp( str9, teststr2 ) );

		EXPECT_EQ( 0, strcmp( str10, teststr3 ) );
		EXPECT_EQ( 0, strcmp( str11, "\x01\x02\x03" ) );
		EXPECT_EQ( 0, strcmp( str12, "\x01\x02\x03\x04\x05\x06\x07\x08\x90\x01\x02\x03\x04\x05\x06\x07\x08\x90\x01\x02" ) );
		EXPECT_EQ( 0, strcmp( str13, teststr3 ) );

		string_deallocate( str1 );
		string_deallocate( str2 );
		string_deallocate( str3 );
		string_deallocate( str4 );
		string_deallocate( str5 );
		string_deallocate( str6 );
		string_deallocate( str7 );
		string_deallocate( str8 );
		string_deallocate( str9 );
		string_deallocate( str10 );
		string_deallocate( str11 );
		string_deallocate( str12 );
		string_deallocate( str13 );
	}
	{
		wchar_t teststr1[] = L"test";
		wchar_t teststr2[] = L"testing long string with more than 16 characters";
		wchar_t teststr3[] = L"0\x0001\x0002\x0003\x0004\x0005\x0006\x0007\x0008\x0009\x0030\x0001\x0002\x0003\x0004\x0005\x0006\x0007\x0008\x0009\x0030\x0001\x0002\x0003\x0004\x0005\x0006\x0007\x0008\x0009\x0030\x0001\x0002\x0003\x0004\x0005\x0006\x0007\x0008\x0009\x0030\x0001\x0002\x0003\x0004\x0005\x0006\x0007\x0008\x0009\x0030\x0001\x0002\x0003\x0004\x0005\x0006\x0007\x0008\x0009";
		wchar_t teststr4[] = L"Wide-char string with some strange characters: åäöÅÄÖ_________ (test utf-8 conversions with long strings)";
		//char utfteststr4[] = "Wide-char string with some strange characters: \xC3\xA5\xC3\xA4\xC3\xB6\xC3\x85\xC3\x84\xC3\x96\xEF\xB7\xB2\xDA\x81\xED\x80\x83\xEC\x99\xA7\xEB\x96\xAF\xE7\xBF\xB3\xE5\xA1\x8C\xE1\xA7\xBC\xE1\xA7\xAD (test utf-8 conversions with long strings)";

		char *str1, *str2, *str3, *str4, *str5, *str6, *str7, *str8, *str9, *str10, *str11, *str12, *str13, *str14, *str15, *str16, *str17;
		wchar_t *wstr1, *wstr2, *wstr3, *wstr4, *wstr5, *wstr6, *wstr7, *wstr8, *wstr9, *wstr10, *wstr11, *wstr12, *wstr13, *wstr14, *wstr15, *wstr16, *wstr17;

		teststr4[53] = (wchar_t)0x602f;//L'ﷲ';
		teststr4[54] = (wchar_t)0xf045;//L'ځ';
		teststr4[55] = (wchar_t)0x1d30;//L'퀃';
		teststr4[56] = (wchar_t)0x0378;//L'왧';
		teststr4[57] = (wchar_t)0x0ffe;//L'떯';
		teststr4[58] = (wchar_t)0xffe0;//L'翳';
		teststr4[59] = (wchar_t)0x1234;//L'塌';
		teststr4[60] = (wchar_t)0x4321;//L'᧼';
		teststr4[61] = (wchar_t)0x0f0f;//L'᧭';

		str1 = string_allocate_from_wstring( teststr1, 0 );
		str2 = string_allocate_from_wstring( teststr1, 3 );
		str3 = string_allocate_from_wstring( teststr1, 4 );
		str4 = string_allocate_from_wstring( teststr1, 32 );
		str5 = string_allocate_from_wstring( teststr1, STRING_NPOS );

		str6 = string_allocate_from_wstring( teststr2, 0 );
		str7 = string_allocate_from_wstring( teststr2, 3 );
		str8 = string_allocate_from_wstring( teststr2, 20 );
		str9 = string_allocate_from_wstring( teststr2, STRING_NPOS );

		str10 = string_allocate_from_wstring( teststr3, 0 );
		str11 = string_allocate_from_wstring( teststr3, 3 );
		str12 = string_allocate_from_wstring( teststr3, 20 );
		str13 = string_allocate_from_wstring( teststr3, STRING_NPOS );

		str14 = string_allocate_from_wstring( teststr4, 0 );
		str15 = string_allocate_from_wstring( teststr4, 3 );
		str16 = string_allocate_from_wstring( teststr4, 63 );
		str17 = string_allocate_from_wstring( teststr4, STRING_NPOS );

		wstr1 = wstring_allocate_from_string( str1, 0 );
		wstr2 = wstring_allocate_from_string( str2, 0 );
		wstr3 = wstring_allocate_from_string( str3, 0 );
		wstr4 = wstring_allocate_from_string( str4, 0 );
		wstr5 = wstring_allocate_from_string( str5, 0 );

		wstr6 = wstring_allocate_from_string( str6, 0 );
		wstr7 = wstring_allocate_from_string( str7, 0 );
		wstr8 = wstring_allocate_from_string( str8, 0 );
		wstr9 = wstring_allocate_from_string( str9, 0 );

		wstr10 = wstring_allocate_from_string( str10, 0 );
		wstr11 = wstring_allocate_from_string( str11, 0 );
		wstr12 = wstring_allocate_from_string( str12, 0 );
		wstr13 = wstring_allocate_from_string( str13, 0 );

		wstr14 = wstring_allocate_from_string( str14, 0 );
		wstr15 = wstring_allocate_from_string( str15, 0 );
		wstr16 = wstring_allocate_from_string( str16, 0 );
		wstr17 = wstring_allocate_from_string( str17, 0 );

		EXPECT_EQ( 0, wcscmp( wstr1, teststr1 ) );
		EXPECT_EQ( 0, wcscmp( wstr2, L"tes" ) );
		EXPECT_EQ( 0, wcscmp( wstr3, teststr1 ) );
		EXPECT_EQ( 0, wcscmp( wstr4, teststr1 ) );
		EXPECT_EQ( 0, wcscmp( wstr5, teststr1 ) );

		EXPECT_EQ( 0, wcscmp( wstr6, teststr2 ) );
		EXPECT_EQ( 0, wcscmp( wstr7, L"tes" ) );
		EXPECT_EQ( 0, wcscmp( wstr8, L"testing long string " ) );
		EXPECT_EQ( 0, wcscmp( wstr9, teststr2 ) );

		EXPECT_EQ( 0, wcscmp( wstr10, teststr3 ) );
		EXPECT_EQ( 0, wcscmp( wstr11, L"0\x0001\x0002" ) );
		EXPECT_EQ( 0, wcscmp( wstr12, L"0\x0001\x0002\x0003\x0004\x0005\x0006\x0007\x0008\x0009\x0030\x0001\x0002\x0003\x0004\x0005\x0006\x0007\x0008\x0009" ) );
		EXPECT_EQ( 0, wcscmp( wstr13, teststr3 ) );

		EXPECT_EQ( 0, wcscmp( wstr14, teststr4 ) );
		EXPECT_EQ( 0, wcscmp( wstr15, L"Wid" ) );
		EXPECT_EQ( 0, wcscmp( wstr17, teststr4 ) );
		teststr4[63] = 0;
		EXPECT_EQ( 0, wcscmp( wstr16, teststr4 ) );

		{
			wchar_t wteststr[] = { 0x0100, 0x078f, 0x1234, 0xFF03, 0xD854, 0xDC53, 0x0032, 0 };
			char* utf8_teststr = string_allocate_from_wstring( wteststr, 0 );
			wchar_t* wchar_teststr = wstring_allocate_from_string( utf8_teststr, 0 );
			EXPECT_EQ( 0, wcscmp( wteststr, wchar_teststr ) );
			wstring_deallocate( wchar_teststr );
			string_deallocate( utf8_teststr );
		}

		{
			uint16_t wteststr[] = { 0xFEFF, 0x0100, 0x078f, 0x1234, 0xFF03, 0xDB02, 0xDC54, 0x0032, 0xFFFE, 0x1234, 0xFF03, 0 };
#if FOUNDATION_WCHAR_SIZE == 32
			wchar_t wtestcmpstr[] = { 0x0100, 0x078f, 0x1234, 0xFF03, 0x000D0854, 0x0032, 0x3412, 0x03FF, 0 };
#else
			wchar_t wtestcmpstr[] = { 0x0100, 0x078f, 0x1234, 0xFF03, 0xDB02, 0xDC54, 0x0032, 0x3412, 0x03FF, 0 };
#endif
			char* utf8_teststr = string_allocate_from_utf16( wteststr, 0 );
			wchar_t* wchar_teststr = wstring_allocate_from_string( utf8_teststr, 0 );
			EXPECT_EQ( 0, wcscmp( wtestcmpstr, wchar_teststr ) );
			wstring_deallocate( wchar_teststr );
			string_deallocate( utf8_teststr );
		}

		{
			uint32_t wteststr[] = { 0x0000FEFF, 0x00000100, 0x0000078f, 0x00001234, 0x0000FF03, 0x000D0854, 0x000D0C53, 0x00000032, 0xFFFE0000, 0x12340000, 0xFF030000, 0 };
#if FOUNDATION_WCHAR_SIZE == 32
			wchar_t wtestcmpstr[] = { 0x0100, 0x078f, 0x1234, 0xFF03, 0x000D0854, 0x000D0C53, 0x0032, 0x3412, 0x03FF, 0 };
#else
			wchar_t wtestcmpstr[] = { 0x0100, 0x078f, 0x1234, 0xFF03, 0xDB02, 0xDC54, 0xDB03, 0xDC53, 0x0032, 0x3412, 0x03FF, 0 };
#endif
			char* utf8_teststr = string_allocate_from_utf32( wteststr, 0 );
			wchar_t* wchar_teststr = wstring_allocate_from_string( utf8_teststr, 0 );
			EXPECT_EQ( 0, wcscmp( wtestcmpstr, wchar_teststr ) );
			wstring_deallocate( wchar_teststr );
			string_deallocate( utf8_teststr );
		}

		string_deallocate( str1 );
		string_deallocate( str2 );
		string_deallocate( str3 );
		string_deallocate( str4 );
		string_deallocate( str5 );
		string_deallocate( str6 );
		string_deallocate( str7 );
		string_deallocate( str8 );
		string_deallocate( str9 );
		string_deallocate( str10 );
		string_deallocate( str11 );
		string_deallocate( str12 );
		string_deallocate( str13 );
		string_deallocate( str14 );
		string_deallocate( str15 );
		string_deallocate( str16 );
		string_deallocate( str17 );

		wstring_deallocate( wstr1 );
		wstring_deallocate( wstr2 );
		wstring_deallocate( wstr3 );
		wstring_deallocate( wstr4 );
		wstring_deallocate( wstr5 );
		wstring_deallocate( wstr6 );
		wstring_deallocate( wstr7 );
		wstring_deallocate( wstr8 );
		wstring_deallocate( wstr9 );
		wstring_deallocate( wstr10 );
		wstring_deallocate( wstr11 );
		wstring_deallocate( wstr12 );
		wstring_deallocate( wstr13 );
		wstring_deallocate( wstr14 );
		wstring_deallocate( wstr15 );
		wstring_deallocate( wstr16 );
		wstring_deallocate( wstr17 );
	}		
	return 0;
}
Beispiel #5
0
int
process_spawn(process_t* proc) {
	static const string_const_t unescaped = { STRING_CONST("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.:/\\") };
	size_t i, num_args;
	size_t size;
#if FOUNDATION_PLATFORM_WINDOWS
	wchar_t* wcmdline;
	wchar_t* wwd;
	string_t cmdline;
#endif
#if !FOUNDATION_PLATFORM_POSIX
	size_t capacity;
#endif

	proc->code = PROCESS_INVALID_ARGS;
	if (!proc->path.length)
		return proc->code;

	//Always escape path on Windows platforms
#if FOUNDATION_PLATFORM_POSIX
	if (string_find_first_not_of(STRING_ARGS(proc->path), STRING_ARGS(unescaped), 0) != STRING_NPOS)
#endif
	{
		bool preesc = (proc->path.str[0] != '"');
		bool postesc = (proc->path.str[ proc->path.length - 1 ] != '"');
		if (preesc || postesc) {
			char* buffer = memory_allocate(HASH_STRING, proc->path.length + 4, 0, MEMORY_PERSISTENT);
			string_t pathesc = string_concat_varg(buffer, proc->path.length + 4,
			                                      "\"", (size_t)(preesc ? 1 : 0),
			                                      STRING_ARGS(proc->path),
			                                      "\"", (size_t)(postesc ? 1 : 0),
			                                      nullptr);
			string_deallocate(proc->path.str);
			proc->path = pathesc;
		}
	}

	size = array_size(proc->args);
	for (i = 0, num_args = 0; i < size; ++i) {
		string_t arg = proc->args[i];

		if (!arg.length)
			continue;

		++num_args;

#if !FOUNDATION_PLATFORM_POSIX
		if (string_find_first_not_of(arg.str, arg.length,
		                             unescaped.str, unescaped.length, 0) != STRING_NPOS) {
			if (arg.str[0] != '"') {
				//Check if we need to escape " characters
				string_t escarg;
				size_t pos = string_find(arg.str, arg.length, '"', 0);
				while (pos != STRING_NPOS) {
					if (arg.str[ pos - 1 ] != '\\') {
						string_const_t right = string_substr(STRING_ARGS(arg), 0, pos);
						string_const_t left = string_substr(STRING_ARGS(arg), pos, STRING_NPOS);
						capacity = arg.length + 2;
						escarg = string_allocate(0, capacity);
						escarg = string_concat(escarg.str, capacity, STRING_ARGS(right), STRING_CONST("\\"));
						escarg = string_append(STRING_ARGS(escarg), capacity, STRING_ARGS(left));
						string_deallocate(arg.str);
						arg = escarg;
					}
					pos = string_find(STRING_ARGS(arg), '"', pos + 2);
				}
				escarg = string_allocate_concat_varg(STRING_CONST("\""), STRING_ARGS(arg),
				                                     STRING_CONST("\""), nullptr);
				string_deallocate(arg.str);
				proc->args[i] = escarg;
			}
		}
#endif
	}

#if FOUNDATION_PLATFORM_WINDOWS

#  ifndef SEE_MASK_NOASYNC
#    define SEE_MASK_NOASYNC 0x00000100
#  endif

	capacity = BUILD_MAX_PATHLEN;
	cmdline = string_allocate(0, capacity);

	//Don't prepend exe path to parameters if using ShellExecute
	if (!(proc->flags & PROCESS_WINDOWS_USE_SHELLEXECUTE))
		cmdline = string_copy(cmdline.str, capacity, STRING_ARGS(proc->path));

	//Build command line string
	for (i = 0; i < size; ++i) {
		string_t arg = proc->args[i];

		if (!arg.length)
			continue;
		if (cmdline.length + arg.length + 2 >= capacity) {
			string_t newline;
			capacity *= 2;
			newline = string_allocate(0, capacity);
			newline = string_copy(newline.str, capacity, STRING_ARGS(cmdline));
		}
		if (cmdline.length)
			cmdline = string_append(STRING_ARGS(cmdline), capacity, STRING_CONST(" "));
		cmdline = string_append(STRING_ARGS(cmdline), capacity, STRING_ARGS(arg));
	}

	if (!proc->wd.length)
		proc->wd = string_clone(STRING_ARGS(environment_current_working_directory()));

	wcmdline = wstring_allocate_from_string(STRING_ARGS(cmdline));
	wwd = wstring_allocate_from_string(STRING_ARGS(proc->wd));

	if (proc->flags & PROCESS_WINDOWS_USE_SHELLEXECUTE) {
		SHELLEXECUTEINFOW sei;
		wchar_t* wverb;
		wchar_t* wpath;

		wverb = (proc->verb.length ? wstring_allocate_from_string(STRING_ARGS(proc->verb)) : 0);
		wpath = wstring_allocate_from_string(STRING_ARGS(proc->path));

		ZeroMemory(&sei, sizeof(sei));

		sei.cbSize          = sizeof(SHELLEXECUTEINFOW);
		sei.hwnd            = 0;
		sei.fMask           = SEE_MASK_NOASYNC | SEE_MASK_FLAG_NO_UI | SEE_MASK_NOCLOSEPROCESS;
		sei.lpVerb          = wverb;
		sei.lpFile          = wpath;
		sei.lpParameters    = wcmdline;
		sei.lpDirectory     = wwd;
		sei.nShow           = SW_SHOWNORMAL;

		if (!(proc->flags & PROCESS_CONSOLE))
			sei.fMask      |= SEE_MASK_NO_CONSOLE;

		if (proc->flags & PROCESS_STDSTREAMS)
			log_warn(0, WARNING_UNSUPPORTED, STRING_CONST("Unable to redirect standard in/out"
			                                              " through pipes when using ShellExecute for process spawning"));

		log_debugf(0, STRING_CONST("Spawn process (ShellExecute): %.*s %.*s"),
		           STRING_FORMAT(proc->path), STRING_FORMAT(cmdline));

		if (!ShellExecuteExW(&sei)) {
			string_const_t errstr = system_error_message(0);
			log_warnf(0, WARNING_SYSTEM_CALL_FAIL,
			          STRING_CONST("Unable to spawn process (ShellExecute) for executable '%.*s': %s"),
			          STRING_FORMAT(proc->path), STRING_FORMAT(errstr));
		}
		else {
			proc->hp   = sei.hProcess;
			proc->ht   = 0;
			proc->code = 0;
		}

		wstring_deallocate(wverb);
		wstring_deallocate(wpath);
	}
	else {
		STARTUPINFOW si;
		PROCESS_INFORMATION pi;
		BOOL inherit_handles = FALSE;

		memset(&si, 0, sizeof(si));
		memset(&pi, 0, sizeof(pi));
		si.cb = sizeof(si);

		if (proc->flags & PROCESS_STDSTREAMS) {
			proc->pipeout = pipe_allocate();
			proc->pipein = pipe_allocate();

			si.dwFlags |= STARTF_USESTDHANDLES;
			si.hStdOutput = pipe_write_handle(proc->pipeout);
			si.hStdInput = pipe_read_handle(proc->pipein);
			si.hStdError = GetStdHandle(STD_ERROR_HANDLE);

			//Don't inherit wrong ends of pipes
			SetHandleInformation(pipe_read_handle(proc->pipeout), HANDLE_FLAG_INHERIT, 0);
			SetHandleInformation(pipe_write_handle(proc->pipein), HANDLE_FLAG_INHERIT, 0);

			inherit_handles = TRUE;
		}

		log_debugf(0, STRING_CONST("Spawn process (CreateProcess): %.*s %.*s"),
		           STRING_FORMAT(proc->path), STRING_FORMAT(cmdline));

		if (!CreateProcessW(0, wcmdline, 0, 0, inherit_handles,
		                    (proc->flags & PROCESS_CONSOLE) ? CREATE_NEW_CONSOLE : 0, 0, wwd, &si, &pi)) {
			string_const_t errstr = system_error_message(0);
			log_warnf(0, WARNING_SYSTEM_CALL_FAIL,
			          STRING_CONST("Unable to spawn process (CreateProcess) for executable '%.*s': %.*s"),
			          STRING_FORMAT(proc->path), STRING_FORMAT(errstr));

			stream_deallocate(proc->pipeout);
			stream_deallocate(proc->pipein);

			proc->pipeout = 0;
			proc->pipein = 0;
		}
		else {
			proc->hp = pi.hProcess;
			proc->ht = pi.hThread;
			proc->code = 0;
		}

		if (proc->pipeout)
			pipe_close_write(proc->pipeout);
		if (proc->pipein)
			pipe_close_read(proc->pipein);
	}

	wstring_deallocate(wcmdline);
	wstring_deallocate(wwd);
	string_deallocate(cmdline.str);

	if (proc->code < 0)
		return proc->code; //Error

#endif

#if FOUNDATION_PLATFORM_MACOSX

	if (proc->flags & PROCESS_MACOSX_USE_OPENAPPLICATION) {
		proc->pid = 0;

		LSApplicationParameters params;
		ProcessSerialNumber psn;
		FSRef* fsref = memory_allocate(0, sizeof(FSRef), 0, MEMORY_TEMPORARY | MEMORY_ZERO_INITIALIZED);

		memset(&params, 0, sizeof(LSApplicationParameters));
		memset(&psn, 0, sizeof(ProcessSerialNumber));

		string_const_t pathstripped = string_strip(proc->path.str, proc->path.length, STRING_CONST("\""));
		size_t localcap = pathstripped.length + 5;
		string_t localpath = string_allocate(0, localcap - 1);
		localpath = string_copy(localpath.str, localcap,
		                        STRING_ARGS(pathstripped));     //Need it zero terminated

		OSStatus status = 0;
		status = FSPathMakeRef((uint8_t*)localpath.str, fsref, 0);
		if (status < 0) {
			localpath = string_append(localpath.str, localpath.length, localcap, STRING_CONST(".app"));
			status = FSPathMakeRef((uint8_t*)localpath.str, fsref, 0);
		}

		CFStringRef* args = 0;
		for (i = 0, size = array_size(proc->args); i < size;
		        ++i)    //App gets executable path automatically, don't include
			array_push(args, CFStringCreateWithCString(0, proc->args[i].str, kCFStringEncodingUTF8));

		CFArrayRef argvref = CFArrayCreate(0, (const void**)args, (CFIndex)array_size(args), 0);

		params.flags = kLSLaunchDefaults;
		params.application = fsref;
		params.argv = argvref;

		log_debugf(0, STRING_CONST("Spawn process (LSOpenApplication): %.*s"), STRING_FORMAT(localpath));

		status = LSOpenApplication(&params, &psn);
		if (status != 0) {
			int err = status;
			string_const_t errmsg = system_error_message(err);
			proc->code = status;
			log_errorf(0, ERROR_SYSTEM_CALL_FAIL,
			           STRING_CONST("Unable to spawn process for executable '%.*s': %.*s (%d)"),
			           STRING_FORMAT(localpath), STRING_FORMAT(errmsg), err);
		}

		CFRelease(argvref);
		for (i = 0, size = array_size(args); i < size; ++i)
			CFRelease(args[i]);
		array_deallocate(args);

		memory_deallocate(fsref);
		string_deallocate(localpath.str);

		if (status == 0) {
			pid_t pid = 0;
			GetProcessPID(&psn, &pid);

			proc->pid = pid;

			//Always "detached" with LSOpenApplication, not a child process at all
			//Setup a kqueue to watch when process terminates so we can emulate a wait
			proc->kq = kqueue();
			if (proc->kq < 0) {
				string_const_t errmsg = system_error_message(proc->kq);
				log_errorf(0, ERROR_SYSTEM_CALL_FAIL,
				           STRING_CONST("Unable to create kqueue for process watch: %.*s (%d)"),
				           STRING_FORMAT(errmsg), proc->kq);
				proc->kq = 0;
			}
			else {
				struct kevent changes;
				EV_SET(&changes, (pid_t)pid, EVFILT_PROC, EV_ADD | EV_RECEIPT, NOTE_EXIT, 0, 0);
				int ret = kevent(proc->kq, &changes, 1, &changes, 1, 0);
				if (ret != 1) {
					int err = errno;
					string_const_t errmsg = system_error_message(err);
					log_errorf(0, ERROR_SYSTEM_CALL_FAIL,
					           STRING_CONST("Unable to setup kqueue for process watch, failed to add event to kqueue: %.*s (%d)"),
					           STRING_FORMAT(errmsg), err);
					close(proc->kq);
					proc->kq = 0;
				}
			}
		}

		goto exit;
	}
#endif

#if FOUNDATION_PLATFORM_POSIX

	//Insert executable arg at start and null ptr at end
	size_t arg;
	size_t argc = array_size(proc->args) + 1;
	array_grow(proc->args, 2);
	for (arg = argc - 1; arg > 0; --arg)
		proc->args[arg] = proc->args[arg - 1];
	proc->args[0] = string_clone(STRING_ARGS(proc->path));
	proc->args[argc] = (string_t) { 0, 0 };

	char** argv = memory_allocate(0, sizeof(char*) * (argc + 1), 0, MEMORY_PERSISTENT);
	for (arg = 0; arg < argc; ++arg)
		argv[arg] = proc->args[arg].str;
	argv[argc] = 0;

	if (proc->flags & PROCESS_STDSTREAMS) {
		proc->pipeout = pipe_allocate();
		proc->pipein = pipe_allocate();
	}

	proc->pid = 0;
	pid_t pid = fork();

	if (pid == 0) {
		//Child
		if (proc->wd.length) {
			log_debugf(0, STRING_CONST("Spawned child process, setting working directory to %.*s"),
			           STRING_FORMAT(proc->wd));
			environment_set_current_working_directory(STRING_ARGS(proc->wd));
		}

		log_debugf(0, STRING_CONST("Child process executing: %.*s"), STRING_FORMAT(proc->path));

		if (proc->flags & PROCESS_STDSTREAMS) {
			pipe_close_read(proc->pipeout);
			dup2(pipe_write_handle(proc->pipeout), STDOUT_FILENO);

			pipe_close_write(proc->pipein);
			dup2(pipe_read_handle(proc->pipein), STDIN_FILENO);
		}

		int code = execv(proc->path.str, (char* const*)argv);

		//Error
		int err = errno;
		string_const_t errmsg = system_error_message(err);
		log_errorf(0, ERROR_SYSTEM_CALL_FAIL,
		           STRING_CONST("Child process failed execve() '%.*s': %.*s (%d) (%d)"),
			       STRING_FORMAT(proc->path), STRING_FORMAT(errmsg), err, code);
		process_exit(PROCESS_EXIT_FAILURE);
		FOUNDATION_UNUSED(code);
	}

	memory_deallocate(argv);

	if (pid > 0) {
		log_debugf(0, STRING_CONST("Child process forked, pid %d"), pid);

		proc->pid = pid;

		if (proc->pipeout)
			pipe_close_write(proc->pipeout);
		if (proc->pipein)
			pipe_close_read(proc->pipein);

		/*if (proc->flags & PROCESS_DETACHED) {
			int cstatus = 0;
			pid_t err = waitpid(pid, &cstatus, WNOHANG);
			if (err == 0) {
				//TODO: Ugly wait to make sure process spawned correctly
				thread_sleep(500);
				err = waitpid(pid, &cstatus, WNOHANG);
			}
			if (err > 0) {
				//Process exited, check code
				proc->pid = 0;
				proc->code = (int)((char)WEXITSTATUS(cstatus));
				log_debugf(0, STRING_CONST("Child process returned: %d"), proc->code);
				return proc->code;
			}
		}*/
	}
	else {
		//Error
		string_const_t errmsg;
		errmsg = system_error_message(proc->code);
		log_errorf(0, ERROR_SYSTEM_CALL_FAIL, STRING_CONST("Unable to spawn process '%.*s': %.*s (%d)"),
		           STRING_FORMAT(proc->path), STRING_FORMAT(errmsg), proc->code);

		if (proc->pipeout)
			stream_deallocate(proc->pipeout);
		if (proc->pipein)
			stream_deallocate(proc->pipein);

		proc->pipeout = 0;
		proc->pipein = 0;
		proc->code = PROCESS_INVALID_ARGS;

		return proc->code;
	}

#endif

#if !FOUNDATION_PLATFORM_WINDOWS && !FOUNDATION_PLATFORM_POSIX
	FOUNDATION_ASSERT_FAIL("Process spawning not supported on platform");
#endif

#if FOUNDATION_PLATFORM_MACOSX
exit:
#endif

	if (proc->flags & PROCESS_DETACHED)
		return PROCESS_STILL_ACTIVE;

	return process_wait(proc);
}