| // 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 "garnet/lib/process/process_builder.h" |
| |
| #include <fcntl.h> |
| #include <lib/fdio/io.h> |
| #include <lib/fdio/limits.h> |
| #include <lib/fdio/namespace.h> |
| #include <lib/fdio/util.h> |
| #include <stdarg.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <zircon/assert.h> |
| #include <zircon/dlfcn.h> |
| #include <zircon/process.h> |
| #include <zircon/processargs.h> |
| #include <zircon/syscalls.h> |
| |
| #include <array> |
| #include <string_view> |
| |
| #include "lib/svc/cpp/services.h" |
| |
| namespace process { |
| |
| namespace { |
| |
| // This should match the values used by zircon/system/ulib/fdio/spawn.c |
| constexpr size_t kFdioResolvePrefixLen = 10; |
| const char kFdioResolvePrefix[kFdioResolvePrefixLen + 1] = "#!resolve "; |
| |
| // It is possible to setup an infinite loop of resolvers. We want to avoid this |
| // being a common abuse vector, but also stay out of the way of any complex user |
| // setups. This value is the same as spawn.c's above. |
| constexpr int kFdioMaxResolveDepth = 256; |
| |
| } // namespace |
| |
| ProcessBuilder::ProcessBuilder(std::shared_ptr<component::Services> services) |
| : services_(services) { |
| services_->ConnectToService(launcher_.NewRequest()); |
| } |
| |
| ProcessBuilder::ProcessBuilder(zx::job job, |
| std::shared_ptr<component::Services> services) |
| : ProcessBuilder(services) { |
| launch_info_.job = std::move(job); |
| } |
| |
| ProcessBuilder::~ProcessBuilder() = default; |
| |
| void ProcessBuilder::LoadVMO(zx::vmo executable) { |
| launch_info_.executable = std::move(executable); |
| } |
| |
| zx_status_t ProcessBuilder::LoadPath(const std::string& path) { |
| int fd = open(path.c_str(), O_RDONLY); |
| if (fd < 0) |
| return ZX_ERR_IO; |
| |
| zx::vmo executable_vmo; |
| zx_status_t status = |
| fdio_get_vmo_clone(fd, executable_vmo.reset_and_get_address()); |
| close(fd); |
| if (status != ZX_OK) |
| return status; |
| |
| ::fidl::InterfaceHandle<::fuchsia::ldsvc::Loader> loader_iface; |
| |
| // Resolve VMOs containing #!resolve. |
| fuchsia::process::ResolverSyncPtr resolver; // Lazily bound. |
| for (int i = 0; true; i++) { |
| std::array<char, fuchsia::process::MAX_RESOLVE_NAME + kFdioResolvePrefixLen> |
| head; |
| head.fill(0); |
| status = executable_vmo.read(head.data(), 0, head.size()); |
| if (status != ZX_OK) |
| return status; |
| if (memcmp(kFdioResolvePrefix, head.data(), kFdioResolvePrefixLen) != 0) |
| break; // No prefix match. |
| |
| if (i == kFdioMaxResolveDepth) |
| return ZX_ERR_IO_INVALID; // Too much nesting. |
| |
| // The process name is after the prefix until the first newline. |
| std::string_view name(&head[kFdioResolvePrefixLen], |
| fuchsia::process::MAX_RESOLVE_NAME); |
| if (auto newline_index = name.rfind('\n'); |
| newline_index != std::string_view::npos) |
| name = name.substr(0, newline_index); |
| |
| // The resolver will give us a new VMO and loader to use. |
| if (!resolver) |
| services_->ConnectToService(resolver.NewRequest()); |
| resolver->Resolve(fidl::StringPtr(name.data(), name.size()), &status, |
| &executable_vmo, &loader_iface); |
| if (status != ZX_OK) |
| return status; |
| } |
| |
| // Save the loader info. |
| zx::handle loader = loader_iface.TakeChannel(); |
| if (!loader) { |
| // Resolver didn't give us a specific one, clone ours. |
| status = dl_clone_loader_service(loader.reset_and_get_address()); |
| if (status != ZX_OK) |
| return status; |
| } |
| AddHandle(PA_LDSVC_LOADER, std::move(loader)); |
| |
| // Save the VMO info. Name it with the file part of the path. |
| launch_info_.executable = std::move(executable_vmo); |
| const char* name = path.c_str(); |
| if (path.length() >= ZX_MAX_NAME_LEN) { |
| size_t offset = path.rfind('/'); |
| if (offset != std::string::npos) { |
| name += offset + 1; |
| } |
| } |
| launch_info_.executable.set_property(ZX_PROP_NAME, name, strlen(name)); |
| |
| return ZX_OK; |
| } |
| |
| void ProcessBuilder::AddArgs(const std::vector<std::string>& argv) { |
| if (argv.empty()) |
| return; |
| if (launch_info_.name->empty()) |
| launch_info_.name.reset(argv[0]); |
| fidl::VectorPtr<fidl::StringPtr> args; |
| for (const auto& arg : argv) |
| args.push_back(arg); |
| launcher_->AddArgs(std::move(args)); |
| } |
| |
| void ProcessBuilder::AddHandle(uint32_t id, zx::handle handle) { |
| handles_.push_back(fuchsia::process::HandleInfo{ |
| .id = id, |
| .handle = std::move(handle), |
| }); |
| } |
| |
| void ProcessBuilder::AddHandles( |
| std::vector<fuchsia::process::HandleInfo> handles) { |
| handles_->insert(handles_->end(), std::make_move_iterator(handles.begin()), |
| std::make_move_iterator(handles.end())); |
| } |
| |
| void ProcessBuilder::SetDefaultJob(zx::job job) { |
| handles_.push_back(fuchsia::process::HandleInfo{ |
| .id = PA_JOB_DEFAULT, |
| .handle = std::move(job), |
| }); |
| } |
| |
| void ProcessBuilder::SetName(std::string name) { |
| launch_info_.name.reset(std::move(name)); |
| } |
| |
| void ProcessBuilder::CloneJob() { |
| zx::job duplicate_job; |
| if (launch_info_.job) |
| launch_info_.job.duplicate(ZX_RIGHT_SAME_RIGHTS, &duplicate_job); |
| else |
| zx::job::default_job()->duplicate(ZX_RIGHT_SAME_RIGHTS, &duplicate_job); |
| SetDefaultJob(std::move(duplicate_job)); |
| } |
| |
| void ProcessBuilder::CloneNamespace() { |
| fdio_flat_namespace_t* flat = nullptr; |
| zx_status_t status = fdio_ns_export_root(&flat); |
| if (status == ZX_OK) { |
| fidl::VectorPtr<fuchsia::process::NameInfo> names; |
| for (size_t i = 0; i < flat->count; ++i) { |
| names.push_back(fuchsia::process::NameInfo{ |
| flat->path[i], |
| fidl::InterfaceHandle<fuchsia::io::Directory>( |
| zx::channel(flat->handle[i])), |
| }); |
| } |
| launcher_->AddNames(std::move(names)); |
| } |
| free(flat); |
| } |
| |
| void ProcessBuilder::CloneStdio() { |
| // These file descriptors might be closed. Skip over erros cloning them. |
| CloneFileDescriptor(STDIN_FILENO, STDIN_FILENO); |
| CloneFileDescriptor(STDOUT_FILENO, STDOUT_FILENO); |
| CloneFileDescriptor(STDERR_FILENO, STDERR_FILENO); |
| } |
| |
| void ProcessBuilder::CloneEnvironment() { |
| auto env = fidl::VectorPtr<fidl::StringPtr>::New(0); |
| for (size_t i = 0; environ[i]; ++i) |
| env.push_back(environ[i]); |
| launcher_->AddEnvirons(std::move(env)); |
| } |
| |
| void ProcessBuilder::CloneAll() { |
| CloneJob(); |
| CloneNamespace(); |
| CloneStdio(); |
| CloneEnvironment(); |
| } |
| |
| zx_status_t ProcessBuilder::CloneFileDescriptor(int local_fd, int target_fd) { |
| zx_handle_t fdio_handles[FDIO_MAX_HANDLES]; |
| uint32_t fdio_types[FDIO_MAX_HANDLES]; |
| zx_status_t status = |
| fdio_clone_fd(local_fd, target_fd, fdio_handles, fdio_types); |
| if (status < ZX_OK) |
| return status; |
| for (int i = 0; i < status; ++i) { |
| handles_.push_back(fuchsia::process::HandleInfo{ |
| .id = fdio_types[i], |
| .handle = zx::handle(fdio_handles[i]), |
| }); |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t ProcessBuilder::Prepare(std::string* error_message) { |
| zx_status_t status = ZX_OK; |
| launcher_->AddHandles(std::move(handles_)); |
| if (!launch_info_.job) { |
| status = zx::job::default_job()->duplicate(ZX_RIGHT_SAME_RIGHTS, |
| &launch_info_.job); |
| if (status != ZX_OK) |
| return status; |
| } |
| fuchsia::process::CreateWithoutStartingResult result; |
| status = launcher_->CreateWithoutStarting(std::move(launch_info_), &result); |
| if (status != ZX_OK) |
| return status; |
| if (result.status != ZX_OK) { |
| if (error_message) |
| *error_message = result.error_message; |
| return result.status; |
| } |
| if (!result.data) |
| return ZX_ERR_INVALID_ARGS; |
| data_ = std::move(*result.data); |
| return ZX_OK; |
| } |
| |
| zx_status_t ProcessBuilder::Start(zx::process* process_out) { |
| zx_status_t status = |
| zx_process_start(data_.process.get(), data_.thread.get(), data_.entry, |
| data_.sp, data_.bootstrap.release(), data_.vdso_base); |
| if (status == ZX_OK && process_out) |
| *process_out = std::move(data_.process); |
| return status; |
| } |
| |
| } // namespace process |