blob: 5190e0788eaafba3b7d2b9957157d14e82537afa [file] [log] [blame]
/*
* Copyright (c) 2011 The Native Client Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include <wchar.h>
#include <windows.h>
#include "native_client/tools/redirector/redirector.h"
/*
* The toolchain redirector reads it's name and invokes the appropriate binary
* from a location with cygwin1.dll nearby so that all cygwin-oriented
* toolchain programs can start (this works for Cygwin version >= 1.7).
*
* Supports arbitrary unicode names in it's base directory name.
*
* The executable must be placed in nacl/bin or nacl64/bin or just bin (in the
* latter case the name of the binary should be prefixed with nacl- or nacl64-)
*
* The binary should be built by visual studio (not cygwin) since when it starts
* cygwin has not been located yet.
*
* Use the following commands:
* cl /c /O2 /GS- redirector.c /DCYGWIN_REDIRECTORS=1
* link /subsystem:console /MERGE:.rdata=.text /NODEFAULTLIB
* redirector.obj kernel32.lib user32.lib
*/
static int debug_enabled = 0;
/*
* Wraps GetModuleFileNameW to always succeed in finding a buffer of
* appropriate size.
*/
static
int get_module_name_safely(wchar_t **oldpath) {
int length = 128;
int real_size;
wchar_t *result;
*oldpath = NULL;
while (1) {
result = HeapAlloc(
GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(wchar_t) * length);
if (result == NULL) return 0;
real_size = GetModuleFileNameW(NULL, result, length);
if (real_size == 0) {
HeapFree(GetProcessHeap(), 0, result);
return 0;
}
if (real_size < length - 1) {
/* Success */
*oldpath = result;
return real_size;
}
length <<= 1;
HeapFree(GetProcessHeap(), 0, result);
}
}
static
const wchar_t* find_last_slash_or_backslash_delimiter(const wchar_t *start) {
const wchar_t *delimiter = start + lstrlenW(start);
while (delimiter > start && *delimiter != L'/' && *delimiter != L'\\')
--delimiter;
return delimiter;
}
static
wchar_t* find_program_arguments(void) {
wchar_t *arguments;
arguments = GetCommandLineW();
if (!arguments) return NULL;
if (arguments[0] == L'\"') {
arguments++;
while (arguments[0] && arguments[0] != L'\"') {
if (arguments[0] == L'\\' &&
(arguments[1] == L'\"' || arguments[1] == L'\\'))
arguments++;
arguments++;
}
arguments++;
}
while (arguments[0] && arguments[0] != L' ') arguments++;
if (arguments[0]) while (arguments[1] == ' ') arguments++;
return arguments;
}
static
wchar_t reduce_wchar(wchar_t c) {
if (c == L'/')
return L'\\';
return (wchar_t)(intptr_t)CharLowerW((LPWSTR)(intptr_t)c);
}
/*
* Returns whether path ends with redirect
*/
static
int check_path(const wchar_t *path, const wchar_t *redirect) {
int path_len = lstrlenW(path);
int redirect_len = lstrlenW(redirect);
int path_offset;
int i;
if (path_len < redirect_len)
return 0;
path_offset = path_len - redirect_len;
for (i = 0; i < redirect_len; i++) {
if (reduce_wchar(path[i+path_offset]) != reduce_wchar(redirect[i]))
return 0;
}
return 1;
}
static
int is_driver(const wchar_t *option) {
return lstrcmpW(option, L"-m32") == 0 || lstrcmpW(option, L"-m64") == 0;
}
static
void debug_print(const char* message) {
HANDLE handle;
DWORD wrote;
if (!debug_enabled)
return;
handle = GetStdHandle(STD_ERROR_HANDLE);
WriteFile(handle, message, lstrlen(message), &wrote, NULL);
}
static
void debug_printW(const wchar_t* message) {
char message_utf8[256];
if (!debug_enabled)
return;
WideCharToMultiByte(CP_UTF8, 0, message, -1, message_utf8,
sizeof(message_utf8), NULL, NULL);
debug_print(message_utf8);
}
static
void println_redirect(const redirect_t *redirect) {
const wchar_t *str;
DWORD tmp;
HANDLE output = GetStdHandle(STD_OUTPUT_HANDLE);
for (str = redirect->from; *str; ++str)
WriteFile(output, str, 1, &tmp, NULL);
WriteFile(output, L"|", 1, &tmp, NULL);
for (str = redirect->to; *str; ++str)
WriteFile(output, str, 1, &tmp, NULL);
WriteFile(output, L"|", 1, &tmp, NULL);
for (str = redirect->args; *str; ++str)
WriteFile(output, str, 1, &tmp, NULL);
WriteFile(output, L"\n", 1, &tmp, NULL);
}
int main(int argc, char **argv) {
wchar_t *newpath = NULL, *oldpath = NULL;
const wchar_t *cmdline, *arguments, *selector;
const char* error_message = "Can not find filename to execute!";
const char* error_prefix = "redirector: ";
DWORD length;
DWORD exitcode;
int done;
int redirect_index;
int length_from;
int length_to;
HANDLE output;
int n_redirects = sizeof(redirects)/sizeof(redirect_t);
static STARTUPINFOW si = {sizeof(STARTUPINFOW), 0};
static PROCESS_INFORMATION pi;
char var[10];
if (GetEnvironmentVariable("NACL_REDIRECTOR_DEBUG", var, 10)) {
if (lstrcmp(var, "1") == 0)
debug_enabled = 1;
}
length = get_module_name_safely(&oldpath);
if (length == 0) {
error_message = "Unable to determine executable name.";
goto ShowErrorMessage;
}
debug_print("module name: ");
debug_printW(oldpath);
debug_print("\n");
/* If redirector is called as "redirector.exe", dump redirector table. */
if (check_path(oldpath, L"redirector.exe")) {
debug_print("dumping redirect table:\n");
output = GetStdHandle(STD_OUTPUT_HANDLE);
for (redirect_index = 0;
redirect_index < n_redirects;
redirect_index++) {
println_redirect(redirects + redirect_index);
}
ExitProcess(0);
}
/* Lookup executable name in redirect table */
for (redirect_index = 0;
redirect_index < n_redirects;
redirect_index++) {
if (check_path(oldpath, redirects[redirect_index].from))
break;
}
if (redirect_index >= n_redirects) {
error_message = "Executable not found in redirect table.";
goto ShowErrorMessage;
}
newpath = HeapAlloc(GetProcessHeap(), 0, sizeof(wchar_t)*(length + 64));
if (!newpath) {
error_message = "HeapAlloc failed";
goto ShowErrorMessage;
}
length_from = lstrlenW(redirects[redirect_index].from);
length_to = lstrlenW(redirects[redirect_index].to);
lstrcpynW(newpath, oldpath, length - length_from + 1);
lstrcpyW(newpath + length - length_from, redirects[redirect_index].to);
HeapFree(GetProcessHeap(), 0, oldpath);
oldpath = NULL;
debug_print("target: ");
debug_printW(newpath);
debug_print("\n");
selector = redirects[redirect_index].args;
debug_print("selector: ");
debug_printW(selector);
debug_print("\n");
/*
* The gcc (cygwin) redirectors truncate argv0 here to make error messages
* less confusing. For nacl-clang it seems that we need to preserve the
* full argv0.
* TODO(sbc): Remove this once we are sure the cygwin behaviour is
* non-essential.
*/
#ifdef CYGWIN_REDIRECTORS
cmdline = redirects[redirect_index].to;
if (!is_driver(selector)) {
cmdline = find_last_slash_or_backslash_delimiter(cmdline);
} else {
cmdline = newpath;
}
#else
cmdline = newpath;
#endif
debug_print("cmdline: ");
debug_printW(cmdline);
debug_print("\n");
arguments = find_program_arguments();
if (!arguments) {
error_message = "GetCommandLine failed";
goto ShowErrorMessage;
}
length = lstrlenW(cmdline);
done = lstrlenW(selector);
oldpath = HeapAlloc(GetProcessHeap(), 0,
(lstrlenW(arguments) + length + done + 2) * sizeof(wchar_t));
if (!oldpath) {
error_message = "HeapAlloc failed";
goto ShowErrorMessage;
}
lstrcpyW(oldpath, cmdline);
/*
* cmdline == newpath means it's some kind of gcc driver and we need to
* handle this case specially: we need to put the -V option to be the first.
*/
if (is_driver(selector) && arguments[1] == L'-' && arguments[2] == L'V') {
oldpath[length++] = (arguments++)[0];
oldpath[length++] = (arguments++)[0];
oldpath[length++] = (arguments++)[0];
if (arguments[0] == L' ')
while (arguments[1] == ' ')
arguments++;
if (arguments[0] != 0) {
oldpath[length++] = (arguments++)[0];
while (arguments[0] && arguments[0] != L' ')
oldpath[length++] = (arguments++)[0];
} else {
selector = L"";
done = 0;
}
}
lstrcpyW(oldpath + length, L" ");
lstrcpyW(oldpath + length + 1, selector);
lstrcpyW(oldpath + length + done + 1, arguments);
if (!CreateProcessW(newpath, oldpath, NULL, NULL, TRUE,
CREATE_UNICODE_ENVIRONMENT, NULL, NULL, &si, &pi) != 0) {
error_message = "CreateProcess failed";
goto ShowErrorMessage;
}
WaitForSingleObject(pi.hProcess, INFINITE);
HeapFree(GetProcessHeap(), 0, newpath);
HeapFree(GetProcessHeap(), 0, oldpath);
if (!GetExitCodeProcess(pi.hProcess, &exitcode)) {
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
error_message = "GetExitCodeProcess failed";
goto ShowErrorMessage;
}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
ExitProcess(exitcode);
return 0;
ShowErrorMessage:
if (newpath) HeapFree(GetProcessHeap(), 0, newpath);
if (oldpath) HeapFree(GetProcessHeap(), 0, oldpath);
output = GetStdHandle(STD_ERROR_HANDLE);
WriteFile(output, error_prefix, lstrlen(error_prefix), &length, NULL);
WriteFile(output, error_message, lstrlen(error_message), &length, NULL);
WriteFile(output, "\r\n", 2, &length, NULL);
ExitProcess(1);
return 1;
}