michael@0: // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. michael@0: // Use of this source code is governed by a BSD-style license that can be michael@0: // found in the LICENSE file. michael@0: michael@0: #include michael@0: #include "sandbox/win/src/sharedmem_ipc_client.h" michael@0: #include "sandbox/win/src/sandbox.h" michael@0: #include "sandbox/win/src/crosscall_client.h" michael@0: #include "sandbox/win/src/crosscall_params.h" michael@0: #include "base/logging.h" michael@0: michael@0: namespace sandbox { michael@0: michael@0: // Get the base of the data buffer of the channel; this is where the input michael@0: // parameters get serialized. Since they get serialized directly into the michael@0: // channel we avoid one copy. michael@0: void* SharedMemIPCClient::GetBuffer() { michael@0: bool failure = false; michael@0: size_t ix = LockFreeChannel(&failure); michael@0: if (failure) { michael@0: return NULL; michael@0: } michael@0: return reinterpret_cast(control_) + michael@0: control_->channels[ix].channel_base; michael@0: } michael@0: michael@0: // If we need to cancel an IPC before issuing DoCall michael@0: // our client should call FreeBuffer with the same pointer michael@0: // returned by GetBuffer. michael@0: void SharedMemIPCClient::FreeBuffer(void* buffer) { michael@0: size_t num = ChannelIndexFromBuffer(buffer); michael@0: ChannelControl* channel = control_->channels; michael@0: LONG result = ::InterlockedExchange(&channel[num].state, kFreeChannel); michael@0: DCHECK(kFreeChannel != result); michael@0: result; michael@0: } michael@0: michael@0: // The constructor simply casts the shared memory to the internal michael@0: // structures. This is a cheap step that is why this IPC object can michael@0: // and should be constructed per call. michael@0: SharedMemIPCClient::SharedMemIPCClient(void* shared_mem) michael@0: : control_(reinterpret_cast(shared_mem)) { michael@0: first_base_ = reinterpret_cast(shared_mem) + michael@0: control_->channels[0].channel_base; michael@0: // There must be at least one channel. michael@0: DCHECK(0 != control_->channels_count); michael@0: } michael@0: michael@0: // Do the IPC. At this point the channel should have already been michael@0: // filled with the serialized input parameters. michael@0: // We follow the pattern explained in the header file. michael@0: ResultCode SharedMemIPCClient::DoCall(CrossCallParams* params, michael@0: CrossCallReturn* answer) { michael@0: if (!control_->server_alive) michael@0: return SBOX_ERROR_CHANNEL_ERROR; michael@0: michael@0: size_t num = ChannelIndexFromBuffer(params->GetBuffer()); michael@0: ChannelControl* channel = control_->channels; michael@0: // Note that the IPC tag goes outside the buffer as well inside michael@0: // the buffer. This should enable the server to prioritize based on michael@0: // IPC tags without having to de-serialize the entire message. michael@0: channel[num].ipc_tag = params->GetTag(); michael@0: michael@0: // Wait for the server to service this IPC call. After kIPCWaitTimeOut1 michael@0: // we check if the server_alive mutex was abandoned which will indicate michael@0: // that the server has died. michael@0: michael@0: // While the atomic signaling and waiting is not a requirement, it michael@0: // is nice because we save a trip to kernel. michael@0: DWORD wait = ::SignalObjectAndWait(channel[num].ping_event, michael@0: channel[num].pong_event, michael@0: kIPCWaitTimeOut1, FALSE); michael@0: if (WAIT_TIMEOUT == wait) { michael@0: // The server is taking too long. Enter a loop were we check if the michael@0: // server_alive mutex has been abandoned which would signal a server crash michael@0: // or else we keep waiting for a response. michael@0: while (true) { michael@0: wait = ::WaitForSingleObject(control_->server_alive, 0); michael@0: if (WAIT_TIMEOUT == wait) { michael@0: // Server seems still alive. We already signaled so here we just wait. michael@0: wait = ::WaitForSingleObject(channel[num].pong_event, kIPCWaitTimeOut1); michael@0: if (WAIT_OBJECT_0 == wait) { michael@0: // The server took a long time but responded. michael@0: break; michael@0: } else if (WAIT_TIMEOUT == wait) { michael@0: continue; michael@0: } else { michael@0: return SBOX_ERROR_CHANNEL_ERROR; michael@0: } michael@0: } else { michael@0: // The server has crashed and windows has signaled the mutex as michael@0: // abandoned. michael@0: ::InterlockedExchange(&channel[num].state, kAbandonnedChannel); michael@0: control_->server_alive = 0; michael@0: return SBOX_ERROR_CHANNEL_ERROR; michael@0: } michael@0: } michael@0: } else if (WAIT_OBJECT_0 != wait) { michael@0: // Probably the server crashed before the kIPCWaitTimeOut1 occurred. michael@0: return SBOX_ERROR_CHANNEL_ERROR; michael@0: } michael@0: michael@0: // The server has returned an answer, copy it and free the channel. michael@0: memcpy(answer, params->GetCallReturn(), sizeof(CrossCallReturn)); michael@0: michael@0: // Return the IPC state It can indicate that while the IPC has michael@0: // completed some error in the Broker has caused to not return valid michael@0: // results. michael@0: return answer->call_outcome; michael@0: } michael@0: michael@0: // Locking a channel is a simple as looping over all the channels michael@0: // looking for one that is has state = kFreeChannel and atomically michael@0: // swapping it to kBusyChannel. michael@0: // If there is no free channel, then we must back off so some other michael@0: // thread makes progress and frees a channel. To back off we sleep. michael@0: size_t SharedMemIPCClient::LockFreeChannel(bool* severe_failure) { michael@0: if (0 == control_->channels_count) { michael@0: *severe_failure = true; michael@0: return 0; michael@0: } michael@0: ChannelControl* channel = control_->channels; michael@0: do { michael@0: for (size_t ix = 0; ix != control_->channels_count; ++ix) { michael@0: if (kFreeChannel == ::InterlockedCompareExchange(&channel[ix].state, michael@0: kBusyChannel, michael@0: kFreeChannel)) { michael@0: *severe_failure = false; michael@0: return ix; michael@0: } michael@0: } michael@0: // We did not find any available channel, maybe the server is dead. michael@0: DWORD wait = ::WaitForSingleObject(control_->server_alive, michael@0: kIPCWaitTimeOut2); michael@0: if (WAIT_TIMEOUT != wait) { michael@0: // The server is dead and we outlive it enough to get in trouble. michael@0: *severe_failure = true; michael@0: return 0; michael@0: } michael@0: } michael@0: while (true); michael@0: } michael@0: michael@0: // Find out which channel we are from the pointer returned by GetBuffer. michael@0: size_t SharedMemIPCClient::ChannelIndexFromBuffer(const void* buffer) { michael@0: ptrdiff_t d = reinterpret_cast(buffer) - first_base_; michael@0: size_t num = d/kIPCChannelSize; michael@0: DCHECK(num < control_->channels_count); michael@0: return (num); michael@0: } michael@0: michael@0: } // namespace sandbox