| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <lib/fdio/spawn.h> |
| #include <lib/fit/defer.h> |
| #include <lib/zx/channel.h> |
| #include <lib/zx/clock.h> |
| #include <lib/zx/process.h> |
| #include <zircon/processargs.h> |
| #include <zircon/utc.h> |
| |
| #include <string> |
| |
| #include <zxtest/zxtest.h> |
| |
| namespace { |
| |
| const std::string kHelperFlag{"utc-procargs-helper"}; |
| constexpr zx::duration kProcessTerminateTimeout = zx::sec(20); |
| |
| // A small wrapper used to launch a process which will fetch the current clock |
| // from the environment which should have been set up by libc, and send back |
| // to us details about the clock that it sees. |
| class TargetProcess { |
| public: |
| struct ResponseMessage { |
| // Note: this is not an actual handle. It is simply the value observed by |
| // the process target. We use it to make sure that the handle is invalid |
| // when it should be. |
| zx_handle_t observed_utc_handle; |
| zx_koid_t observed_utc_koid; |
| zx_rights_t observed_utc_rights; |
| }; |
| |
| static void SetProgramName(const char* program_name) { program_name_ = program_name; } |
| static int Main(); |
| |
| TargetProcess() = default; |
| |
| // No copy or move, either via construction or assignment. |
| TargetProcess(const TargetProcess&) = delete; |
| TargetProcess(TargetProcess&&) = delete; |
| TargetProcess& operator=(const TargetProcess&) = delete; |
| TargetProcess& operator=(TargetProcess&&) = delete; |
| |
| ~TargetProcess() { Stop(); } |
| |
| void Run(zx::clock clock_to_send); |
| const zx::channel& control_channel() const { return control_channel_; } |
| |
| private: |
| static const char* program_name_; |
| |
| void Stop() { |
| // If the target process is valid, attempt to kill it. It should have |
| // exited already, but something must have gone terribly wrong. |
| if (target_process_) { |
| target_process_.kill(); |
| target_process_.reset(); |
| } |
| control_channel_.reset(); |
| } |
| |
| zx::process target_process_; |
| zx::channel control_channel_; |
| }; |
| |
| const char* TargetProcess::program_name_ = nullptr; |
| |
| // Run the target process, passing the clock provided (if any) and wait for it to exit. |
| void TargetProcess::Run(zx::clock clock_to_send) { |
| auto on_failure = fit::defer([this]() { Stop(); }); |
| |
| // Make sure that we have a program name and have not already started. |
| ASSERT_NOT_NULL(program_name_); |
| ASSERT_EQ(target_process_.get(), ZX_HANDLE_INVALID); |
| ASSERT_EQ(control_channel_.get(), ZX_HANDLE_INVALID); |
| |
| // Create the channel we will use for talking to our external process. |
| zx::channel remote; |
| ASSERT_OK(zx::channel::create(0, &control_channel_, &remote)); |
| |
| const char* args[] = {program_name_, kHelperFlag.c_str(), nullptr}; |
| size_t handles_to_send = clock_to_send.is_valid() ? 2 : 1; |
| struct fdio_spawn_action startup_handles[] = { |
| {.action = FDIO_SPAWN_ACTION_ADD_HANDLE, |
| .h = {.id = PA_HND(PA_USER0, 0), .handle = remote.release()}}, |
| {.action = FDIO_SPAWN_ACTION_ADD_HANDLE, |
| .h = {.id = PA_HND(PA_CLOCK_UTC, 0), .handle = clock_to_send.release()}}}; |
| |
| // Clone everything but the UTC clock, which is being passed explicitly. |
| const uint32_t spawn_flags = FDIO_SPAWN_CLONE_ALL & ~FDIO_SPAWN_CLONE_UTC_CLOCK; |
| char err_msg_out[FDIO_SPAWN_ERR_MSG_MAX_LENGTH]; |
| zx_status_t res = |
| fdio_spawn_etc(ZX_HANDLE_INVALID, spawn_flags, program_name_, args, nullptr, handles_to_send, |
| startup_handles, target_process_.reset_and_get_address(), err_msg_out); |
| ASSERT_OK(res, "%s", err_msg_out); |
| |
| // Wait for the process we spawned to exit. We wait a finite (but very long) |
| // amount of time for this to happen in the hopes that if something goes wrong |
| // that we will have a chance to kill the process we spawned instead of |
| // needing to hope that our test framework will be able to do so for us. |
| ASSERT_OK(target_process_.wait_one(ZX_PROCESS_TERMINATED, |
| zx::deadline_after(kProcessTerminateTimeout), nullptr)); |
| |
| // OK, the process exited. Go ahead and close the handle so that we don't |
| // bother to try and kill it later on. |
| target_process_.reset(); |
| |
| // Things went well! Cancel our on_failure cleanup routine. |
| on_failure.cancel(); |
| } |
| |
| int TargetProcess::Main() { |
| // Get a hold of the channel we will use to respond to the test harness with, |
| // extract the details of the clock object (the koid and the rights), and send |
| // it back to the test harness for validation. If anything goes wrong here, |
| // return the non-zero line number at which failure occurred in an attempt to |
| // give an indication to the test process something to log which might be |
| // helpful for someone trying to figure out where the helper process failed in |
| // the case that all they have to go on are some automated test logs. |
| // |
| // If things go well, return 0 to indicate success. |
| zx::channel response_channel(zx_take_startup_handle(PA_HND(PA_USER0, 0))); |
| if (!response_channel.is_valid()) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| // Now take a peek at our clock handle as stashed by the runtime. |
| TargetProcess::ResponseMessage response{}; |
| response.observed_utc_handle = zx_utc_reference_get(); |
| zx::unowned_clock utc_clock(response.observed_utc_handle); |
| |
| if (utc_clock->is_valid()) { |
| zx_info_handle_basic_t clock_info{}; |
| zx_status_t res = utc_clock->get_info(ZX_INFO_HANDLE_BASIC, &clock_info, sizeof(clock_info), |
| nullptr, nullptr); |
| |
| if (res != ZX_OK) { |
| return res; |
| } |
| |
| response.observed_utc_koid = clock_info.koid; |
| response.observed_utc_rights = clock_info.rights; |
| } |
| |
| // Send a message back with the details of the clock that the runtime has |
| // stashed for us. |
| return response_channel.write(0, &response, sizeof(response), nullptr, 0); |
| } |
| |
| // We will end up running three variants of the test, but the vast majority of |
| // the code that we are going to run is common, so we pick which variant we want |
| // using an enum at runtime to reduce code duplication. Note, if there was a |
| // reason to, this decision could be made using templates and expanded at |
| // compile time instead. |
| enum class TransferTestFlavor { |
| kNoHandleProvided, |
| kReadOnlyHandleProvided, |
| kReadWriteHandleProvided, |
| }; |
| |
| void TransferTestCommon(TransferTestFlavor flavor) { |
| zx::clock the_clock; |
| zx_info_handle_basic_t clock_info{}; |
| |
| // If this test involves actually creating a clock, create it now, start it, |
| // reduce its rights to the appropriate level, and stash its basic information |
| // for later validation. |
| if ((flavor == TransferTestFlavor::kReadOnlyHandleProvided) || |
| (flavor == TransferTestFlavor::kReadWriteHandleProvided)) { |
| // Just go with a default clock for now. We don't really care all that much |
| // about the features of the clock for these tests. |
| ASSERT_OK(zx::clock::create(0, nullptr, &the_clock)); |
| |
| // Start the clock, just in case the environment we are sending the clock to |
| // has any opinions at all as to whether or not the clock should be running. |
| ASSERT_OK(the_clock.update(zx::clock::update_args().set_value(zx::time(0)))); |
| |
| // Query and stash the basic info |
| ASSERT_OK(the_clock.get_info(ZX_INFO_HANDLE_BASIC, &clock_info, sizeof(clock_info), nullptr, |
| nullptr)); |
| |
| // If this test involves a read-only clock, reduce the rights on our handle. |
| if (flavor == TransferTestFlavor::kReadOnlyHandleProvided) { |
| clock_info.rights &= ~ZX_RIGHT_WRITE; |
| ASSERT_OK(the_clock.replace(clock_info.rights, &the_clock)); |
| } |
| } |
| |
| // Now go ahead and run, passing it the clock we created (if any) |
| TargetProcess target_process; |
| ASSERT_NO_FATAL_FAILURE(target_process.Run(std::move(the_clock))); |
| |
| // At this point, the process should have already sent us a response in the |
| // control channel and exited. Go ahead and read the response now. |
| TargetProcess::ResponseMessage response; |
| ASSERT_OK(target_process.control_channel().read(0, &response, nullptr, sizeof(response), 0, |
| nullptr, nullptr)); |
| |
| // Now just check the results based on the type of test we are running. |
| switch (flavor) { |
| // If this was the no-handle test, then we should just have HANDLE_INVALID |
| // for the handle value, and nothing else. |
| case TransferTestFlavor::kNoHandleProvided: |
| EXPECT_EQ(ZX_HANDLE_INVALID, response.observed_utc_handle); |
| break; |
| |
| // For either the read-only, or the read write tests, the handle should not |
| // be invalid, the koid/rights should match what we sent to the process |
| // exactly. We do not expect the runtime to reduce the rights any further. |
| case TransferTestFlavor::kReadOnlyHandleProvided: |
| case TransferTestFlavor::kReadWriteHandleProvided: |
| EXPECT_NE(ZX_HANDLE_INVALID, response.observed_utc_handle); |
| EXPECT_EQ(clock_info.koid, response.observed_utc_koid); |
| EXPECT_EQ(clock_info.rights, response.observed_utc_rights); |
| break; |
| |
| default: |
| ASSERT_TRUE(false); |
| break; |
| }; |
| } |
| |
| TEST(UtcProcargsTestCase, TransferNoHandle) { |
| ASSERT_NO_FAILURES(TransferTestCommon(TransferTestFlavor::kNoHandleProvided)); |
| } |
| |
| TEST(UtcProcargsTestCase, TransferReadOnly) { |
| ASSERT_NO_FAILURES(TransferTestCommon(TransferTestFlavor::kReadOnlyHandleProvided)); |
| } |
| |
| TEST(UtcProcargsTestCase, TransferReadWrite) { |
| ASSERT_NO_FAILURES(TransferTestCommon(TransferTestFlavor::kReadWriteHandleProvided)); |
| } |
| |
| } // namespace |
| |
| int main(int argc, char** argv) { |
| TargetProcess::SetProgramName(argv[0]); |
| |
| // If we were the spawned helper process, then fork off to the helper process |
| // behavior instead of running the tests. |
| if ((argc == 2) && !strcmp(argv[1], kHelperFlag.c_str())) { |
| return TargetProcess::Main(); |
| } |
| |
| return RUN_ALL_TESTS(argc, argv); |
| } |