blob: 2c81c9f067c3d28d782188f95bf78e50c60559cd [file] [log] [blame]
// Copyright 2020 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.
use anyhow::{bail, Context, Result};
use daemonize::daemonize;
use errors::{ffx_error, FfxError};
use ffx_config::EnvironmentContext;
use fidl::endpoints::DiscoverableProtocolMarker;
use fidl_fuchsia_developer_ffx::{DaemonMarker, DaemonProxy};
use fidl_fuchsia_overnet_protocol::NodeId;
use fuchsia_async::{Time, Timer};
use futures::prelude::*;
use hoist::{Hoist, OvernetInstance};
use nix::sys::signal;
use std::{
path::{Path, PathBuf},
pin::Pin,
process::{Child, Command},
time::Duration,
};
mod config;
mod constants;
mod socket;
pub use config::*;
pub use constants::LOG_FILE_PREFIX;
pub use socket::SocketDetails;
async fn create_daemon_proxy(hoist: &Hoist, id: &mut NodeId) -> Result<DaemonProxy> {
let svc = hoist.connect_as_service_consumer()?;
let (s, p) = fidl::Channel::create();
svc.connect_to_service(id, DaemonMarker::PROTOCOL_NAME, s)?;
let proxy = fidl::AsyncChannel::from_channel(p).context("failed to make async channel")?;
Ok(DaemonProxy::new(proxy))
}
pub async fn get_daemon_proxy_single_link(
hoist: &Hoist,
socket_path: PathBuf,
exclusions: Option<Vec<NodeId>>,
) -> Result<(NodeId, DaemonProxy, Pin<Box<impl Future<Output = Result<()>>>>), FfxError> {
// Start a race betwen:
// - The unix socket link being lost
// - A timeout
// - Getting a FIDL proxy over the link
let cso = if ffx_config::get_connection_modes().await.use_cso() {
hoist::Cso::Enabled
} else {
hoist::Cso::Disabled
};
let link = hoist.clone().run_single_ascendd_link(socket_path.clone(), cso).fuse();
let mut link = Box::pin(link);
let find = find_next_daemon(hoist, exclusions).fuse();
let mut find = Box::pin(find);
let mut timeout = Timer::new(Duration::from_secs(5)).fuse();
let res = futures::select! {
r = link => {
Err(ffx_error!("Daemon link lost while attempting to connect to socket {}: {:#?}\nRun `ffx doctor` for further diagnostics.", socket_path.display(), r))
}
_ = timeout => {
Err(ffx_error!("Timed out waiting for the ffx daemon on the Overnet mesh over socket {}.\nRun `ffx doctor --restart-daemon` for further diagnostics.", socket_path.display()))
}
proxy = find => proxy.map_err(|e| ffx_error!("Error connecting to Daemon at socket: {}: {:#?}\nRun `ffx doctor` for further diagnostics.", socket_path.display(), e)),
};
res.map(|(nodeid, proxy)| (nodeid, proxy, link))
}
async fn find_next_daemon<'a>(
hoist: &Hoist,
exclusions: Option<Vec<NodeId>>,
) -> Result<(NodeId, DaemonProxy)> {
let svc = hoist.connect_as_service_consumer()?;
loop {
let peers = svc.list_peers().await?;
for peer in peers.iter() {
if peer
.description
.services
.as_ref()
.unwrap_or(&Vec::new())
.iter()
.find(|name| *name == DaemonMarker::PROTOCOL_NAME)
.is_none()
{
continue;
}
match exclusions {
Some(ref exclusions) => {
if exclusions.iter().any(|n| *n == peer.id) {
continue;
}
}
None => {}
}
return create_daemon_proxy(hoist, &mut peer.id.clone())
.await
.map(|proxy| (peer.id.clone(), proxy));
}
}
}
// Note that this function assumes the daemon has been started separately.
pub async fn find_and_connect(hoist: &Hoist, socket_path: PathBuf) -> Result<DaemonProxy> {
// This function is due for deprecation/removal. It should only be used
// currently by the doctor daemon_manager, which should instead learn to
// understand the link state in future revisions.
get_daemon_proxy_single_link(hoist, socket_path, None)
.await
.map(|(_nodeid, proxy, link_fut)| {
fuchsia_async::Task::local(link_fut.map(|_| ())).detach();
proxy
})
.context("connecting to the ffx daemon")
}
// We have both spawn_daemon(), and run_daemon() below, because we want
// the daemon to be handled in two different ways. "Real" invocations use
// spawn_daemon(), because it daemonizes the process, disconnecting it from the
// controlling terminal, etc. "Test" invocations uses run_daemon(), because
// they want to have control of the child process, running wait(), etc.
#[tracing::instrument]
pub async fn spawn_daemon(context: &EnvironmentContext) -> Result<()> {
let mut cmd = daemon_cmd(context).await?;
tracing::info!("Starting new background ffx daemon from {:?}", &cmd.get_program());
daemonize(&mut cmd)
.spawn()
.context("spawning daemon start")?
.wait()
.map(|_| ())
.context("waiting for daemon start")
}
// See the above comment for spawn_daemon(). This function is only used by the
// "ffx self-test" function `test_config_flag()`.
#[tracing::instrument]
pub async fn run_daemon(context: &EnvironmentContext) -> Result<Child> {
let mut cmd = daemon_cmd(context).await?;
tracing::info!("Starting new ffx daemon from {:?}", &cmd.get_program());
let child = cmd.spawn().context("running daemon start")?;
Ok(child)
}
#[tracing::instrument]
async fn daemon_cmd(context: &EnvironmentContext) -> Result<Command> {
use std::process::Stdio;
let mut cmd = context.rerun_prefix().await?;
let socket_path = context.get_ascendd_path().await.context("No socket path configured")?;
let mut stdout = Stdio::null();
let mut stderr = Stdio::null();
if ffx_config::logging::is_enabled(context).await {
stdout = Stdio::from(ffx_config::logging::log_file(context, LOG_FILE_PREFIX, true).await?);
// Second argument is false, meaning don't perform log rotation. We rotated the logs once
// for the call above, we shouldn't do it again.
stderr = Stdio::from(ffx_config::logging::log_file(context, LOG_FILE_PREFIX, false).await?);
}
cmd.stdin(Stdio::null()).stdout(stdout).stderr(stderr).env("RUST_BACKTRACE", "full");
cmd.arg("daemon");
cmd.arg("start");
cmd.arg("--path").arg(socket_path);
Ok(cmd)
}
// Time between polling to see if process has exited
const STOP_WAIT_POLL_TIME: Duration = Duration::from_millis(50);
pub async fn try_to_kill_pid(pid: u32) -> Result<()> {
// UNIX defines a pid as a _signed_ int -- who knew?
let nix_pid = nix::unistd::Pid::from_raw(pid as i32);
let res = signal::kill(nix_pid, Some(signal::Signal::SIGTERM));
match res {
// No longer there
Err(nix::errno::Errno::ESRCH) => return Ok(()),
// Kill failed for some other reason
Err(e) => bail!("Could not kill daemon: {e}"),
// The kill() worked, i.e. the process was still around
_ => (),
}
Timer::new(STOP_WAIT_POLL_TIME).await;
// Check to see if it actually died
let res = signal::kill(nix_pid, None);
match res {
// No longer there
Err(nix::errno::Errno::ESRCH) => return Ok(()),
// Kill failed for some other reason
Err(e) => bail!("Could not kill daemon: {e}"),
// The kill() worked, i.e. the process was still around
_ => bail!("Daemon did not exit. Giving up"),
}
}
// Similar to ffx_daemon::wait_for_daemon_to_exit(), but this version is
// for non-interactive use.
pub async fn wait_for_daemon_to_exit(pid: u32, timeout_ms: u32) -> Result<()> {
let start_time = Time::now();
let nix_pid = nix::unistd::Pid::from_raw(pid as i32);
loop {
if let Err(nix::errno::Errno::ESRCH) = signal::kill(nix_pid, None) {
// The pid has exited
return Ok(());
}
Timer::new(STOP_WAIT_POLL_TIME).await;
if Time::now() - start_time > Duration::from_millis(timeout_ms.into()) {
return try_to_kill_pid(pid).await;
}
}
}
////////////////////////////////////////////////////////////////////////////////
// start
#[tracing::instrument]
pub fn is_daemon_running_at_path(socket_path: &Path) -> bool {
// Not strictly necessary check, but improves log output for diagnostics
match std::fs::metadata(socket_path) {
Ok(_) => {}
Err(ref e) if e.kind() == std::io::ErrorKind::NotFound => {
tracing::info!("no daemon found at {}", socket_path.display());
return false;
}
Err(e) => {
tracing::info!("error stating {}: {}", socket_path.display(), e);
// speculatively carry on
}
}
let sock = hoist::short_socket_path(&socket_path)
.and_then(|safe_socket_path| std::os::unix::net::UnixStream::connect(&safe_socket_path));
match sock {
Ok(sock) => match sock.peer_addr() {
Ok(_) => {
tracing::debug!("found running daemon at {}", socket_path.display());
true
}
Err(err) => {
tracing::info!(
"found daemon socket at {} but could not see peer: {}",
socket_path.display(),
err
);
false
}
},
Err(err) => {
tracing::info!("failed to connect to daemon at {}: {}", socket_path.display(), err);
false
}
}
}