|  | // 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); | 
|  | } |