| // Copyright 2016 The Fuchsia Authors | |
| // | |
| // Use of this source code is governed by a MIT-style | |
| // license that can be found in the LICENSE file or at | |
| // https://opensource.org/licenses/MIT | |
| #include <err.h> | |
| #include <inttypes.h> | |
| #include <lib/ktrace.h> | |
| #include <lib/user_copy/user_ptr.h> | |
| #include <platform.h> | |
| #include <trace.h> | |
| #include <zircon/types.h> | |
| #include <fbl/inline_array.h> | |
| #include <fbl/ref_ptr.h> | |
| #include <kernel/event.h> | |
| #include <kernel/lockdep.h> | |
| #include <kernel/thread.h> | |
| #include <object/dispatcher.h> | |
| #include <object/handle.h> | |
| #include <object/port_dispatcher.h> | |
| #include <object/process_dispatcher.h> | |
| #include <object/wait_signal_observer.h> | |
| #include "priv.h" | |
| #define LOCAL_TRACE 0 | |
| constexpr uint32_t kMaxWaitHandleCount = ZX_WAIT_MANY_MAX_ITEMS; | |
| static_assert(ZX_WAIT_MANY_MAX_ITEMS == ZX_CHANNEL_MAX_MSG_HANDLES); | |
| // zx_status_t zx_object_wait_one | |
| zx_status_t sys_object_wait_one(zx_handle_t handle_value, zx_signals_t signals, zx_time_t deadline, | |
| user_out_ptr<zx_signals_t> observed) { | |
| LTRACEF("handle %x\n", handle_value); | |
| Event event; | |
| zx_status_t result; | |
| WaitSignalObserver wait_signal_observer; | |
| uint32_t koid; | |
| auto up = ProcessDispatcher::GetCurrent(); | |
| { | |
| Guard<BrwLockPi, BrwLockPi::Reader> guard{up->handle_table_lock()}; | |
| Handle* handle = up->GetHandleLocked(handle_value); | |
| if (!handle) | |
| return ZX_ERR_BAD_HANDLE; | |
| if (!handle->HasRights(ZX_RIGHT_WAIT)) | |
| return ZX_ERR_ACCESS_DENIED; | |
| result = wait_signal_observer.Begin(&event, handle, signals); | |
| if (result != ZX_OK) | |
| return result; | |
| koid = static_cast<uint32_t>(handle->dispatcher()->get_koid()); | |
| } | |
| ktrace(TAG_WAIT_ONE, koid, signals, (uint32_t)deadline, (uint32_t)(deadline >> 32)); | |
| const TimerSlack slack = up->GetTimerSlackPolicy(); | |
| const Deadline slackDeadline(deadline, slack); | |
| // Event::Wait() will return ZX_OK if already signaled, | |
| // even if the deadline has passed. It will return ZX_ERR_TIMED_OUT | |
| // after the deadline passes if the event has not been | |
| // signaled. | |
| { | |
| ThreadDispatcher::AutoBlocked by(ThreadDispatcher::Blocked::WAIT_ONE); | |
| result = event.Wait(slackDeadline); | |
| } | |
| // Regardless of wait outcome, we must call End(). | |
| auto signals_state = wait_signal_observer.End(); | |
| ktrace(TAG_WAIT_ONE_DONE, koid, signals_state, result, 0); | |
| if (observed) { | |
| zx_status_t status = observed.copy_to_user(signals_state); | |
| if (status != ZX_OK) | |
| return status; | |
| } | |
| if (signals_state & ZX_SIGNAL_HANDLE_CLOSED) | |
| return ZX_ERR_CANCELED; | |
| return result; | |
| } | |
| // zx_status_t zx_object_wait_many | |
| zx_status_t sys_object_wait_many(user_inout_ptr<zx_wait_item_t> user_items, size_t count, | |
| zx_time_t deadline) { | |
| LTRACEF("count %zu\n", count); | |
| const auto up = ProcessDispatcher::GetCurrent(); | |
| const Deadline slackDeadline(deadline, up->GetTimerSlackPolicy()); | |
| if (!count) { | |
| const zx_time_t now = current_time(); | |
| { | |
| ThreadDispatcher::AutoBlocked by(ThreadDispatcher::Blocked::WAIT_MANY); | |
| zx_status_t result = Thread::Current::SleepEtc(slackDeadline, Interruptible::Yes, now); | |
| if (result != ZX_OK) { | |
| return result; | |
| } | |
| } | |
| return ZX_ERR_TIMED_OUT; | |
| } | |
| if (count > kMaxWaitHandleCount) | |
| return ZX_ERR_OUT_OF_RANGE; | |
| zx_wait_item_t items[kMaxWaitHandleCount]; | |
| if (user_items.copy_array_from_user(items, count) != ZX_OK) | |
| return ZX_ERR_INVALID_ARGS; | |
| // WaitSignalObserver is heavier than it looks so make sure we know how | |
| // much stack InlineArray is going to use, given limited kernel stack size. | |
| static_assert(sizeof(WaitSignalObserver) * 8 < 640); | |
| fbl::AllocChecker ac; | |
| fbl::InlineArray<WaitSignalObserver, 8u> wait_signal_observers(&ac, count); | |
| if (!ac.check()) { | |
| return ZX_ERR_NO_MEMORY; | |
| } | |
| Event event; | |
| // We may need to unwind (which can be done outside the lock). | |
| zx_status_t result = ZX_OK; | |
| size_t num_added = 0; | |
| { | |
| Guard<BrwLockPi, BrwLockPi::Reader> guard{up->handle_table_lock()}; | |
| for (; num_added != count; ++num_added) { | |
| Handle* handle = up->GetHandleLocked(items[num_added].handle); | |
| if (!handle) { | |
| result = ZX_ERR_BAD_HANDLE; | |
| break; | |
| } | |
| if (!handle->HasRights(ZX_RIGHT_WAIT)) { | |
| result = ZX_ERR_ACCESS_DENIED; | |
| break; | |
| } | |
| result = wait_signal_observers[num_added].Begin(&event, handle, items[num_added].waitfor); | |
| if (result != ZX_OK) | |
| break; | |
| } | |
| } | |
| if (result != ZX_OK) { | |
| for (size_t ix = 0; ix < num_added; ++ix) | |
| wait_signal_observers[ix].End(); | |
| return result; | |
| } | |
| // Event::Wait() will return ZX_OK if already signaled, | |
| // even if deadline has passed. It will return ZX_ERR_TIMED_OUT | |
| // after the deadline passes if the event has not been | |
| // signaled. | |
| { | |
| ThreadDispatcher::AutoBlocked by(ThreadDispatcher::Blocked::WAIT_MANY); | |
| result = event.Wait(slackDeadline); | |
| } | |
| // Regardless of wait outcome, we must call End(). | |
| zx_signals_t combined = 0; | |
| for (size_t ix = 0; ix != count; ++ix) { | |
| combined |= (items[ix].pending = wait_signal_observers[ix].End()); | |
| } | |
| if (user_items.copy_array_to_user(items, count) != ZX_OK) | |
| return ZX_ERR_INVALID_ARGS; | |
| if (combined & ZX_SIGNAL_HANDLE_CLOSED) | |
| return ZX_ERR_CANCELED; | |
| return result; | |
| } | |
| // zx_status_t zx_object_wait_async | |
| zx_status_t sys_object_wait_async(zx_handle_t handle_value, zx_handle_t port_handle_value, | |
| uint64_t key, zx_signals_t signals, uint32_t options) { | |
| LTRACEF("handle %x\n", handle_value); | |
| if ((options != 0u) && (options != ZX_WAIT_ASYNC_TIMESTAMP)) { | |
| return ZX_ERR_INVALID_ARGS; | |
| } | |
| // Allocate space for a |PortObserver| before taking any locks. | |
| // | |
| // |PortDispatcher::MakeObserver| is responsible for constructing the |PortObserver|, however, it | |
| // must be called while holding the process's handle table lock and don't want to perform a | |
| // potentially blocking allocation while holding that lock. Allocate a special placeholder that | |
| // we'll pass in to |MakeObserver|. | |
| fbl::AllocChecker ac; | |
| auto placeholder = ktl::make_unique<PortDispatcher::PortObserverPlaceholder>(&ac); | |
| if (!ac.check()) { | |
| return ZX_ERR_NO_MEMORY; | |
| } | |
| auto up = ProcessDispatcher::GetCurrent(); | |
| { | |
| Guard<BrwLockPi, BrwLockPi::Reader> guard{up->handle_table_lock()}; | |
| // Note, we're doing this all while holding the handle table lock for two reasons. | |
| // | |
| // First, this thread may be racing with another thread that's closing the last handle to | |
| // the port. By holding the lock we can ensure that this syscall behaves as if the port was | |
| // closed just *before* the syscall started or closed just *after* it has completed. | |
| // | |
| // Second, MakeObserver takes a Handle. By holding the lock we ensure the Handle isn't | |
| // destroyed out from under it. | |
| Handle* port_handle = up->GetHandleLocked(port_handle_value); | |
| if (!port_handle) { | |
| return ZX_ERR_BAD_HANDLE; | |
| } | |
| fbl::RefPtr<Dispatcher> disp = port_handle->dispatcher(); | |
| fbl::RefPtr<PortDispatcher> port = DownCastDispatcher<PortDispatcher>(&disp); | |
| if (!port) { | |
| return ZX_ERR_WRONG_TYPE; | |
| } | |
| if (!port_handle->HasRights(ZX_RIGHT_WRITE)) { | |
| return ZX_ERR_ACCESS_DENIED; | |
| } | |
| Handle* handle = up->GetHandleLocked(handle_value); | |
| if (!handle) { | |
| return ZX_ERR_BAD_HANDLE; | |
| } | |
| if (!handle->HasRights(ZX_RIGHT_WAIT)) { | |
| return ZX_ERR_ACCESS_DENIED; | |
| } | |
| return port->MakeObserver(ktl::move(placeholder), options, handle, key, signals); | |
| } | |
| } |