blob: b216659facdc6158cd97ffe449f1b23b94fb5add [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 {
crate::constants::{DAEMON, DEFAULT_MAX_RETRY_COUNT, OVERNET_MAX_RETRY_COUNT},
crate::daemon::Daemon,
anyhow::{bail, Context, Result},
ffx_config::get,
ffx_lib_args::Ffx,
fidl::endpoints::{ClientEnd, RequestStream, ServiceMarker},
fidl_fuchsia_developer_bridge::{DaemonMarker, DaemonProxy, DaemonRequestStream},
fidl_fuchsia_overnet::{ServiceProviderRequest, ServiceProviderRequestStream},
fidl_fuchsia_overnet_protocol::NodeId,
fuchsia_async::Task,
futures::prelude::*,
hoist::OvernetInstance,
libc,
std::env,
std::os::unix::process::CommandExt,
std::process::{Command, Stdio},
};
mod constants;
mod daemon;
mod discovery;
mod events;
mod fastboot;
mod logger;
mod mdns;
mod onet;
mod ssh;
mod target_task;
mod util;
pub mod target;
pub use constants::{get_socket, LOG_FILE_PREFIX};
async fn create_daemon_proxy(
overnet_instance: &dyn OvernetInstance,
id: &mut NodeId,
) -> Result<DaemonProxy> {
let svc = overnet_instance.connect_as_service_consumer()?;
let (s, p) = fidl::Channel::create().context("failed to create zx channel")?;
svc.connect_to_service(id, DaemonMarker::NAME, s)?;
let proxy = fidl::AsyncChannel::from_channel(p).context("failed to make async channel")?;
Ok(DaemonProxy::new(proxy))
}
// Note that this function assumes the daemon has been started separately.
pub async fn find_and_connect(overnet_instance: &dyn OvernetInstance) -> Result<DaemonProxy> {
let svc = overnet_instance.connect_as_service_consumer()?;
// Sometimes list_peers doesn't properly report the published services - retry a few times
// but don't loop indefinitely.
let max_retry_count: u64 =
get(OVERNET_MAX_RETRY_COUNT).await.unwrap_or(DEFAULT_MAX_RETRY_COUNT);
for _ in 0..max_retry_count {
let peers = svc.list_peers().await?;
for mut peer in peers {
if peer.description.services.is_none() {
continue;
}
if peer
.description
.services
.unwrap()
.iter()
.find(|name| *name == DaemonMarker::NAME)
.is_none()
{
continue;
}
return create_daemon_proxy(overnet_instance, &mut peer.id).await;
}
}
bail!("Timed out waiting for ffx daemon connection")
}
pub async fn spawn_daemon() -> Result<()> {
let mut ffx_path = env::current_exe()?;
// when we daemonize, our path will change to /, so get the canonical path before that occurs.
ffx_path = std::fs::canonicalize(ffx_path)?;
log::info!("Starting new ffx background daemon from {:?}", &ffx_path);
let ffx: Ffx = argh::from_env();
let mut stdout = Stdio::null();
let mut stderr = Stdio::null();
if ffx.verbose {
stdout = Stdio::inherit();
stderr = Stdio::inherit();
} else {
if ffx_config::logging::is_enabled().await {
// TODO(raggi): maybe dup instead.
stdout = Stdio::from(ffx_config::logging::log_file(LOG_FILE_PREFIX).await?);
stderr = Stdio::from(ffx_config::logging::log_file(LOG_FILE_PREFIX).await?);
}
}
let mut cmd = Command::new(ffx_path);
cmd.stdin(Stdio::null()).stdout(stdout).stderr(stderr).env("RUST_BACKTRACE", "full");
if let Some(c) = ffx.config.as_ref() {
cmd.arg("--config").arg(c);
}
if let Some(e) = ffx.env.as_ref() {
cmd.arg("--env").arg(e);
}
cmd.arg(DAEMON);
cmd.arg("start");
daemonize(&mut cmd)
.spawn()
.context("spawning daemon start")?
.wait()
.map(|_| ())
.context("waiting for daemon start")
}
////////////////////////////////////////////////////////////////////////////////
// Overnet Server implementation
async fn next_request(
stream: &mut ServiceProviderRequestStream,
) -> Result<Option<ServiceProviderRequest>> {
Ok(stream.try_next().await.context("error running service provider server")?)
}
async fn exec_server(daemon: Daemon) -> Result<()> {
let (s, p) = fidl::Channel::create().context("failed to create zx channel")?;
let chan = fidl::AsyncChannel::from_channel(s).context("failed to make async channel")?;
let mut stream = ServiceProviderRequestStream::from_channel(chan);
daemon.publish_service(DaemonMarker::NAME, ClientEnd::new(p))?;
while let Some(ServiceProviderRequest::ConnectToService {
chan,
info: _,
control_handle: _control_handle,
}) = next_request(&mut stream).await?
{
log::trace!("Received service request for service");
let chan =
fidl::AsyncChannel::from_channel(chan).context("failed to make async channel")?;
let daemon_clone = daemon.clone();
Task::spawn(async move {
daemon_clone
.handle_requests_from_stream(DaemonRequestStream::from_channel(chan))
.await
.unwrap_or_else(|err| panic!("fatal error handling request: {:?}", err));
})
.detach();
}
Ok(())
}
////////////////////////////////////////////////////////////////////////////////
// start
pub async fn is_daemon_running() -> bool {
// Try to connect directly to the socket. This will fail if nothing is listening on the other side
// (even if the path exists).
let path = get_socket().await;
// Not strictly necessary check, but improves log output for diagnostics
match std::fs::metadata(&path) {
Ok(_) => {}
Err(ref e) if e.kind() == std::io::ErrorKind::NotFound => {
log::info!("no daemon found at {}", &path);
return false;
}
Err(e) => {
log::info!("error stating {}: {}", &path, e);
// speculatively carry on
}
}
match std::os::unix::net::UnixStream::connect(&path) {
Ok(sock) => match sock.peer_addr() {
Ok(_) => {
log::info!("found running daemon at {}", &path);
true
}
Err(err) => {
log::info!("found daemon socket at {} but could not see peer: {}", &path, err);
false
}
},
Err(err) => {
log::info!("failed to connect to daemon at {}: {}", &path, err);
false
}
}
}
pub async fn start() -> Result<()> {
exec_server(Daemon::new().await?).await?;
Ok(())
}
// daemonize adds a pre_exec to call daemon(3) causing the spawned
// process to be forked again and detached from the controlling
// terminal.
fn daemonize(c: &mut Command) -> &mut Command {
unsafe {
c.pre_exec(|| {
// daemonize(3) is deprecated on macOS 10.15. The replacement is not
// yet clear, we may want to replace this with a manual double fork
// setsid, etc.
#[allow(deprecated)]
// First argument: chdir(/)
// Second argument: do not close stdio (we use stdio to write to the daemon log file)
match libc::daemon(0, 1) {
0 => Ok(()),
x => Err(std::io::Error::from_raw_os_error(x)),
}
})
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_daemonize() -> Result<()> {
let started = std::time::Instant::now();
// TODO(raggi): this technically leaks a sleep process, which is
// not ideal, but the much better approach would be a
// significant amount of work, as we'd really want a program
// that will wait for a signal on some other channel (such as a
// unix socket) and otherwise linger as a daemon. If we had
// that, we could then check the ppid and assert that daemon(3)
// really did the work we're expecting it to. As that would
// involve specific subprograms, finding those, and so on, it is
// likely beyond ROI for this test coverage, which aims to just
// prove that the immediate spawn() succeeded was detached from
// the program in question. There is a risk that this
// implementation passes if sleep(1) is not found, which is also
// not ideal.
let mut child = daemonize(Command::new("sleep").arg("10")).spawn()?;
child.wait()?;
assert!(started.elapsed() < std::time::Duration::from_secs(10));
Ok(())
}
}