OLD | NEW |
| (Empty) |
1 // Copyright 2015 The Crashpad 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 #include "util/test/multiprocess_exec.h" | |
16 | |
17 #include "base/logging.h" | |
18 #include "base/strings/utf_string_conversions.h" | |
19 #include "gtest/gtest.h" | |
20 | |
21 namespace crashpad { | |
22 namespace test { | |
23 | |
24 namespace { | |
25 | |
26 // Ref: http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/
everyone-quotes-arguments-the-wrong-way.aspx | |
27 void AppendCommandLineArgument(const std::wstring& argument, | |
28 std::wstring* command_line) { | |
29 // Don't bother quoting if unnecessary. | |
30 if (!argument.empty() && | |
31 argument.find_first_of(L" \t\n\v\"") == std::wstring::npos) { | |
32 command_line->append(argument); | |
33 } else { | |
34 command_line->push_back(L'"'); | |
35 for (std::wstring::const_iterator i = argument.begin();; ++i) { | |
36 size_t backslash_count = 0; | |
37 while (i != argument.end() && *i == L'\\') { | |
38 ++i; | |
39 ++backslash_count; | |
40 } | |
41 if (i == argument.end()) { | |
42 // Escape all backslashes, but let the terminating double quotation mark | |
43 // we add below be interpreted as a metacharacter. | |
44 command_line->append(backslash_count * 2, L'\\'); | |
45 break; | |
46 } else if (*i == L'"') { | |
47 // Escape all backslashes and the following double quotation mark. | |
48 command_line->append(backslash_count * 2 + 1, L'\\'); | |
49 command_line->push_back(*i); | |
50 } else { | |
51 // Backslashes aren't special here. | |
52 command_line->append(backslash_count, L'\\'); | |
53 command_line->push_back(*i); | |
54 } | |
55 } | |
56 command_line->push_back(L'"'); | |
57 } | |
58 } | |
59 | |
60 } // namespace | |
61 | |
62 namespace internal { | |
63 | |
64 struct MultiprocessInfo { | |
65 MultiprocessInfo() {} | |
66 ScopedFileHANDLE pipe_c2p_read; | |
67 ScopedFileHANDLE pipe_c2p_write; | |
68 ScopedFileHANDLE pipe_p2c_read; | |
69 ScopedFileHANDLE pipe_p2c_write; | |
70 PROCESS_INFORMATION process_info; | |
71 }; | |
72 | |
73 } // namespace internal | |
74 | |
75 Multiprocess::Multiprocess() | |
76 : info_(nullptr), | |
77 code_(EXIT_SUCCESS), | |
78 reason_(kTerminationNormal) { | |
79 } | |
80 | |
81 void Multiprocess::Run() { | |
82 // Set up and spawn the child process. | |
83 ASSERT_NO_FATAL_FAILURE(PreFork()); | |
84 RunChild(); | |
85 | |
86 // And then run the parent actions in this process. | |
87 RunParent(); | |
88 | |
89 // Reap the child. | |
90 WaitForSingleObject(info_->process_info.hProcess, INFINITE); | |
91 CloseHandle(info_->process_info.hThread); | |
92 CloseHandle(info_->process_info.hProcess); | |
93 } | |
94 | |
95 Multiprocess::~Multiprocess() { | |
96 delete info_; | |
97 } | |
98 | |
99 void Multiprocess::PreFork() { | |
100 NOTREACHED(); | |
101 } | |
102 | |
103 FileHandle Multiprocess::ReadPipeHandle() const { | |
104 // This is the parent case, it's stdin in the child. | |
105 return info_->pipe_c2p_read.get(); | |
106 } | |
107 | |
108 FileHandle Multiprocess::WritePipeHandle() const { | |
109 // This is the parent case, it's stdout in the child. | |
110 return info_->pipe_p2c_write.get(); | |
111 } | |
112 | |
113 void Multiprocess::CloseReadPipe() { | |
114 info_->pipe_c2p_read.reset(); | |
115 } | |
116 | |
117 void Multiprocess::CloseWritePipe() { | |
118 info_->pipe_p2c_write.reset(); | |
119 } | |
120 | |
121 void Multiprocess::RunParent() { | |
122 MultiprocessParent(); | |
123 | |
124 info_->pipe_c2p_read.reset(); | |
125 info_->pipe_p2c_write.reset(); | |
126 } | |
127 | |
128 void Multiprocess::RunChild() { | |
129 MultiprocessChild(); | |
130 | |
131 info_->pipe_c2p_write.reset(); | |
132 info_->pipe_p2c_read.reset(); | |
133 } | |
134 | |
135 MultiprocessExec::MultiprocessExec() | |
136 : Multiprocess(), command_(), arguments_(), command_line_() { | |
137 } | |
138 | |
139 void MultiprocessExec::SetChildCommand( | |
140 const std::string& command, | |
141 const std::vector<std::string>* arguments) { | |
142 command_ = command; | |
143 if (arguments) { | |
144 arguments_ = *arguments; | |
145 } else { | |
146 arguments_.clear(); | |
147 } | |
148 } | |
149 | |
150 MultiprocessExec::~MultiprocessExec() { | |
151 } | |
152 | |
153 void MultiprocessExec::PreFork() { | |
154 ASSERT_FALSE(command_.empty()); | |
155 | |
156 command_line_.clear(); | |
157 AppendCommandLineArgument(base::UTF8ToUTF16(command_), &command_line_); | |
158 for (size_t i = 0; i < arguments_.size(); ++i) { | |
159 command_line_ += L" "; | |
160 AppendCommandLineArgument(base::UTF8ToUTF16(arguments_[i]), &command_line_); | |
161 } | |
162 | |
163 // Make pipes for child-to-parent and parent-to-child communication. Mark them | |
164 // as inheritable via the SECURITY_ATTRIBUTES, but use SetHandleInformation to | |
165 // ensure that the parent sides are not inherited. | |
166 ASSERT_EQ(nullptr, info()); | |
167 set_info(new internal::MultiprocessInfo()); | |
168 | |
169 SECURITY_ATTRIBUTES security_attributes = {0}; | |
170 security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES); | |
171 security_attributes.bInheritHandle = TRUE; | |
172 | |
173 HANDLE c2p_read, c2p_write; | |
174 PCHECK(CreatePipe(&c2p_read, &c2p_write, &security_attributes, 0)); | |
175 PCHECK(SetHandleInformation(c2p_read, HANDLE_FLAG_INHERIT, 0)); | |
176 info()->pipe_c2p_read.reset(c2p_read); | |
177 info()->pipe_c2p_write.reset(c2p_write); | |
178 | |
179 HANDLE p2c_read, p2c_write; | |
180 PCHECK(CreatePipe(&p2c_read, &p2c_write, &security_attributes, 0)); | |
181 PCHECK(SetHandleInformation(p2c_write, HANDLE_FLAG_INHERIT, 0)); | |
182 info()->pipe_p2c_read.reset(p2c_read); | |
183 info()->pipe_p2c_write.reset(p2c_write); | |
184 } | |
185 | |
186 void MultiprocessExec::MultiprocessChild() { | |
187 STARTUPINFO startup_info = {0}; | |
188 startup_info.cb = sizeof(startup_info); | |
189 startup_info.hStdInput = info()->pipe_p2c_read.get(); | |
190 startup_info.hStdOutput = info()->pipe_c2p_write.get(); | |
191 startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE); | |
192 startup_info.dwFlags = STARTF_USESTDHANDLES; | |
193 PCHECK(CreateProcess(base::UTF8ToUTF16(command_).c_str(), | |
194 &command_line_[0], // This cannot be constant, per MSDN. | |
195 nullptr, | |
196 nullptr, | |
197 TRUE, | |
198 0, | |
199 nullptr, | |
200 nullptr, | |
201 &startup_info, | |
202 &info()->process_info)); | |
203 } | |
204 | |
205 } // namespace test | |
206 } // namespace crashpad | |
OLD | NEW |