1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include <windows.h> 6 #include <shlwapi.h> 7 8 #include <stdio.h> 9 #include <stdlib.h> 10 11 #include <algorithm> 12 #include <iterator> 13 #include <string> 14 #include <vector> 15 16 #ifndef SPLIT_LINK_SCRIPT_PATH 17 #error SPLIT_LINK_SCRIPT_PATH must be defined as the path to "split_link.py". 18 #endif 19 20 #ifndef PYTHON_PATH 21 #error PYTHON_PATH must be defined to be the path to the python binary. 22 #endif 23 24 #define WIDEN2(x) L ## x 25 #define WIDEN(x) WIDEN2(x) 26 #define WPYTHON_PATH WIDEN(PYTHON_PATH) 27 #define WSPLIT_LINK_SCRIPT_PATH WIDEN(SPLIT_LINK_SCRIPT_PATH) 28 29 using namespace std; 30 31 // Don't use stderr for errors because VS has large buffers on them, leading 32 // to confusing error output. 33 static void Fatal(const wchar_t* msg) { 34 wprintf(L"split_link fatal error: %s\n", msg); 35 exit(1); 36 } 37 38 static wstring ErrorMessageToString(DWORD err) { 39 wchar_t* msg_buf = NULL; 40 DWORD rc = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | 41 FORMAT_MESSAGE_FROM_SYSTEM, 42 NULL, 43 err, 44 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 45 reinterpret_cast<LPTSTR>(&msg_buf), 46 0, 47 NULL); 48 if (!rc) 49 return L"unknown error"; 50 wstring ret(msg_buf); 51 LocalFree(msg_buf); 52 return ret; 53 } 54 55 static void ArgvQuote(const std::wstring& argument, 56 std::wstring* command_line) { 57 // Don't quote unless we actually need to. 58 if (!argument.empty() && 59 argument.find_first_of(L" \t\n\v\"") == argument.npos) { 60 command_line->append(argument); 61 } else { 62 command_line->push_back(L'"'); 63 for (std::wstring::const_iterator it = argument.begin();; ++it) { 64 int num_backslashes = 0; 65 while (it != argument.end() && *it == L'\\') { 66 ++it; 67 ++num_backslashes; 68 } 69 if (it == argument.end()) { 70 // Escape all backslashes, but let the terminating double quotation 71 // mark we add below be interpreted as a metacharacter. 72 command_line->append(num_backslashes * 2, L'\\'); 73 break; 74 } else if (*it == L'"') { 75 // Escape all backslashes and the following double quotation mark. 76 command_line->append(num_backslashes * 2 + 1, L'\\'); 77 command_line->push_back(*it); 78 } else { 79 // Backslashes aren't special here. 80 command_line->append(num_backslashes, L'\\'); 81 command_line->push_back(*it); 82 } 83 } 84 command_line->push_back(L'"'); 85 } 86 } 87 88 // Does the opposite of CommandLineToArgvW. Suitable for CreateProcess, but 89 // not for cmd.exe. |args| should include the program name as argv[0]. 90 // See http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx 91 static wstring BuildCommandLine(const vector<wstring>& args) { 92 std::wstring result; 93 for (size_t i = 0; i < args.size(); ++i) { 94 ArgvQuote(args[i], &result); 95 if (i < args.size() - 1) { 96 result += L" "; 97 } 98 } 99 return result; 100 } 101 102 static void RunLinker(const vector<wstring>& prefix, const wchar_t* msg) { 103 if (msg) { 104 wprintf(L"split_link failed (%s), trying to fallback to standard link.\n", 105 msg); 106 wprintf(L"Original command line: %s\n", GetCommandLine()); 107 fflush(stdout); 108 } 109 110 STARTUPINFO startup_info = { sizeof(STARTUPINFO) }; 111 PROCESS_INFORMATION process_info; 112 DWORD exit_code; 113 114 GetStartupInfo(&startup_info); 115 116 if (getenv("SPLIT_LINK_DEBUG")) { 117 wprintf(L" original command line '%s'\n", GetCommandLine()); 118 fflush(stdout); 119 } 120 121 int num_args; 122 LPWSTR* args = CommandLineToArgvW(GetCommandLine(), &num_args); 123 if (!args) 124 Fatal(L"Couldn't parse command line."); 125 vector<wstring> argv; 126 argv.insert(argv.end(), prefix.begin(), prefix.end()); 127 for (int i = 1; i < num_args; ++i) // Skip old argv[0]. 128 argv.push_back(args[i]); 129 LocalFree(args); 130 131 wstring cmd = BuildCommandLine(argv); 132 133 if (getenv("SPLIT_LINK_DEBUG")) { 134 wprintf(L" running '%s'\n", cmd.c_str()); 135 fflush(stdout); 136 } 137 if (!CreateProcess(NULL, 138 &cmd[0], 139 NULL, 140 NULL, 141 TRUE, 142 0, 143 NULL, 144 NULL, 145 &startup_info, &process_info)) { 146 wstring error = ErrorMessageToString(GetLastError()); 147 Fatal(error.c_str()); 148 } 149 CloseHandle(process_info.hThread); 150 WaitForSingleObject(process_info.hProcess, INFINITE); 151 GetExitCodeProcess(process_info.hProcess, &exit_code); 152 CloseHandle(process_info.hProcess); 153 exit(exit_code); 154 } 155 156 static void Fallback(const wchar_t* msg) { 157 wchar_t original_link[1024]; 158 DWORD type; 159 DWORD size = sizeof(original_link); 160 if (SHGetValue(HKEY_CURRENT_USER, 161 L"Software\\Chromium\\split_link_installed", 162 NULL, 163 &type, 164 original_link, 165 &size) != ERROR_SUCCESS || type != REG_SZ) { 166 Fatal(L"Couldn't retrieve linker location from " 167 L"HKCU\\Software\\Chromium\\split_link_installed."); 168 } 169 if (getenv("SPLIT_LINK_DEBUG")) { 170 wprintf(L" got original linker '%s'\n", original_link); 171 fflush(stdout); 172 } 173 vector<wstring> link_binary; 174 link_binary.push_back(original_link); 175 RunLinker(link_binary, msg); 176 } 177 178 static void Fallback() { 179 Fallback(NULL); 180 } 181 182 static unsigned char* SlurpFile(const wchar_t* path, size_t* length) { 183 HANDLE file = CreateFile( 184 path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); 185 if (file == INVALID_HANDLE_VALUE) 186 Fallback(L"couldn't open file"); 187 LARGE_INTEGER file_size; 188 if (!GetFileSizeEx(file, &file_size)) 189 Fallback(L"couldn't get file size"); 190 *length = static_cast<size_t>(file_size.QuadPart); 191 unsigned char* buffer = static_cast<unsigned char*>(malloc(*length)); 192 DWORD bytes_read = 0; 193 if (!ReadFile(file, buffer, *length, &bytes_read, NULL)) 194 Fallback(L"couldn't read file"); 195 return buffer; 196 } 197 198 static bool SplitLinkRequested(const wchar_t* rsp_path) { 199 size_t length; 200 unsigned char* data = SlurpFile(rsp_path, &length); 201 bool flag_found = false; 202 if (data[0] == 0xff && data[1] == 0xfe) { 203 // UTF-16LE 204 wstring wide(reinterpret_cast<wchar_t*>(&data[2]), 205 length / sizeof(wchar_t) - 1); 206 flag_found = wide.find(L"/splitlink") != wide.npos; 207 } else { 208 string narrow(reinterpret_cast<char*>(data), length); 209 flag_found = narrow.find("/splitlink") != narrow.npos; 210 } 211 free(data); 212 return flag_found; 213 } 214 215 // If /splitlink is on the command line, delegate to split_link.py, otherwise 216 // fallback to standard linker. 217 int wmain(int argc, wchar_t** argv) { 218 int rsp_file_index = -1; 219 220 if (argc < 2) 221 Fallback(); 222 223 for (int i = 1; i < argc; ++i) { 224 if (argv[i][0] == L'@') { 225 rsp_file_index = i; 226 break; 227 } 228 } 229 230 if (rsp_file_index == -1) 231 Fallback(L"couldn't find a response file in argv"); 232 233 if (getenv("SPLIT_LINK_DEBUG")) { 234 wstring backup_copy(&argv[rsp_file_index][1]); 235 backup_copy += L".copy"; 236 wchar_t buf[1024]; 237 swprintf(buf, 238 sizeof(buf), 239 L"copy %s %s", 240 &argv[rsp_file_index][1], 241 backup_copy.c_str()); 242 if (_wsystem(buf) == 0) 243 wprintf(L"Saved original rsp as %s\n", backup_copy.c_str()); 244 else 245 wprintf(L"'%s' failed.", buf); 246 } 247 248 if (SplitLinkRequested(&argv[rsp_file_index][1])) { 249 vector<wstring> link_binary; 250 link_binary.push_back(WPYTHON_PATH); 251 link_binary.push_back(WSPLIT_LINK_SCRIPT_PATH); 252 RunLinker(link_binary, NULL); 253 } 254 255 // Otherwise, run regular linker silently. 256 Fallback(); 257 } 258