• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2012 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 "base/test/multiprocess_test.h"
6 
7 #include <errno.h>
8 #include <string.h>
9 #include <sys/types.h>
10 #include <sys/socket.h>
11 #include <unistd.h>
12 
13 #include <memory>
14 #include <utility>
15 #include <vector>
16 
17 #include "base/base_switches.h"
18 #include "base/command_line.h"
19 #include "base/containers/hash_tables.h"
20 #include "base/lazy_instance.h"
21 #include "base/logging.h"
22 #include "base/macros.h"
23 #include "base/pickle.h"
24 #include "base/posix/global_descriptors.h"
25 #include "base/posix/unix_domain_socket_linux.h"
26 #include "testing/multiprocess_func_list.h"
27 
28 namespace base {
29 
30 namespace {
31 
32 const int kMaxMessageSize = 1024 * 1024;
33 const int kFragmentSize = 4096;
34 
35 // Message sent between parent process and helper child process.
36 enum class MessageType : uint32_t {
37   START_REQUEST,
38   START_RESPONSE,
39   WAIT_REQUEST,
40   WAIT_RESPONSE,
41 };
42 
43 struct MessageHeader {
44   uint32_t size;
45   MessageType type;
46 };
47 
48 struct StartProcessRequest {
49   MessageHeader header =
50       {sizeof(StartProcessRequest), MessageType::START_REQUEST};
51 
52   uint32_t num_args = 0;
53   uint32_t num_fds = 0;
54 };
55 
56 struct StartProcessResponse {
57   MessageHeader header =
58       {sizeof(StartProcessResponse), MessageType::START_RESPONSE};
59 
60   pid_t child_pid;
61 };
62 
63 struct WaitProcessRequest {
64   MessageHeader header =
65       {sizeof(WaitProcessRequest), MessageType::WAIT_REQUEST};
66 
67   pid_t pid;
68   uint64_t timeout_ms;
69 };
70 
71 struct WaitProcessResponse {
72   MessageHeader header =
73       {sizeof(WaitProcessResponse), MessageType::WAIT_RESPONSE};
74 
75   bool success = false;
76   int32_t exit_code = 0;
77 };
78 
79 // Helper class that implements an alternate test child launcher for
80 // multi-process tests. The default implementation doesn't work if the child is
81 // launched after starting threads. However, for some tests (i.e. Mojo), this
82 // is necessary. This implementation works around that issue by forking a helper
83 // process very early in main(), before any real work is done. Then, when a
84 // child needs to be spawned, a message is sent to that helper process, which
85 // then forks and returns the result to the parent. The forked child then calls
86 // main() and things look as though a brand new process has been fork/exec'd.
87 class LaunchHelper {
88  public:
89   using MainFunction = int (*)(int, char**);
90 
LaunchHelper()91   LaunchHelper() {}
92 
93   // Initialise the alternate test child implementation.
94   void Init(MainFunction main);
95 
96   // Starts a child test helper process.
97   Process StartChildTestHelper(const std::string& procname,
98                                const CommandLine& base_command_line,
99                                const LaunchOptions& options);
100 
101   // Waits for a child test helper process.
102   bool WaitForChildExitWithTimeout(const Process& process, TimeDelta timeout,
103                                    int* exit_code);
104 
IsReady() const105   bool IsReady() const { return child_fd_ != -1; }
IsChild() const106   bool IsChild() const { return is_child_; }
107 
108  private:
109   // Wrappers around sendmsg/recvmsg that supports message fragmentation.
110   void Send(int fd, const MessageHeader* msg, const std::vector<int>& fds);
111   ssize_t Recv(int fd, void* buf, std::vector<ScopedFD>* fds);
112 
113   // Parent process implementation.
114   void DoParent(int fd);
115   // Helper process implementation.
116   void DoHelper(int fd);
117 
118   void StartProcessInHelper(const StartProcessRequest* request,
119                            std::vector<ScopedFD> fds);
120   void WaitForChildInHelper(const WaitProcessRequest* request);
121 
122   bool is_child_ = false;
123 
124   // Parent vars.
125   int child_fd_ = -1;
126 
127   // Helper vars.
128   int parent_fd_ = -1;
129   MainFunction main_ = nullptr;
130 
131   DISALLOW_COPY_AND_ASSIGN(LaunchHelper);
132 };
133 
Init(MainFunction main)134 void LaunchHelper::Init(MainFunction main) {
135   main_ = main;
136 
137   // Create a communication channel between the parent and child launch helper.
138   // fd[0] belongs to the parent, fd[1] belongs to the child.
139   int fds[2] = {-1, -1};
140   int rv = socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds);
141   PCHECK(rv == 0);
142   CHECK_NE(-1, fds[0]);
143   CHECK_NE(-1, fds[1]);
144 
145   pid_t pid = fork();
146   PCHECK(pid >= 0) << "Fork failed";
147   if (pid) {
148     // Parent.
149     rv = close(fds[1]);
150     PCHECK(rv == 0);
151     DoParent(fds[0]);
152   } else {
153     // Helper.
154     rv = close(fds[0]);
155     PCHECK(rv == 0);
156     DoHelper(fds[1]);
157     NOTREACHED();
158     _exit(0);
159   }
160 }
161 
Send(int fd,const MessageHeader * msg,const std::vector<int> & fds)162 void LaunchHelper::Send(
163     int fd, const MessageHeader* msg, const std::vector<int>& fds) {
164   uint32_t bytes_remaining = msg->size;
165   const char* buf = reinterpret_cast<const char*>(msg);
166   while (bytes_remaining) {
167     size_t send_size =
168         (bytes_remaining > kFragmentSize) ? kFragmentSize : bytes_remaining;
169     bool success = UnixDomainSocket::SendMsg(
170         fd, buf, send_size,
171         (bytes_remaining == msg->size) ? fds : std::vector<int>());
172     CHECK(success);
173     bytes_remaining -= send_size;
174     buf += send_size;
175   }
176 }
177 
Recv(int fd,void * buf,std::vector<ScopedFD> * fds)178 ssize_t LaunchHelper::Recv(int fd, void* buf, std::vector<ScopedFD>* fds) {
179   ssize_t size = UnixDomainSocket::RecvMsg(fd, buf, kFragmentSize, fds);
180   if (size <= 0)
181     return size;
182 
183   const MessageHeader* header = reinterpret_cast<const MessageHeader*>(buf);
184   CHECK(header->size < kMaxMessageSize);
185   uint32_t bytes_remaining = header->size - size;
186   char* buffer = reinterpret_cast<char*>(buf);
187   buffer += size;
188   while (bytes_remaining) {
189     std::vector<ScopedFD> dummy_fds;
190     size = UnixDomainSocket::RecvMsg(fd, buffer, kFragmentSize, &dummy_fds);
191     if (size <= 0)
192       return size;
193 
194     CHECK(dummy_fds.empty());
195     CHECK(size == kFragmentSize ||
196           static_cast<size_t>(size) == bytes_remaining);
197     bytes_remaining -= size;
198     buffer += size;
199   }
200   return header->size;
201 }
202 
DoParent(int fd)203 void LaunchHelper::DoParent(int fd) {
204   child_fd_ = fd;
205 }
206 
DoHelper(int fd)207 void LaunchHelper::DoHelper(int fd) {
208   parent_fd_ = fd;
209   is_child_ = true;
210   std::unique_ptr<char[]> buf(new char[kMaxMessageSize]);
211   while (true) {
212     // Wait for a message from the parent.
213     std::vector<ScopedFD> fds;
214     ssize_t size = Recv(parent_fd_, buf.get(), &fds);
215     if (size == 0 || (size < 0 && errno == ECONNRESET)) {
216       _exit(0);
217     }
218     PCHECK(size > 0);
219 
220     const MessageHeader* header =
221         reinterpret_cast<const MessageHeader*>(buf.get());
222     CHECK_EQ(static_cast<ssize_t>(header->size), size);
223     switch (header->type) {
224       case MessageType::START_REQUEST:
225         StartProcessInHelper(
226             reinterpret_cast<const StartProcessRequest*>(buf.get()),
227             std::move(fds));
228         break;
229       case MessageType::WAIT_REQUEST:
230         WaitForChildInHelper(
231             reinterpret_cast<const WaitProcessRequest*>(buf.get()));
232         break;
233       default:
234         LOG(FATAL) << "Unsupported message type: "
235                    << static_cast<uint32_t>(header->type);
236     }
237   }
238 }
239 
StartProcessInHelper(const StartProcessRequest * request,std::vector<ScopedFD> fds)240 void LaunchHelper::StartProcessInHelper(const StartProcessRequest* request,
241                                         std::vector<ScopedFD> fds) {
242   pid_t pid = fork();
243   PCHECK(pid >= 0) << "Fork failed";
244   if (pid) {
245     // Helper.
246     StartProcessResponse resp;
247     resp.child_pid = pid;
248     Send(parent_fd_, reinterpret_cast<const MessageHeader*>(&resp),
249          std::vector<int>());
250   } else {
251     // Child.
252     PCHECK(close(parent_fd_) == 0);
253     parent_fd_ = -1;
254     CommandLine::Reset();
255 
256     Pickle serialised_extra(reinterpret_cast<const char*>(request + 1),
257                             request->header.size - sizeof(StartProcessRequest));
258     PickleIterator iter(serialised_extra);
259     std::vector<std::string> args;
260     for (size_t i = 0; i < request->num_args; i++) {
261       std::string arg;
262       CHECK(iter.ReadString(&arg));
263       args.push_back(std::move(arg));
264     }
265 
266     CHECK_EQ(request->num_fds, fds.size());
267     for (size_t i = 0; i < request->num_fds; i++) {
268       int new_fd;
269       CHECK(iter.ReadInt(&new_fd));
270       int old_fd = fds[i].release();
271       if (new_fd != old_fd) {
272         if (dup2(old_fd, new_fd) < 0) {
273           PLOG(FATAL) << "dup2";
274         }
275         PCHECK(close(old_fd) == 0);
276       }
277     }
278 
279     // argv has argc+1 elements, where the last element is NULL.
280     std::unique_ptr<char*[]> argv(new char*[args.size() + 1]);
281     for (size_t i = 0; i < args.size(); i++) {
282       argv[i] = const_cast<char*>(args[i].c_str());
283     }
284     argv[args.size()] = nullptr;
285     _exit(main_(args.size(), argv.get()));
286     NOTREACHED();
287   }
288 }
289 
WaitForChildInHelper(const WaitProcessRequest * request)290 void LaunchHelper::WaitForChildInHelper(const WaitProcessRequest* request) {
291   Process process(request->pid);
292   TimeDelta timeout = TimeDelta::FromMilliseconds(request->timeout_ms);
293   int exit_code = -1;
294   bool success = process.WaitForExitWithTimeout(timeout, &exit_code);
295 
296   WaitProcessResponse resp;
297   resp.exit_code = exit_code;
298   resp.success = success;
299   Send(parent_fd_, reinterpret_cast<const MessageHeader*>(&resp),
300        std::vector<int>());
301 }
302 
StartChildTestHelper(const std::string & procname,const CommandLine & base_command_line,const LaunchOptions & options)303 Process LaunchHelper::StartChildTestHelper(const std::string& procname,
304                                            const CommandLine& base_command_line,
305                                            const LaunchOptions& options) {
306 
307   CommandLine command_line(base_command_line);
308   if (!command_line.HasSwitch(switches::kTestChildProcess))
309     command_line.AppendSwitchASCII(switches::kTestChildProcess, procname);
310 
311   StartProcessRequest request;
312   Pickle serialised_extra;
313   const CommandLine::StringVector& argv = command_line.argv();
314   for (const auto& arg : argv)
315     CHECK(serialised_extra.WriteString(arg));
316   request.num_args = argv.size();
317 
318   std::vector<int> fds_to_send;
319   if (options.fds_to_remap) {
320     for (auto p : *options.fds_to_remap) {
321       CHECK(serialised_extra.WriteInt(p.second));
322       fds_to_send.push_back(p.first);
323     }
324     request.num_fds = options.fds_to_remap->size();
325   }
326 
327   size_t buf_size = sizeof(StartProcessRequest) + serialised_extra.size();
328   request.header.size = buf_size;
329   std::unique_ptr<char[]> buffer(new char[buf_size]);
330   memcpy(buffer.get(), &request, sizeof(StartProcessRequest));
331   memcpy(buffer.get() + sizeof(StartProcessRequest), serialised_extra.data(),
332          serialised_extra.size());
333 
334   // Send start message.
335   Send(child_fd_, reinterpret_cast<const MessageHeader*>(buffer.get()),
336        fds_to_send);
337 
338   // Synchronously get response.
339   StartProcessResponse response;
340   std::vector<ScopedFD> recv_fds;
341   ssize_t resp_size = Recv(child_fd_, &response, &recv_fds);
342   PCHECK(resp_size == sizeof(StartProcessResponse));
343 
344   return Process(response.child_pid);
345 }
346 
WaitForChildExitWithTimeout(const Process & process,TimeDelta timeout,int * exit_code)347 bool LaunchHelper::WaitForChildExitWithTimeout(
348     const Process& process, TimeDelta timeout, int* exit_code) {
349 
350   WaitProcessRequest request;
351   request.pid = process.Handle();
352   request.timeout_ms = timeout.InMilliseconds();
353 
354   Send(child_fd_, reinterpret_cast<const MessageHeader*>(&request),
355        std::vector<int>());
356 
357   WaitProcessResponse response;
358   std::vector<ScopedFD> recv_fds;
359   ssize_t resp_size = Recv(child_fd_, &response, &recv_fds);
360   PCHECK(resp_size == sizeof(WaitProcessResponse));
361 
362   if (!response.success)
363     return false;
364 
365   *exit_code = response.exit_code;
366   return true;
367 }
368 
369 LazyInstance<LaunchHelper>::Leaky g_launch_helper;
370 
371 }  // namespace
372 
InitAndroidMultiProcessTestHelper(int (* main)(int,char **))373 void InitAndroidMultiProcessTestHelper(int (*main)(int, char**)) {
374   DCHECK(main);
375   // Don't allow child processes to themselves create new child processes.
376   if (g_launch_helper.Get().IsChild())
377     return;
378   g_launch_helper.Get().Init(main);
379 }
380 
AndroidIsChildProcess()381 bool AndroidIsChildProcess() {
382   return g_launch_helper.Get().IsChild();
383 }
384 
AndroidWaitForChildExitWithTimeout(const Process & process,TimeDelta timeout,int * exit_code)385 bool AndroidWaitForChildExitWithTimeout(
386     const Process& process, TimeDelta timeout, int* exit_code) {
387   CHECK(g_launch_helper.Get().IsReady());
388   return g_launch_helper.Get().WaitForChildExitWithTimeout(
389       process, timeout, exit_code);
390 }
391 
392 // A very basic implementation for Android. On Android tests can run in an APK
393 // and we don't have an executable to exec*. This implementation does the bare
394 // minimum to execute the method specified by procname (in the child process).
395 //  - All options except |fds_to_remap| are ignored.
SpawnMultiProcessTestChild(const std::string & procname,const CommandLine & base_command_line,const LaunchOptions & options)396 Process SpawnMultiProcessTestChild(const std::string& procname,
397                                    const CommandLine& base_command_line,
398                                    const LaunchOptions& options) {
399   if (g_launch_helper.Get().IsReady()) {
400     return g_launch_helper.Get().StartChildTestHelper(
401         procname, base_command_line, options);
402   }
403 
404   // TODO(viettrungluu): The FD-remapping done below is wrong in the presence of
405   // cycles (e.g., fd1 -> fd2, fd2 -> fd1). crbug.com/326576
406   FileHandleMappingVector empty;
407   const FileHandleMappingVector* fds_to_remap =
408       options.fds_to_remap ? options.fds_to_remap : &empty;
409 
410   pid_t pid = fork();
411 
412   if (pid < 0) {
413     PLOG(ERROR) << "fork";
414     return Process();
415   }
416   if (pid > 0) {
417     // Parent process.
418     return Process(pid);
419   }
420   // Child process.
421   base::hash_set<int> fds_to_keep_open;
422   for (FileHandleMappingVector::const_iterator it = fds_to_remap->begin();
423        it != fds_to_remap->end(); ++it) {
424     fds_to_keep_open.insert(it->first);
425   }
426   // Keep standard FDs (stdin, stdout, stderr, etc.) open since this
427   // is not meant to spawn a daemon.
428   int base = GlobalDescriptors::kBaseDescriptor;
429   for (int fd = base; fd < sysconf(_SC_OPEN_MAX); ++fd) {
430     if (fds_to_keep_open.find(fd) == fds_to_keep_open.end()) {
431       close(fd);
432     }
433   }
434   for (FileHandleMappingVector::const_iterator it = fds_to_remap->begin();
435        it != fds_to_remap->end(); ++it) {
436     int old_fd = it->first;
437     int new_fd = it->second;
438     if (dup2(old_fd, new_fd) < 0) {
439       PLOG(FATAL) << "dup2";
440     }
441     close(old_fd);
442   }
443   CommandLine::Reset();
444   CommandLine::Init(0, nullptr);
445   CommandLine* command_line = CommandLine::ForCurrentProcess();
446   command_line->InitFromArgv(base_command_line.argv());
447   if (!command_line->HasSwitch(switches::kTestChildProcess))
448     command_line->AppendSwitchASCII(switches::kTestChildProcess, procname);
449 
450   _exit(multi_process_function_list::InvokeChildProcessTest(procname));
451   return Process();
452 }
453 
454 }  // namespace base
455