blob: d0265976026806ab926847d3225634d66f3863de [file] [log] [blame]
// 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/bin/zxdb/client/job_context_impl.h"
#include <sstream>
#include "garnet/bin/zxdb/client/job_impl.h"
#include "garnet/bin/zxdb/client/remote_api.h"
#include "garnet/bin/zxdb/client/session.h"
#include "garnet/bin/zxdb/client/setting_schema_definition.h"
#include "garnet/bin/zxdb/client/system_impl.h"
#include "garnet/lib/debug_ipc/helper/message_loop.h"
#include "garnet/lib/debug_ipc/helper/zx_status.h"
#include "garnet/public/lib/fxl/logging.h"
#include "garnet/public/lib/fxl/strings/string_printf.h"
namespace zxdb {
JobContextImpl::JobContextImpl(SystemImpl* system,
bool is_implicit_component_root)
: JobContext(system->session()),
system_(system),
is_implicit_component_root_(is_implicit_component_root),
impl_weak_factory_(this) {
settings_.AddObserver(ClientSettings::Job::kFilters, this);
}
JobContextImpl::~JobContextImpl() {
// If the job is still running, make sure we broadcast terminated
// notifications before deleting everything.
ImplicitlyDetach();
}
std::unique_ptr<JobContextImpl> JobContextImpl::Clone(SystemImpl* system) {
return std::make_unique<JobContextImpl>(system, false);
}
void JobContextImpl::ImplicitlyDetach() {
if (GetJob())
OnDetachReply(Err(), 0, [](fxl::WeakPtr<JobContext>, const Err&) {});
}
JobContext::State JobContextImpl::GetState() const { return state_; }
Job* JobContextImpl::GetJob() const { return job_.get(); }
// static
void JobContextImpl::OnAttachReplyThunk(
fxl::WeakPtr<JobContextImpl> job_context, Callback callback, const Err& err,
uint64_t koid, uint32_t status, const std::string& job_name) {
if (job_context) {
job_context->OnAttachReply(std::move(callback), err, koid, status,
job_name);
if (!job_context->filters_.empty()) {
job_context->SendAndUpdateFilters(job_context->filters_, true);
}
} else {
// The reply that the job was launched came after the local
// objects were destroyed.
if (err.has_error()) {
// Process not launched, forward the error.
callback(job_context, err);
} else {
callback(job_context, Err("Warning: job attach race, extra job is "
"likely attached."));
}
}
}
void JobContextImpl::OnAttachReply(Callback callback, const Err& err,
uint64_t koid, uint32_t status,
const std::string& job_name) {
FXL_DCHECK(state_ == State::kAttaching || state_ == State::kStarting);
FXL_DCHECK(!job_.get()); // Shouldn't have a job.
Err issue_err; // Error to send in callback.
if (err.has_error()) {
// Error from transport.
state_ = State::kNone;
issue_err = err;
} else if (status != 0) {
// Error from launching.
state_ = State::kNone;
issue_err = Err(fxl::StringPrintf("Error attaching, status = %d.", status));
} else {
state_ = State::kRunning;
job_ = std::make_unique<JobImpl>(this, koid, job_name);
}
callback(GetWeakPtr(), issue_err);
}
void JobContextImpl::AttachInternal(debug_ipc::AttachRequest::Type type,
uint64_t koid, Callback callback) {
if (state_ != State::kNone) {
// Avoid reentering caller to dispatch the error.
debug_ipc::MessageLoop::Current()->PostTask(
FROM_HERE, [ callback, weak_ptr = GetWeakPtr() ]() {
callback(std::move(weak_ptr),
Err("Can't attach, job is already running or starting."));
});
return;
}
state_ = State::kAttaching;
debug_ipc::AttachRequest request;
request.koid = koid;
request.type = type;
session()->remote_api()->Attach(
request, [ callback, weak_job_context = impl_weak_factory_.GetWeakPtr() ](
const Err& err, debug_ipc::AttachReply reply) {
OnAttachReplyThunk(std::move(weak_job_context), std::move(callback),
err, reply.koid, reply.status, reply.name);
});
}
void JobContextImpl::Attach(uint64_t koid, Callback callback) {
AttachInternal(debug_ipc::AttachRequest::Type::kJob, koid, callback);
}
void JobContextImpl::AttachToComponentRoot(Callback callback) {
AttachInternal(debug_ipc::AttachRequest::Type::kComponentRoot, 0, callback);
}
void JobContextImpl::Detach(Callback callback) {
if (!job_.get()) {
debug_ipc::MessageLoop::Current()->PostTask(
FROM_HERE, [ callback, weak_ptr = GetWeakPtr() ]() {
callback(std::move(weak_ptr), Err("Error detaching: No job."));
});
return;
}
// This job could have been the one automatically created to watch the
// component root. If the user explicitly detaches it, the user is taking
// control over what job it's attached to so we don't want to track it
// implicitly any more.
is_implicit_component_root_ = false;
debug_ipc::DetachRequest request;
request.koid = job_->GetKoid();
request.type = debug_ipc::DetachRequest::Type::kJob;
session()->remote_api()->Detach(request, [
callback, weak_job_context = impl_weak_factory_.GetWeakPtr()
](const Err& err, debug_ipc::DetachReply reply) {
if (weak_job_context) {
weak_job_context->OnDetachReply(err, reply.status, std::move(callback));
} else {
// The reply that the process was launched came after the local
// objects were destroyed. We're still OK to dispatch either way.
callback(weak_job_context, err);
}
});
}
void JobContextImpl::SendAndUpdateFilters(std::vector<std::string> filters,
bool force_send) {
if (!job_.get()) {
filters_ = std::move(filters);
return;
}
if (!force_send && filters_ == filters) {
return;
}
debug_ipc::JobFilterRequest request;
request.job_koid = job_->GetKoid();
request.filters = filters;
session()->remote_api()->JobFilter(
request, [ filters, weak_job_context = impl_weak_factory_.GetWeakPtr() ](
const Err& err, debug_ipc::JobFilterReply reply) {
if (reply.status != 0) {
printf("Error adding filter, error: %s.",
debug_ipc::ZxStatusToString(reply.status));
if (weak_job_context) {
// Agent failed, reset filters in settings.
// This will also trigger another callback but would be a no-op
// because |force_send| would be false.
weak_job_context->settings_.SetList(ClientSettings::Job::kFilters,
weak_job_context->filters_);
}
return;
}
if (weak_job_context) {
weak_job_context->filters_ = std::move(filters);
}
});
}
void JobContextImpl::OnSettingChanged(const SettingStore&,
const std::string& setting_name) {
FXL_CHECK(setting_name == ClientSettings::Job::kFilters);
SendAndUpdateFilters(settings_.GetList(setting_name));
}
void JobContextImpl::OnDetachReply(const Err& err, uint32_t status,
Callback callback) {
FXL_DCHECK(job_.get()); // Should have a job.
Err issue_err; // Error to send in callback.
if (err.has_error()) {
// Error from transport.
state_ = State::kNone;
issue_err = err;
} else if (status != 0) {
// Error from detaching.
// TODO(donosoc): Print error using ZxStatusToString
issue_err = Err(fxl::StringPrintf("Error detaching, status = %d.", status));
} else {
// Successfully detached.
state_ = State::kNone;
job_.release();
}
callback(GetWeakPtr(), issue_err);
}
} // namespace zxdb