| // Copyright 2016 The Chromium 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 "services/service_manager/service_process_launcher.h" |
| |
| #include <utility> |
| |
| #include "base/base_paths.h" |
| #include "base/base_switches.h" |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/feature_list.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/path_service.h" |
| #include "base/process/kill.h" |
| #include "base/process/launch.h" |
| #include "base/rand_util.h" |
| #include "base/run_loop.h" |
| #include "base/sequence_checker.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/synchronization/lock.h" |
| #include "base/task/post_task.h" |
| #include "base/task_runner_util.h" |
| #include "base/threading/thread.h" |
| #include "build/build_config.h" |
| #include "mojo/public/cpp/bindings/interface_ptr_info.h" |
| #include "mojo/public/cpp/platform/features.h" |
| #include "mojo/public/cpp/platform/platform_channel.h" |
| #include "mojo/public/cpp/system/core.h" |
| #include "mojo/public/cpp/system/invitation.h" |
| #include "services/service_manager/public/cpp/service_executable/switches.h" |
| #include "services/service_manager/public/mojom/service.mojom.h" |
| #include "services/service_manager/sandbox/switches.h" |
| #include "services/service_manager/switches.h" |
| |
| #if defined(OS_LINUX) |
| #include "sandbox/linux/services/namespace_sandbox.h" |
| #endif |
| |
| #if defined(OS_WIN) |
| #include "base/win/windows_version.h" |
| #endif |
| |
| #if defined(OS_MACOSX) |
| #include "mojo/core/embedder/default_mach_broker.h" |
| #endif |
| |
| namespace service_manager { |
| |
| // Thread-safe owner of state related to a service process. This facilitates |
| // safely scheduling the launching and joining of a service process in the |
| // background. |
| class ServiceProcessLauncher::ProcessState |
| : public base::RefCountedThreadSafe<ProcessState> { |
| public: |
| ProcessState() { DETACH_FROM_SEQUENCE(sequence_checker_); } |
| |
| base::ProcessId LaunchInBackground( |
| const Identity& target, |
| SandboxType sandbox_type, |
| std::unique_ptr<base::CommandLine> child_command_line, |
| mojo::PlatformChannel::HandlePassingInfo handle_passing_info, |
| mojo::PlatformChannel channel, |
| mojo::OutgoingInvitation invitation); |
| |
| void StopInBackground(); |
| |
| private: |
| friend class base::RefCountedThreadSafe<ProcessState>; |
| |
| ~ProcessState() = default; |
| |
| base::Process child_process_; |
| SEQUENCE_CHECKER(sequence_checker_); |
| |
| DISALLOW_COPY_AND_ASSIGN(ProcessState); |
| }; |
| |
| ServiceProcessLauncher::ServiceProcessLauncher( |
| ServiceProcessLauncherDelegate* delegate, |
| const base::FilePath& service_path) |
| : delegate_(delegate), |
| service_path_(service_path.empty() |
| ? base::CommandLine::ForCurrentProcess()->GetProgram() |
| : service_path), |
| background_task_runner_(base::CreateSequencedTaskRunnerWithTraits( |
| {base::TaskPriority::USER_VISIBLE, base::WithBaseSyncPrimitives(), |
| base::MayBlock()})) {} |
| |
| ServiceProcessLauncher::~ServiceProcessLauncher() { |
| // We don't really need to wait for the process to be joined, as long as it |
| // eventually happens. Schedule a task to do it, and move on. |
| background_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&ProcessState::StopInBackground, state_)); |
| } |
| |
| mojom::ServicePtr ServiceProcessLauncher::Start(const Identity& target, |
| SandboxType sandbox_type, |
| ProcessReadyCallback callback) { |
| DCHECK(!state_); |
| |
| const base::CommandLine& parent_command_line = |
| *base::CommandLine::ForCurrentProcess(); |
| |
| std::unique_ptr<base::CommandLine> child_command_line( |
| new base::CommandLine(service_path_)); |
| |
| child_command_line->AppendArguments(parent_command_line, false); |
| |
| // Add enabled/disabled features from base::FeatureList. These will take |
| // precedence over existing ones (if there is any copied from the |
| // |parent_command_line| above) as they appear later in the arguments list. |
| std::string enabled_features; |
| std::string disabled_features; |
| base::FeatureList::GetInstance()->GetFeatureOverrides(&enabled_features, |
| &disabled_features); |
| if (!enabled_features.empty()) { |
| child_command_line->AppendSwitchASCII(::switches::kEnableFeatures, |
| enabled_features); |
| } |
| if (!disabled_features.empty()) { |
| child_command_line->AppendSwitchASCII(::switches::kDisableFeatures, |
| disabled_features); |
| } |
| |
| child_command_line->AppendSwitchASCII(switches::kServiceName, target.name()); |
| #ifndef NDEBUG |
| child_command_line->AppendSwitchASCII("g", |
| target.instance_group().ToString()); |
| #endif |
| |
| if (!IsUnsandboxedSandboxType(sandbox_type)) { |
| child_command_line->AppendSwitchASCII( |
| switches::kServiceSandboxType, |
| StringFromUtilitySandboxType(sandbox_type)); |
| } |
| |
| mojo::PlatformChannel::HandlePassingInfo handle_passing_info; |
| mojo::PlatformChannel channel; |
| channel.PrepareToPassRemoteEndpoint(&handle_passing_info, |
| child_command_line.get()); |
| mojo::OutgoingInvitation invitation; |
| mojom::ServicePtr client = |
| PassServiceRequestOnCommandLine(&invitation, child_command_line.get()); |
| |
| if (delegate_) { |
| delegate_->AdjustCommandLineArgumentsForTarget(target, |
| child_command_line.get()); |
| } |
| |
| state_ = base::WrapRefCounted(new ProcessState); |
| base::PostTaskAndReplyWithResult( |
| background_task_runner_.get(), FROM_HERE, |
| base::BindOnce(&ProcessState::LaunchInBackground, state_, target, |
| sandbox_type, std::move(child_command_line), |
| std::move(handle_passing_info), std::move(channel), |
| std::move(invitation)), |
| std::move(callback)); |
| |
| return client; |
| } |
| |
| // static |
| mojom::ServicePtr ServiceProcessLauncher::PassServiceRequestOnCommandLine( |
| mojo::OutgoingInvitation* invitation, |
| base::CommandLine* command_line) { |
| const auto attachment_name = base::NumberToString(base::RandUint64()); |
| command_line->AppendSwitchASCII(switches::kServiceRequestAttachmentName, |
| attachment_name); |
| return mojom::ServicePtr{ |
| mojom::ServicePtrInfo{invitation->AttachMessagePipe(attachment_name), 0}}; |
| } |
| |
| base::ProcessId ServiceProcessLauncher::ProcessState::LaunchInBackground( |
| const Identity& target, |
| SandboxType sandbox_type, |
| std::unique_ptr<base::CommandLine> child_command_line, |
| mojo::PlatformChannel::HandlePassingInfo handle_passing_info, |
| mojo::PlatformChannel channel, |
| mojo::OutgoingInvitation invitation) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| base::LaunchOptions options; |
| #if defined(OS_WIN) |
| options.handles_to_inherit = handle_passing_info; |
| options.stdin_handle = INVALID_HANDLE_VALUE; |
| options.stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE); |
| options.stderr_handle = GetStdHandle(STD_ERROR_HANDLE); |
| // Always inherit stdout/stderr as a pair. |
| if (!options.stdout_handle || !options.stdin_handle) |
| options.stdin_handle = options.stdout_handle = nullptr; |
| |
| // Pseudo handles are used when stdout and stderr redirect to the console. In |
| // that case, they're automatically inherited by child processes. See |
| // https://msdn.microsoft.com/en-us/library/windows/desktop/ms682075.aspx |
| // Trying to add them to the list of handles to inherit causes CreateProcess |
| // to fail. When this process is launched from Python then a real handle is |
| // used. In that case, we do want to add it to the list of handles that is |
| // inherited. |
| if (options.stdout_handle && |
| GetFileType(options.stdout_handle) != FILE_TYPE_CHAR) { |
| options.handles_to_inherit.push_back(options.stdout_handle); |
| } |
| if (options.stderr_handle && |
| GetFileType(options.stderr_handle) != FILE_TYPE_CHAR && |
| options.stdout_handle != options.stderr_handle) { |
| options.handles_to_inherit.push_back(options.stderr_handle); |
| } |
| #elif defined(OS_FUCHSIA) |
| // LaunchProcess will share stdin/out/err with the child process by default. |
| if (!IsUnsandboxedSandboxType(sandbox_type)) |
| NOTIMPLEMENTED(); |
| options.handles_to_transfer = std::move(handle_passing_info); |
| #elif defined(OS_POSIX) |
| const base::FileHandleMappingVector fd_mapping{ |
| {STDIN_FILENO, STDIN_FILENO}, |
| {STDOUT_FILENO, STDOUT_FILENO}, |
| {STDERR_FILENO, STDERR_FILENO}, |
| }; |
| #if defined(OS_MACOSX) |
| if (base::FeatureList::IsEnabled(mojo::features::kMojoChannelMac)) { |
| options.fds_to_remap = fd_mapping; |
| options.mach_ports_for_rendezvous = handle_passing_info; |
| } else { |
| options.fds_to_remap = handle_passing_info; |
| options.fds_to_remap.insert(options.fds_to_remap.end(), fd_mapping.begin(), |
| fd_mapping.end()); |
| } |
| #else |
| handle_passing_info.insert(handle_passing_info.end(), fd_mapping.begin(), |
| fd_mapping.end()); |
| options.fds_to_remap = handle_passing_info; |
| #endif // defined(OS_MACOSX) |
| #endif |
| DVLOG(2) << "Launching child with command line: " |
| << child_command_line->GetCommandLineString(); |
| #if defined(OS_LINUX) |
| if (!IsUnsandboxedSandboxType(sandbox_type)) { |
| child_process_ = |
| sandbox::NamespaceSandbox::LaunchProcess(*child_command_line, options); |
| if (!child_process_.IsValid()) |
| LOG(ERROR) << "Starting the process with a sandbox failed."; |
| } else |
| #endif |
| { |
| #if defined(OS_MACOSX) |
| mojo::core::DefaultMachBroker* mach_broker = |
| mojo::core::DefaultMachBroker::Get(); |
| base::AutoLock locker(mach_broker->GetLock()); |
| #endif |
| child_process_ = base::LaunchProcess(*child_command_line, options); |
| #if defined(OS_MACOSX) |
| mach_broker->ExpectPid(child_process_.Handle()); |
| #endif |
| } |
| |
| channel.RemoteProcessLaunchAttempted(); |
| if (!child_process_.IsValid()) { |
| LOG(ERROR) << "Failed to start child process for service: " |
| << target.name(); |
| return base::kNullProcessId; |
| } |
| |
| #if defined(OS_CHROMEOS) |
| // Always log instead of DVLOG because knowing which pid maps to which |
| // service is vital for interpreting crashes after-the-fact and Chrome OS |
| // devices generally run release builds, even in development. |
| VLOG(0) |
| #else |
| DVLOG(0) |
| #endif |
| << "Launched child process pid=" << child_process_.Pid() |
| << " id=" << target.ToString(); |
| |
| mojo::OutgoingInvitation::Send(std::move(invitation), child_process_.Handle(), |
| channel.TakeLocalEndpoint()); |
| |
| return child_process_.Pid(); |
| } |
| |
| void ServiceProcessLauncher::ProcessState::StopInBackground() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (!child_process_.IsValid()) |
| return; |
| |
| int rv = -1; |
| LOG_IF(ERROR, !child_process_.WaitForExit(&rv)) |
| << "Failed to wait for child process"; |
| child_process_.Close(); |
| } |
| |
| } // namespace service_manager |