blob: 1fe0980b7b0be68075e19bf56989ea2cc331bfee [file] [log] [blame]
// Copyright 2019 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::{format_err, Error},
fidl::endpoints::DiscoverableService,
fidl_fuchsia_inspect::TreeMarker,
fidl_fuchsia_inspect_deprecated::InspectMarker,
fidl_fuchsia_io::NodeInfo,
files_async,
fuchsia_inspect::reader::{self, DiagnosticsHierarchy, PartialNodeHierarchy},
fuchsia_zircon::DurationNum,
futures::stream::StreamExt,
inspect_fidl_load as inspect_fidl, io_util,
lazy_static::lazy_static,
std::{convert::TryFrom, path::PathBuf, str::FromStr},
};
lazy_static! {
static ref EXPECTED_FILES: Vec<(String, InspectType)> = vec![
(<TreeMarker as fidl::endpoints::ServiceMarker>::DEBUG_NAME.to_string(), InspectType::Tree),
(
<InspectMarker as fidl::endpoints::ServiceMarker>::DEBUG_NAME.to_string(),
InspectType::DeprecatedFidl,
),
(".inspect".to_string(), InspectType::Vmo),
];
static ref READDIR_TIMEOUT_SECONDS: i64 = 15;
}
/// Gets an iterator over all inspect files in a directory.
pub async fn all_locations(root: impl AsRef<str>) -> Result<Vec<InspectLocation>, Error> {
let mut path = std::env::current_dir()?;
let root = root.as_ref();
path.push(&root);
let dir_proxy = io_util::open_directory_in_namespace(
&path.to_string_lossy().to_string(),
io_util::OPEN_RIGHT_READABLE,
)?;
let locations =
files_async::readdir_recursive(&dir_proxy, Some(READDIR_TIMEOUT_SECONDS.seconds()))
.filter_map(|result| async move {
match result {
Err(err) => {
eprintln!("{}", err);
None
}
Ok(entry) => {
let mut path = PathBuf::from(&root);
path.push(&entry.name);
EXPECTED_FILES
.iter()
.find(|(filename, _)| entry.name.ends_with(filename))
.map(|(_, inspect_type)| InspectLocation {
inspect_type: inspect_type.clone(),
path,
})
}
}
})
.collect::<Vec<InspectLocation>>()
.await;
Ok(locations)
}
/// Type of the inspect file.
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum InspectType {
Vmo,
DeprecatedFidl,
Tree,
}
/// InspectLocation of an inspect file.
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub struct InspectLocation {
/// The type of the inspect location.
pub inspect_type: InspectType,
/// The path to the inspect location.
pub path: PathBuf,
}
impl InspectLocation {
pub fn absolute_path(&self) -> Result<String, Error> {
// Note: self.path.canonicalize() returns error for files such as:
// /hub/r/test/*/c/iquery_test_component.cmx/*/out/diagnostics/root.inspect
// Hence, getting the absolute path manually.
let current_dir = std::env::current_dir()?.to_string_lossy().to_string();
let path_string =
self.path.canonicalize().unwrap_or(self.path.clone()).to_string_lossy().to_string();
if path_string.is_empty() {
return Ok(current_dir);
}
if path_string.chars().next() == Some('/') {
return Ok(path_string);
}
if current_dir == "/" {
return Ok(format!("/{}", path_string));
}
Ok(format!("{}/{}", current_dir, path_string))
}
pub async fn load(self) -> Result<InspectObject, Error> {
InspectObject::new(self).await
}
}
impl FromStr for InspectLocation {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
// Some valid locations won't include the service name in the name and will be just the
// directory. Append the name and attempt to load that file.
InspectLocation::try_from(PathBuf::from(s))
.or_else(|_| {
let mut path = PathBuf::from(s);
path.push(InspectMarker::SERVICE_NAME);
InspectLocation::try_from(path)
})
.or_else(|_| {
let mut path = PathBuf::from(s);
path.push(TreeMarker::SERVICE_NAME);
InspectLocation::try_from(path)
})
}
}
impl TryFrom<PathBuf> for InspectLocation {
type Error = anyhow::Error;
fn try_from(path: PathBuf) -> Result<Self, Self::Error> {
match path.file_name() {
None => return Err(format_err!("Failed to get filename")),
Some(filename) => {
if filename == InspectMarker::SERVICE_NAME && path.exists() {
Ok(InspectLocation { inspect_type: InspectType::DeprecatedFidl, path })
} else if filename == TreeMarker::SERVICE_NAME && path.exists() {
Ok(InspectLocation { inspect_type: InspectType::Tree, path })
} else if filename.to_string_lossy().ends_with(".inspect") {
Ok(InspectLocation { inspect_type: InspectType::Vmo, path })
} else {
return Err(format_err!("Not an inspect file"));
}
}
}
}
}
#[derive(Debug)]
pub struct InspectObject {
pub location: InspectLocation,
pub hierarchy: Option<DiagnosticsHierarchy>,
}
impl InspectObject {
async fn new(location: InspectLocation) -> Result<Self, Error> {
let mut this = Self { location, hierarchy: None };
match this.location.inspect_type {
InspectType::Vmo => this.load_from_vmo().await?,
InspectType::Tree => this.load_from_tree().await?,
InspectType::DeprecatedFidl => this.load_from_fidl().await?,
}
Ok(this)
}
async fn load_from_tree(&mut self) -> Result<(), Error> {
let path = self.location.absolute_path()?;
let (tree, server) = fidl::endpoints::create_proxy::<TreeMarker>()?;
fdio::service_connect(&path, server.into_channel())?;
self.hierarchy = Some(reader::read(&tree).await?);
Ok(())
}
async fn load_from_vmo(&mut self) -> Result<(), Error> {
let proxy = io_util::open_file_in_namespace(
&self.location.absolute_path()?,
io_util::OPEN_RIGHT_READABLE,
)?;
// Obtain the vmo backing any VmoFiles.
let node_info = proxy.describe().await?;
match node_info {
NodeInfo::Vmofile(vmofile) => {
self.hierarchy = Some(PartialNodeHierarchy::try_from(&vmofile.vmo)?.into());
Ok(())
}
NodeInfo::File(_) => {
let bytes = io_util::read_file_bytes(&proxy).await?;
self.hierarchy = Some(PartialNodeHierarchy::try_from(bytes)?.into());
Ok(())
}
_ => Err(format_err!("Unknown inspect file at {}", self.location.path.display())),
}
}
async fn load_from_fidl(&mut self) -> Result<(), Error> {
let path = self.location.absolute_path()?;
self.hierarchy = Some(inspect_fidl::load_hierarchy_from_path(&path).await?);
Ok(())
}
}