// Copyright (c) 2013 The Chromium 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 #include #include #include #include #include #include #include #ifndef SPLIT_LINK_SCRIPT_PATH #error SPLIT_LINK_SCRIPT_PATH must be defined as the path to "split_link.py". #endif #ifndef PYTHON_PATH #error PYTHON_PATH must be defined to be the path to the python binary. #endif #define WIDEN2(x) L ## x #define WIDEN(x) WIDEN2(x) #define WPYTHON_PATH WIDEN(PYTHON_PATH) #define WSPLIT_LINK_SCRIPT_PATH WIDEN(SPLIT_LINK_SCRIPT_PATH) using namespace std; // Don't use stderr for errors because VS has large buffers on them, leading // to confusing error output. static void Fatal(const wchar_t* msg) { wprintf(L"split_link fatal error: %s\n", msg); exit(1); } static wstring ErrorMessageToString(DWORD err) { wchar_t* msg_buf = NULL; DWORD rc = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast(&msg_buf), 0, NULL); if (!rc) return L"unknown error"; wstring ret(msg_buf); LocalFree(msg_buf); return ret; } static void ArgvQuote(const std::wstring& argument, std::wstring* command_line) { // Don't quote unless we actually need to. if (!argument.empty() && argument.find_first_of(L" \t\n\v\"") == argument.npos) { command_line->append(argument); } else { command_line->push_back(L'"'); for (std::wstring::const_iterator it = argument.begin();; ++it) { int num_backslashes = 0; while (it != argument.end() && *it == L'\\') { ++it; ++num_backslashes; } if (it == argument.end()) { // Escape all backslashes, but let the terminating double quotation // mark we add below be interpreted as a metacharacter. command_line->append(num_backslashes * 2, L'\\'); break; } else if (*it == L'"') { // Escape all backslashes and the following double quotation mark. command_line->append(num_backslashes * 2 + 1, L'\\'); command_line->push_back(*it); } else { // Backslashes aren't special here. command_line->append(num_backslashes, L'\\'); command_line->push_back(*it); } } command_line->push_back(L'"'); } } // Does the opposite of CommandLineToArgvW. Suitable for CreateProcess, but // not for cmd.exe. |args| should include the program name as argv[0]. // See http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx static wstring BuildCommandLine(const vector& args) { std::wstring result; for (size_t i = 0; i < args.size(); ++i) { ArgvQuote(args[i], &result); if (i < args.size() - 1) { result += L" "; } } return result; } static void RunLinker(const vector& prefix, const wchar_t* msg) { if (msg) { wprintf(L"split_link failed (%s), trying to fallback to standard link.\n", msg); wprintf(L"Original command line: %s\n", GetCommandLine()); fflush(stdout); } STARTUPINFO startup_info = { sizeof(STARTUPINFO) }; PROCESS_INFORMATION process_info; DWORD exit_code; GetStartupInfo(&startup_info); if (getenv("SPLIT_LINK_DEBUG")) { wprintf(L" original command line '%s'\n", GetCommandLine()); fflush(stdout); } int num_args; LPWSTR* args = CommandLineToArgvW(GetCommandLine(), &num_args); if (!args) Fatal(L"Couldn't parse command line."); vector argv; argv.insert(argv.end(), prefix.begin(), prefix.end()); for (int i = 1; i < num_args; ++i) // Skip old argv[0]. argv.push_back(args[i]); LocalFree(args); wstring cmd = BuildCommandLine(argv); if (getenv("SPLIT_LINK_DEBUG")) { wprintf(L" running '%s'\n", cmd.c_str()); fflush(stdout); } if (!CreateProcess(NULL, reinterpret_cast(const_cast( cmd.c_str())), NULL, NULL, TRUE, 0, NULL, NULL, &startup_info, &process_info)) { wstring error = ErrorMessageToString(GetLastError()); Fatal(error.c_str()); } CloseHandle(process_info.hThread); WaitForSingleObject(process_info.hProcess, INFINITE); GetExitCodeProcess(process_info.hProcess, &exit_code); CloseHandle(process_info.hProcess); exit(exit_code); } static void Fallback(const wchar_t* msg) { wchar_t original_link[1024]; DWORD type; DWORD size = sizeof(original_link); if (SHGetValue(HKEY_CURRENT_USER, L"Software\\Chromium\\split_link_installed", NULL, &type, original_link, &size) != ERROR_SUCCESS || type != REG_SZ) { Fatal(L"Couldn't retrieve linker location from " L"HKCU\\Software\\Chromium\\split_link_installed."); } if (getenv("SPLIT_LINK_DEBUG")) { wprintf(L" got original linker '%s'\n", original_link); fflush(stdout); } vector link_binary; link_binary.push_back(original_link); RunLinker(link_binary, msg); } static void Fallback() { Fallback(NULL); } static unsigned char* SlurpFile(const wchar_t* path, size_t* length) { HANDLE file = CreateFile( path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if (file == INVALID_HANDLE_VALUE) Fallback(L"couldn't open file"); LARGE_INTEGER file_size; if (!GetFileSizeEx(file, &file_size)) Fallback(L"couldn't get file size"); *length = static_cast(file_size.QuadPart); unsigned char* buffer = static_cast(malloc(*length)); DWORD bytes_read = 0; if (!ReadFile(file, buffer, *length, &bytes_read, NULL)) Fallback(L"couldn't read file"); return buffer; } static bool SplitLinkRequested(const wchar_t* rsp_path) { size_t length; unsigned char* data = SlurpFile(rsp_path, &length); bool flag_found = false; if (data[0] == 0xff && data[1] == 0xfe) { // UTF-16LE wstring wide(reinterpret_cast(&data[2]), length / sizeof(wchar_t) - 1); flag_found = wide.find(L"/splitlink") != wide.npos; } else { string narrow(reinterpret_cast(data), length); flag_found = narrow.find("/splitlink") != narrow.npos; } free(data); return flag_found; } // If /splitlink is on the command line, delegate to split_link.py, otherwise // fallback to standard linker. int wmain(int argc, wchar_t** argv) { int rsp_file_index = -1; if (argc < 2) Fallback(); for (int i = 1; i < argc; ++i) { if (argv[i][0] == L'@') { rsp_file_index = i; break; } } if (rsp_file_index == -1) Fallback(L"couldn't find a response file in argv"); if (getenv("SPLIT_LINK_DEBUG")) { wstring backup_copy(&argv[rsp_file_index][1]); backup_copy += L".copy"; wchar_t buf[1024]; swprintf(buf, sizeof(buf), L"copy %s %s", &argv[rsp_file_index][1], backup_copy.c_str()); if (_wsystem(buf) == 0) wprintf(L"Saved original rsp as %s\n", backup_copy.c_str()); else wprintf(L"'%s' failed.", buf); } if (SplitLinkRequested(&argv[rsp_file_index][1])) { vector link_binary; link_binary.push_back(WPYTHON_PATH); link_binary.push_back(WSPLIT_LINK_SCRIPT_PATH); RunLinker(link_binary, NULL); } // Otherwise, run regular linker silently. Fallback(); }