OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "build/build_config.h" | 5 #include "build/build_config.h" |
6 | 6 |
7 #if defined(OS_POSIX) | 7 #if defined(OS_POSIX) |
8 #if defined(OS_MACOSX) | 8 #if defined(OS_MACOSX) |
9 extern "C" { | 9 extern "C" { |
10 #include <sandbox.h> | 10 #include <sandbox.h> |
11 } | 11 } |
12 #endif | 12 #endif |
13 #include <fcntl.h> | 13 #include <fcntl.h> |
| 14 #include <sys/socket.h> |
14 #include <sys/stat.h> | 15 #include <sys/stat.h> |
15 #include <unistd.h> | 16 #include <unistd.h> |
16 | 17 |
| 18 #include <queue> |
| 19 |
| 20 #include "base/callback.h" |
17 #include "base/file_descriptor_posix.h" | 21 #include "base/file_descriptor_posix.h" |
18 #include "base/message_loop/message_loop.h" | 22 #include "base/message_loop/message_loop.h" |
19 #include "base/pickle.h" | 23 #include "base/pickle.h" |
20 #include "base/posix/eintr_wrapper.h" | 24 #include "base/posix/eintr_wrapper.h" |
| 25 #include "base/synchronization/waitable_event.h" |
21 #include "ipc/ipc_message_utils.h" | 26 #include "ipc/ipc_message_utils.h" |
22 #include "ipc/ipc_test_base.h" | 27 #include "ipc/ipc_test_base.h" |
23 | 28 |
24 namespace { | 29 namespace { |
25 | 30 |
26 const unsigned kNumFDsToSend = 20; | 31 const unsigned kNumFDsToSend = 20; |
27 const char* kDevZeroPath = "/dev/zero"; | 32 const char* kDevZeroPath = "/dev/zero"; |
28 | 33 |
29 static void VerifyAndCloseDescriptor(int fd, ino_t inode_num) { | 34 class MyChannelDescriptorListenerBase : public IPC::Listener { |
30 // Check that we can read from the FD. | |
31 char buf; | |
32 ssize_t amt_read = read(fd, &buf, 1); | |
33 ASSERT_EQ(amt_read, 1); | |
34 ASSERT_EQ(buf, 0); // /dev/zero always reads 0 bytes. | |
35 | |
36 struct stat st; | |
37 ASSERT_EQ(fstat(fd, &st), 0); | |
38 | |
39 ASSERT_EQ(close(fd), 0); | |
40 | |
41 // Compare inode numbers to check that the file sent over the wire is actually | |
42 // the one expected. | |
43 ASSERT_EQ(inode_num, st.st_ino); | |
44 } | |
45 | |
46 class MyChannelDescriptorListener : public IPC::Listener { | |
47 public: | 35 public: |
48 explicit MyChannelDescriptorListener(ino_t expected_inode_num) | |
49 : expected_inode_num_(expected_inode_num), | |
50 num_fds_received_(0) {} | |
51 | |
52 virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE { | 36 virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE { |
53 PickleIterator iter(message); | 37 PickleIterator iter(message); |
54 | 38 |
55 ++num_fds_received_; | |
56 base::FileDescriptor descriptor; | 39 base::FileDescriptor descriptor; |
57 | 40 |
58 IPC::ParamTraits<base::FileDescriptor>::Read(&message, &iter, &descriptor); | 41 IPC::ParamTraits<base::FileDescriptor>::Read(&message, &iter, &descriptor); |
59 | 42 |
60 VerifyAndCloseDescriptor(descriptor.fd, expected_inode_num_); | 43 HandleFD(descriptor.fd); |
61 if (num_fds_received_ == kNumFDsToSend) | 44 return true; |
62 base::MessageLoop::current()->Quit(); | 45 } |
63 | 46 |
64 return true; | 47 protected: |
| 48 virtual void HandleFD(int fd) = 0; |
| 49 }; |
| 50 |
| 51 class MyChannelDescriptorListener : public MyChannelDescriptorListenerBase { |
| 52 public: |
| 53 explicit MyChannelDescriptorListener(ino_t expected_inode_num) |
| 54 : MyChannelDescriptorListenerBase(), |
| 55 expected_inode_num_(expected_inode_num), |
| 56 num_fds_received_(0) { |
| 57 } |
| 58 |
| 59 bool GotExpectedNumberOfDescriptors() const { |
| 60 return num_fds_received_ == kNumFDsToSend; |
65 } | 61 } |
66 | 62 |
67 virtual void OnChannelError() OVERRIDE { | 63 virtual void OnChannelError() OVERRIDE { |
68 base::MessageLoop::current()->Quit(); | 64 base::MessageLoop::current()->Quit(); |
69 } | 65 } |
70 | 66 |
71 bool GotExpectedNumberOfDescriptors() const { | 67 protected: |
72 return num_fds_received_ == kNumFDsToSend; | 68 virtual void HandleFD(int fd) OVERRIDE { |
| 69 // Check that we can read from the FD. |
| 70 char buf; |
| 71 ssize_t amt_read = read(fd, &buf, 1); |
| 72 ASSERT_EQ(amt_read, 1); |
| 73 ASSERT_EQ(buf, 0); // /dev/zero always reads 0 bytes. |
| 74 |
| 75 struct stat st; |
| 76 ASSERT_EQ(fstat(fd, &st), 0); |
| 77 |
| 78 ASSERT_EQ(close(fd), 0); |
| 79 |
| 80 // Compare inode numbers to check that the file sent over the wire is |
| 81 // actually the one expected. |
| 82 ASSERT_EQ(expected_inode_num_, st.st_ino); |
| 83 |
| 84 ++num_fds_received_; |
| 85 if (num_fds_received_ == kNumFDsToSend) |
| 86 base::MessageLoop::current()->Quit(); |
73 } | 87 } |
74 | 88 |
75 private: | 89 private: |
76 ino_t expected_inode_num_; | 90 ino_t expected_inode_num_; |
77 unsigned num_fds_received_; | 91 unsigned num_fds_received_; |
78 }; | 92 }; |
79 | 93 |
| 94 |
80 class IPCSendFdsTest : public IPCTestBase { | 95 class IPCSendFdsTest : public IPCTestBase { |
81 protected: | 96 protected: |
82 void RunServer() { | 97 void RunServer() { |
83 // Set up IPC channel and start client. | 98 // Set up IPC channel and start client. |
84 MyChannelDescriptorListener listener(-1); | 99 MyChannelDescriptorListener listener(-1); |
85 CreateChannel(&listener); | 100 CreateChannel(&listener); |
86 ASSERT_TRUE(ConnectChannel()); | 101 ASSERT_TRUE(ConnectChannel()); |
87 ASSERT_TRUE(StartClient()); | 102 ASSERT_TRUE(StartClient()); |
88 | 103 |
89 for (unsigned i = 0; i < kNumFDsToSend; ++i) { | 104 for (unsigned i = 0; i < kNumFDsToSend; ++i) { |
(...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
171 if (open(kDevZeroPath, O_RDONLY) != -1) { | 186 if (open(kDevZeroPath, O_RDONLY) != -1) { |
172 LOG(ERROR) << "Sandbox wasn't properly enabled"; | 187 LOG(ERROR) << "Sandbox wasn't properly enabled"; |
173 return -1; | 188 return -1; |
174 } | 189 } |
175 | 190 |
176 // See if we can receive a file descriptor. | 191 // See if we can receive a file descriptor. |
177 return SendFdsClientCommon("SendFdsSandboxedClient", st.st_ino); | 192 return SendFdsClientCommon("SendFdsSandboxedClient", st.st_ino); |
178 } | 193 } |
179 #endif // defined(OS_MACOSX) | 194 #endif // defined(OS_MACOSX) |
180 | 195 |
| 196 |
| 197 class MyCBListener : public MyChannelDescriptorListenerBase { |
| 198 public: |
| 199 MyCBListener(base::Callback<void(int)> cb, int fds_to_send) |
| 200 : MyChannelDescriptorListenerBase(), |
| 201 cb_(cb) { |
| 202 } |
| 203 |
| 204 protected: |
| 205 virtual void HandleFD(int fd) OVERRIDE { |
| 206 cb_.Run(fd); |
| 207 } |
| 208 private: |
| 209 base::Callback<void(int)> cb_; |
| 210 }; |
| 211 |
| 212 std::pair<int, int> make_socket_pair() { |
| 213 int pipe_fds[2]; |
| 214 CHECK_EQ(0, HANDLE_EINTR(socketpair(AF_UNIX, SOCK_STREAM, 0, pipe_fds))); |
| 215 return std::pair<int, int>(pipe_fds[0], pipe_fds[1]); |
| 216 } |
| 217 |
| 218 static void null_cb(int unused_fd) { |
| 219 NOTREACHED(); |
| 220 } |
| 221 |
| 222 class PipeChannelHelper { |
| 223 public: |
| 224 PipeChannelHelper(base::Thread* in_thread, |
| 225 base::Thread* out_thread, |
| 226 base::Callback<void(int)> cb, |
| 227 int fds_to_send) : |
| 228 in_thread_(in_thread), |
| 229 out_thread_(out_thread), |
| 230 cb_listener_(cb, fds_to_send), |
| 231 null_listener_(base::Bind(&null_cb), 0) { |
| 232 } |
| 233 |
| 234 void Init() { |
| 235 IPC::ChannelHandle in_handle("IN"); |
| 236 in.reset(new IPC::Channel(in_handle, |
| 237 IPC::Channel::MODE_SERVER, |
| 238 &null_listener_)); |
| 239 base::FileDescriptor out_fd(in->TakeClientFileDescriptor(), false); |
| 240 IPC::ChannelHandle out_handle("OUT", out_fd); |
| 241 out.reset(new IPC::Channel(out_handle, |
| 242 IPC::Channel::MODE_CLIENT, |
| 243 &cb_listener_)); |
| 244 // PostTask the connect calls to make sure the callbacks happens |
| 245 // on the right threads. |
| 246 in_thread_->message_loop()->PostTask( |
| 247 FROM_HERE, |
| 248 base::Bind(&PipeChannelHelper::Connect, in.get())); |
| 249 out_thread_->message_loop()->PostTask( |
| 250 FROM_HERE, |
| 251 base::Bind(&PipeChannelHelper::Connect, out.get())); |
| 252 } |
| 253 |
| 254 static void DestroyChannel(scoped_ptr<IPC::Channel> *c, |
| 255 base::WaitableEvent *event) { |
| 256 c->reset(0); |
| 257 event->Signal(); |
| 258 } |
| 259 |
| 260 ~PipeChannelHelper() { |
| 261 base::WaitableEvent a(true, false); |
| 262 base::WaitableEvent b(true, false); |
| 263 in_thread_->message_loop()->PostTask( |
| 264 FROM_HERE, |
| 265 base::Bind(&PipeChannelHelper::DestroyChannel, &in, &a)); |
| 266 out_thread_->message_loop()->PostTask( |
| 267 FROM_HERE, |
| 268 base::Bind(&PipeChannelHelper::DestroyChannel, &out, &b)); |
| 269 a.Wait(); |
| 270 b.Wait(); |
| 271 } |
| 272 |
| 273 static void Connect(IPC::Channel *channel) { |
| 274 EXPECT_TRUE(channel->Connect()); |
| 275 } |
| 276 |
| 277 void Send(int fd) { |
| 278 CHECK_EQ(base::MessageLoop::current(), in_thread_->message_loop()); |
| 279 |
| 280 ASSERT_GE(fd, 0); |
| 281 base::FileDescriptor descriptor(fd, true); |
| 282 |
| 283 IPC::Message* message = |
| 284 new IPC::Message(0, 3, IPC::Message::PRIORITY_NORMAL); |
| 285 IPC::ParamTraits<base::FileDescriptor>::Write(message, descriptor); |
| 286 ASSERT_TRUE(in->Send(message)); |
| 287 } |
| 288 |
| 289 private: |
| 290 scoped_ptr<IPC::Channel> in, out; |
| 291 base::Thread* in_thread_; |
| 292 base::Thread* out_thread_; |
| 293 MyCBListener cb_listener_; |
| 294 MyCBListener null_listener_; |
| 295 }; |
| 296 |
| 297 // This test is meant to provoke a kernel bug on OSX, and to prove |
| 298 // that the workaround for it is working. It sets up two pipes and three |
| 299 // threads, the producer thread creates socketpairs and sends one of the fds |
| 300 // over pipe1 to the middleman thread. The middleman thread simply takes the fd |
| 301 // sends it over pipe2 to the consumer thread. The consumer thread writes a byte |
| 302 // to each fd it receives and then closes the pipe. The producer thread reads |
| 303 // the bytes back from each pair of pipes and make sure that everything worked. |
| 304 // This feedback mechanism makes sure that not too many file descriptors are |
| 305 // in flight at the same time. For more info on the bug, see: |
| 306 // http://crbug.com/298276 |
| 307 class IPCMultiSendingFdsTest : public testing::Test { |
| 308 public: |
| 309 IPCMultiSendingFdsTest() : received_(true, false) {} |
| 310 |
| 311 void Producer(PipeChannelHelper* dest, |
| 312 base::Thread* t, |
| 313 int pipes_to_send) { |
| 314 for (int i = 0; i < pipes_to_send; i++) { |
| 315 received_.Reset(); |
| 316 std::pair<int, int> pipe_fds = make_socket_pair(); |
| 317 t->message_loop()->PostTask( |
| 318 FROM_HERE, |
| 319 base::Bind(&PipeChannelHelper::Send, |
| 320 base::Unretained(dest), |
| 321 pipe_fds.second)); |
| 322 char tmp = 'x'; |
| 323 CHECK_EQ(1, HANDLE_EINTR(write(pipe_fds.first, &tmp, 1))); |
| 324 CHECK_EQ(0, HANDLE_EINTR(close(pipe_fds.first))); |
| 325 received_.Wait(); |
| 326 } |
| 327 } |
| 328 |
| 329 void ConsumerHandleFD(int fd) { |
| 330 char tmp = 'y'; |
| 331 CHECK_EQ(1, HANDLE_EINTR(read(fd, &tmp, 1))); |
| 332 CHECK_EQ(tmp, 'x'); |
| 333 CHECK_EQ(0, HANDLE_EINTR(close(fd))); |
| 334 received_.Signal(); |
| 335 } |
| 336 |
| 337 base::Thread* CreateThread(const char* name) { |
| 338 base::Thread* ret = new base::Thread(name); |
| 339 base::Thread::Options options; |
| 340 options.message_loop_type = base::MessageLoop::TYPE_IO; |
| 341 ret->StartWithOptions(options); |
| 342 return ret; |
| 343 } |
| 344 |
| 345 void Run() { |
| 346 // On my mac, this test fails roughly 35 times per |
| 347 // million sends with low load, but much more with high load. |
| 348 // Unless the workaround is in place. With 10000 sends, we |
| 349 // should see at least a 3% failure rate. |
| 350 const int pipes_to_send = 20000; |
| 351 scoped_ptr<base::Thread> producer(CreateThread("producer")); |
| 352 scoped_ptr<base::Thread> middleman(CreateThread("middleman")); |
| 353 scoped_ptr<base::Thread> consumer(CreateThread("consumer")); |
| 354 PipeChannelHelper pipe1( |
| 355 middleman.get(), |
| 356 consumer.get(), |
| 357 base::Bind(&IPCMultiSendingFdsTest::ConsumerHandleFD, |
| 358 base::Unretained(this)), |
| 359 pipes_to_send); |
| 360 PipeChannelHelper pipe2( |
| 361 producer.get(), |
| 362 middleman.get(), |
| 363 base::Bind(&PipeChannelHelper::Send, base::Unretained(&pipe1)), |
| 364 pipes_to_send); |
| 365 pipe1.Init(); |
| 366 pipe2.Init(); |
| 367 Producer(&pipe2, producer.get(), pipes_to_send); |
| 368 } |
| 369 |
| 370 private: |
| 371 base::WaitableEvent received_; |
| 372 }; |
| 373 |
| 374 TEST_F(IPCMultiSendingFdsTest, StressTest) { |
| 375 Run(); |
| 376 } |
| 377 |
181 } // namespace | 378 } // namespace |
182 | 379 |
183 #endif // defined(OS_POSIX) | 380 #endif // defined(OS_POSIX) |
OLD | NEW |