blob: d049814d45941319ea2ebca9672c9e84eec591dd [file] [log] [blame]
// Copyright 2016 The Chromium Authors
// 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/metrics/field_trial.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/thread_pool.h"
#include "base/threading/thread.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "mojo/public/cpp/platform/platform_channel.h"
#include "mojo/public/cpp/system/invitation.h"
#include "sandbox/policy/mojom/sandbox.mojom.h"
#include "sandbox/policy/sandbox_type.h"
#include "sandbox/policy/switches.h"
#include "services/service_manager/public/cpp/service_executable/switches.h"
#include "services/service_manager/public/mojom/service.mojom.h"
#include "services/service_manager/switches.h"
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
#include "sandbox/linux/services/namespace_sandbox.h"
#endif
#if BUILDFLAG(IS_WIN)
#include "base/win/windows_version.h"
#include <windows.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_); }
ProcessState(const ProcessState&) = delete;
ProcessState& operator=(const ProcessState&) = delete;
base::ProcessId LaunchInBackground(
const Identity& target,
sandbox::mojom::Sandbox 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_);
};
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::ThreadPool::CreateSequencedTaskRunner(
{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_));
}
mojo::PendingRemote<mojom::Service> ServiceProcessLauncher::Start(
const Identity& target,
sandbox::mojom::Sandbox 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);
}
// Use --force-field-trials to make the child process to create field trials.
std::string field_trial_states;
base::FieldTrialList::AllStatesToString(&field_trial_states);
if (!field_trial_states.empty()) {
DCHECK(!child_command_line->HasSwitch(::switches::kForceFieldTrials));
child_command_line->AppendSwitchASCII(::switches::kForceFieldTrials,
field_trial_states);
}
child_command_line->AppendSwitchASCII(switches::kServiceName, target.name());
#ifndef NDEBUG
child_command_line->AppendSwitchASCII("g",
target.instance_group().ToString());
#endif
if (!sandbox::policy::IsUnsandboxedSandboxType(sandbox_type)) {
child_command_line->AppendSwitchASCII(
sandbox::policy::switches::kServiceSandboxType,
sandbox::policy::StringFromUtilitySandboxType(sandbox_type));
}
mojo::PlatformChannel::HandlePassingInfo handle_passing_info;
mojo::PlatformChannel channel;
channel.PrepareToPassRemoteEndpoint(&handle_passing_info,
child_command_line.get());
mojo::OutgoingInvitation invitation;
auto client =
PassServiceRequestOnCommandLine(&invitation, child_command_line.get());
if (delegate_) {
delegate_->AdjustCommandLineArgumentsForTarget(target,
child_command_line.get());
}
state_ = base::WrapRefCounted(new ProcessState);
background_task_runner_->PostTaskAndReplyWithResult(
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
mojo::PendingRemote<mojom::Service>
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 mojo::PendingRemote<mojom::Service>(
invitation->AttachMessagePipe(attachment_name), 0);
}
base::ProcessId ServiceProcessLauncher::ProcessState::LaunchInBackground(
const Identity& target,
sandbox::mojom::Sandbox 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 BUILDFLAG(IS_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 BUILDFLAG(IS_FUCHSIA)
// LaunchProcess will share stdin/out/err with the child process by default.
if (!sandbox::policy::IsUnsandboxedSandboxType(sandbox_type))
NOTIMPLEMENTED();
options.handles_to_transfer = std::move(handle_passing_info);
#elif BUILDFLAG(IS_POSIX)
const base::FileHandleMappingVector fd_mapping{
{STDIN_FILENO, STDIN_FILENO},
{STDOUT_FILENO, STDOUT_FILENO},
{STDERR_FILENO, STDERR_FILENO},
};
#if BUILDFLAG(IS_MAC)
options.fds_to_remap = fd_mapping;
options.mach_ports_for_rendezvous = handle_passing_info;
#else
handle_passing_info.insert(handle_passing_info.end(), fd_mapping.begin(),
fd_mapping.end());
options.fds_to_remap = handle_passing_info;
#endif // BUILDFLAG(IS_MAC)
#endif
DVLOG(2) << "Launching child with command line: "
<< child_command_line->GetCommandLineString();
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
if (!sandbox::policy::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
{
child_process_ = base::LaunchProcess(*child_command_line, options);
}
channel.RemoteProcessLaunchAttempted();
if (!child_process_.IsValid()) {
LOG(ERROR) << "Failed to start child process for service: "
<< target.name();
return base::kNullProcessId;
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
// 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