Laszlo Csomor | dcc80fb | 2019-08-09 05:05:21 -0700 | [diff] [blame] | 1 | // Copyright 2019 The Bazel Authors. All rights reserved. |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | #ifndef WIN32_LEAN_AND_MEAN |
| 16 | #define WIN32_LEAN_AND_MEAN |
| 17 | #endif |
| 18 | #include "src/main/native/windows/process.h" |
| 19 | |
| 20 | #include <wchar.h> |
| 21 | #include <windows.h> |
| 22 | |
| 23 | #include <memory> |
| 24 | #include <string> |
| 25 | #include <vector> |
| 26 | |
| 27 | #include "gtest/gtest.h" |
| 28 | #include "src/main/cpp/util/path.h" |
| 29 | #include "src/main/native/windows/util.h" |
| 30 | #include "tools/cpp/runfiles/runfiles.h" |
| 31 | |
| 32 | namespace { |
| 33 | |
| 34 | // Asserts argument escaping for subprocesses. |
| 35 | // |
| 36 | // For each pair in 'args', this method: |
| 37 | // 1. asserts that WindowsEscapeArg(pair.first) == pair.second |
| 38 | // 2. asserts that passing pair.second to a subprocess results in the subprocess |
| 39 | // receiving pair.first |
| 40 | // |
| 41 | // The method performs the second assertion by running "printarg.exe" (a |
| 42 | // data-dependency of this test) once for each argument. |
| 43 | void AssertSubprocessReceivesArgsAsIntended( |
| 44 | const std::vector<std::pair<std::wstring, std::wstring> >& args) { |
| 45 | // Assert that the WindowsEscapeArg produces what we expect. |
| 46 | for (const auto& i : args) { |
| 47 | ASSERT_EQ(bazel::windows::WindowsEscapeArg(i.first), i.second); |
| 48 | } |
| 49 | |
| 50 | // Create a Runfiles object. |
| 51 | std::string error; |
| 52 | std::unique_ptr<bazel::tools::cpp::runfiles::Runfiles> runfiles( |
| 53 | bazel::tools::cpp::runfiles::Runfiles::CreateForTest(&error)); |
| 54 | ASSERT_NE(runfiles.get(), nullptr) << error; |
| 55 | |
| 56 | // Look up the path of the printarg.exe utility. |
| 57 | std::string printarg = |
| 58 | runfiles->Rlocation("io_bazel/src/test/native/windows/printarg.exe"); |
| 59 | ASSERT_NE(printarg, ""); |
| 60 | |
| 61 | // Convert printarg.exe's path to a wchar_t Windows path. |
| 62 | std::wstring wprintarg; |
| 63 | bool success = |
| 64 | blaze_util::AsAbsoluteWindowsPath(printarg, &wprintarg, &error); |
| 65 | ASSERT_TRUE(success) << error; |
| 66 | |
| 67 | // SECURITY_ATTRIBUTES for inheritable HANDLEs. |
| 68 | SECURITY_ATTRIBUTES sa; |
| 69 | sa.nLength = sizeof(sa); |
Vertexwahn | 26c7e10 | 2021-03-10 07:25:59 -0800 | [diff] [blame] | 70 | sa.lpSecurityDescriptor = nullptr; |
Laszlo Csomor | dcc80fb | 2019-08-09 05:05:21 -0700 | [diff] [blame] | 71 | sa.bInheritHandle = TRUE; |
| 72 | |
| 73 | // Open /dev/null that will be redirected into the subprocess' stdin. |
| 74 | bazel::windows::AutoHandle devnull( |
| 75 | CreateFileW(L"NUL", GENERIC_READ, |
| 76 | FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, &sa, |
Vertexwahn | 26c7e10 | 2021-03-10 07:25:59 -0800 | [diff] [blame] | 77 | OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)); |
Laszlo Csomor | dcc80fb | 2019-08-09 05:05:21 -0700 | [diff] [blame] | 78 | ASSERT_TRUE(devnull.IsValid()); |
| 79 | |
| 80 | // Create a pipe that the subprocess' stdout will be redirected to. |
| 81 | HANDLE pipe_read_h, pipe_write_h; |
| 82 | if (!CreatePipe(&pipe_read_h, &pipe_write_h, &sa, 0x10000)) { |
| 83 | DWORD err = GetLastError(); |
| 84 | ASSERT_EQ(err, 0); |
| 85 | } |
| 86 | bazel::windows::AutoHandle pipe_read(pipe_read_h), pipe_write(pipe_write_h); |
| 87 | |
| 88 | // Duplicate stderr, where the subprocess' stderr will be redirected to. |
| 89 | HANDLE stderr_h; |
| 90 | if (!DuplicateHandle(GetCurrentProcess(), GetStdHandle(STD_ERROR_HANDLE), |
| 91 | GetCurrentProcess(), &stderr_h, 0, TRUE, |
| 92 | DUPLICATE_SAME_ACCESS)) { |
| 93 | DWORD err = GetLastError(); |
| 94 | ASSERT_EQ(err, 0); |
| 95 | } |
| 96 | bazel::windows::AutoHandle stderr_dup(stderr_h); |
| 97 | |
| 98 | // Create the attribute object for the process creation. This object describes |
| 99 | // exactly which handles the subprocess shall inherit. |
| 100 | STARTUPINFOEXW startupInfo; |
| 101 | std::unique_ptr<bazel::windows::AutoAttributeList> attrs; |
| 102 | std::wstring werror; |
| 103 | ASSERT_TRUE(bazel::windows::AutoAttributeList::Create( |
| 104 | devnull, pipe_write, stderr_dup, &attrs, &werror)); |
| 105 | attrs->InitStartupInfoExW(&startupInfo); |
| 106 | |
| 107 | // MSDN says the maximum command line is 32767 characters, with a null |
| 108 | // terminator that is exactly 2^15 (= 0x8000). |
| 109 | static constexpr size_t kMaxCmdline = 0x8000; |
| 110 | wchar_t cmdline[kMaxCmdline]; |
| 111 | |
| 112 | // Copy printarg.exe's escaped path into the 'cmdline', and append a space. |
| 113 | // We will append arguments to this command line in the for-loop below. |
| 114 | wprintarg = bazel::windows::WindowsEscapeArg(wprintarg); |
| 115 | wcsncpy(cmdline, wprintarg.c_str(), wprintarg.size()); |
| 116 | wchar_t* pcmdline = cmdline + wprintarg.size(); |
| 117 | *pcmdline++ = L' '; |
| 118 | |
| 119 | // Run a subprocess for each of the arguments and assert that the argument |
| 120 | // arrived to the subprocess as intended. |
| 121 | for (const auto& i : args) { |
| 122 | // We already asserted for every element that WindowsEscapeArg(i.first) |
| 123 | // produces the same output as i.second, so just use i.second instead of |
| 124 | // converting i.first again. |
| 125 | wcsncpy(pcmdline, i.second.c_str(), i.second.size()); |
| 126 | pcmdline[i.second.size()] = 0; |
| 127 | |
| 128 | // Run the subprocess. |
| 129 | PROCESS_INFORMATION processInfo; |
| 130 | BOOL ok = CreateProcessW( |
Vertexwahn | 26c7e10 | 2021-03-10 07:25:59 -0800 | [diff] [blame] | 131 | nullptr, cmdline, nullptr, nullptr, TRUE, |
| 132 | CREATE_UNICODE_ENVIRONMENT | EXTENDED_STARTUPINFO_PRESENT, nullptr, |
| 133 | nullptr, &startupInfo.StartupInfo, &processInfo); |
Laszlo Csomor | dcc80fb | 2019-08-09 05:05:21 -0700 | [diff] [blame] | 134 | if (!ok) { |
| 135 | DWORD err = GetLastError(); |
| 136 | ASSERT_EQ(err, 0); |
| 137 | } |
| 138 | CloseHandle(processInfo.hThread); |
| 139 | bazel::windows::AutoHandle process(processInfo.hProcess); |
| 140 | |
| 141 | // Wait for the subprocess to exit. Timeout is 5 seconds, which should be |
| 142 | // more than enough for the subprocess to finish. |
| 143 | ASSERT_EQ(WaitForSingleObject(process, 5000), WAIT_OBJECT_0); |
| 144 | |
| 145 | // The subprocess printed its argv[1] (without a newline) to its stdout, |
| 146 | // which is redirected into the pipe. |
| 147 | // Let's write a null-terminator to the pipe to separate the output from the |
| 148 | // output of the subsequent subprocess. The null-terminator also yields |
| 149 | // null-terminated strings in the pipe, making it easy to read them out |
| 150 | // later. |
| 151 | DWORD dummy; |
Vertexwahn | 26c7e10 | 2021-03-10 07:25:59 -0800 | [diff] [blame] | 152 | ASSERT_TRUE(WriteFile(pipe_write, "\0", 1, &dummy, nullptr)); |
Laszlo Csomor | dcc80fb | 2019-08-09 05:05:21 -0700 | [diff] [blame] | 153 | } |
| 154 | |
| 155 | // Read the output of the subprocesses from the pipe. They are divided by |
| 156 | // null-terminators, so 'buf' will contain a sequence of null-terminated |
| 157 | // strings. We close the writing end so that ReadFile won't block until the |
| 158 | // desired amount of bytes is available. |
| 159 | DWORD total_output_len; |
| 160 | char buf[0x10000]; |
| 161 | pipe_write = INVALID_HANDLE_VALUE; |
Vertexwahn | 26c7e10 | 2021-03-10 07:25:59 -0800 | [diff] [blame] | 162 | if (!ReadFile(pipe_read, buf, 0x10000, &total_output_len, nullptr)) { |
Laszlo Csomor | dcc80fb | 2019-08-09 05:05:21 -0700 | [diff] [blame] | 163 | DWORD err = GetLastError(); |
| 164 | ASSERT_EQ(err, 0); |
| 165 | } |
| 166 | |
| 167 | // Assert that the subprocesses produced exactly the *unescaped* arguments. |
| 168 | size_t start = 0; |
| 169 | for (const auto& arg : args) { |
| 170 | // Assert that there was enough data produced by the subprocesses. |
| 171 | ASSERT_LT(start, total_output_len); |
| 172 | |
| 173 | // Find the output of the corresponding subprocess. Since all subprocesses |
| 174 | // printed into the same pipe and we added null-terminators between them, |
| 175 | // the output is already there, conveniently as a null-terminated string. |
| 176 | std::string actual_arg(buf + start); |
| 177 | start += actual_arg.size() + 1; |
| 178 | |
| 179 | // 'args' contains wchar_t strings, but the subprocesses printed ASCII |
| 180 | // (char) strings. To compare, we convert arg.first to a char-string. |
| 181 | std::string expected_arg; |
| 182 | expected_arg.reserve(arg.first.size()); |
| 183 | for (const auto& wc : arg.first) { |
| 184 | expected_arg.append(1, static_cast<char>(wc)); |
| 185 | } |
| 186 | |
| 187 | // Assert that the subprocess printed exactly the *unescaped* argument. |
| 188 | EXPECT_EQ(expected_arg, actual_arg); |
| 189 | } |
| 190 | } |
| 191 | |
| 192 | TEST(ProcessTest, WindowsEscapeArgTest) { |
| 193 | AssertSubprocessReceivesArgsAsIntended({ |
| 194 | {L"", L"\"\""}, |
| 195 | {L" ", L"\" \""}, |
| 196 | {L"\"", L"\"\\\"\""}, |
| 197 | {L"\"\\", L"\"\\\"\\\\\""}, |
| 198 | {L"\\", L"\\"}, |
| 199 | {L"\\\"", L"\"\\\\\\\"\""}, |
| 200 | {L"with space", L"\"with space\""}, |
| 201 | {L"with^caret", L"with^caret"}, |
| 202 | {L"space ^caret", L"\"space ^caret\""}, |
| 203 | {L"caret^ space", L"\"caret^ space\""}, |
| 204 | {L"with\"quote", L"\"with\\\"quote\""}, |
| 205 | {L"with\\backslash", L"with\\backslash"}, |
| 206 | {L"one\\ backslash and \\space", L"\"one\\ backslash and \\space\""}, |
| 207 | {L"two\\\\backslashes", L"two\\\\backslashes"}, |
| 208 | {L"two\\\\ backslashes \\\\and space", |
| 209 | L"\"two\\\\ backslashes \\\\and space\""}, |
| 210 | {L"one\\\"x", L"\"one\\\\\\\"x\""}, |
| 211 | {L"two\\\\\"x", L"\"two\\\\\\\\\\\"x\""}, |
| 212 | {L"a \\ b", L"\"a \\ b\""}, |
| 213 | {L"a \\\" b", L"\"a \\\\\\\" b\""}, |
| 214 | {L"A", L"A"}, |
| 215 | {L"\"a\"", L"\"\\\"a\\\"\""}, |
| 216 | {L"B C", L"\"B C\""}, |
| 217 | {L"\"b c\"", L"\"\\\"b c\\\"\""}, |
| 218 | {L"D\"E", L"\"D\\\"E\""}, |
| 219 | {L"\"d\"e\"", L"\"\\\"d\\\"e\\\"\""}, |
| 220 | {L"C:\\F G", L"\"C:\\F G\""}, |
| 221 | {L"\"C:\\f g\"", L"\"\\\"C:\\f g\\\"\""}, |
| 222 | {L"C:\\H\"I", L"\"C:\\H\\\"I\""}, |
| 223 | {L"\"C:\\h\"i\"", L"\"\\\"C:\\h\\\"i\\\"\""}, |
| 224 | {L"C:\\J\\\"K", L"\"C:\\J\\\\\\\"K\""}, |
| 225 | {L"\"C:\\j\\\"k\"", L"\"\\\"C:\\j\\\\\\\"k\\\"\""}, |
| 226 | {L"C:\\L M ", L"\"C:\\L M \""}, |
| 227 | {L"\"C:\\l m \"", L"\"\\\"C:\\l m \\\"\""}, |
| 228 | {L"C:\\N O\\", L"\"C:\\N O\\\\\""}, |
| 229 | {L"\"C:\\n o\\\"", L"\"\\\"C:\\n o\\\\\\\"\""}, |
| 230 | {L"C:\\P Q\\ ", L"\"C:\\P Q\\ \""}, |
| 231 | {L"\"C:\\p q\\ \"", L"\"\\\"C:\\p q\\ \\\"\""}, |
| 232 | {L"C:\\R\\S\\", L"C:\\R\\S\\"}, |
| 233 | {L"C:\\R x\\S\\", L"\"C:\\R x\\S\\\\\""}, |
| 234 | {L"\"C:\\r\\s\\\"", L"\"\\\"C:\\r\\s\\\\\\\"\""}, |
| 235 | {L"\"C:\\r x\\s\\\"", L"\"\\\"C:\\r x\\s\\\\\\\"\""}, |
| 236 | {L"C:\\T U\\W\\", L"\"C:\\T U\\W\\\\\""}, |
| 237 | {L"\"C:\\t u\\w\\\"", L"\"\\\"C:\\t u\\w\\\\\\\"\""}, |
| 238 | }); |
| 239 | } |
| 240 | |
| 241 | } // namespace |