blob: 328058ac6f563fb2deff07188ce72556574c4e5a [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, Error},
fuchsia_triage::{ActionTagDirective, DiagnosticData, ParseResult, Source},
std::{collections::HashMap, fs, path::Path},
};
// Third field is whether the file is required.
const SNAPSHOT_FILES: [(&str, Source, bool); 5] = [
("inspect.json", Source::Inspect, true),
("log.kernel.txt", Source::Klog, false),
("log.system.txt", Source::Syslog, false),
("log.system.previous_boot.txt", Source::Bootlog, false),
("annotations.json", Source::Annotations, false),
];
pub fn diagnostics_from_directory(directory: &Path) -> Result<Vec<DiagnosticData>, Error> {
SNAPSHOT_FILES
.iter()
.map(|(name, source, required)| diagnostic_from_file(directory, name, source, required))
.collect::<Result<Vec<_>, _>>()
}
fn diagnostic_from_file(
directory: &Path,
file_name: &str,
source: &Source,
required: &bool,
) -> Result<DiagnosticData, Error> {
let file_path = directory.join(file_name).into_os_string().to_string_lossy().to_string();
let contents = match fs::read_to_string(&file_path) {
Ok(contents) => contents,
Err(_) => {
if *required {
bail!("Couldn't read file '{}' to string", file_path);
} else {
return Ok(DiagnosticData::new_empty(file_path, *source));
}
}
};
DiagnosticData::new(file_path, *source, contents)
}
pub fn config_from_files<T: AsRef<Path>>(
config_files: &[T],
action_tag_directive_from_tags: &ActionTagDirective,
) -> Result<ParseResult, Error> {
let mut config_file_map: HashMap<String, String> = HashMap::new();
for file_name in config_files {
let file_name = file_name.as_ref();
let file_data = match fs::read_to_string(file_name) {
Ok(data) => data,
Err(e) => {
bail!("Couldn't read config file '{}' to string, {}", file_name.display(), e);
}
};
match serde_json::from_str::<serde_json::Value>(&file_data) {
// This file looks like a bundle.
// Bundles are JSON with a "files" key containing name => content
// mappings.
Ok(serde_json::Value::Object(file_object)) if file_object.contains_key("files") => {
match file_object.get("files").unwrap() {
serde_json::Value::Object(files) => {
for (name, content) in files {
match content {
serde_json::Value::String(content) => {
config_file_map.insert(name.to_string(), content.to_string());
}
_ => {
bail!("File {} looks like a bundle, but key {} must contain a string.", file_name.display(), name);
}
}
}
}
_ => {
bail!(
"File {} looks like a bundle, but key 'files' is not an object.",
file_name.display()
);
}
}
}
// The file does not look like a bundle.
// Get the base name of the file as the namesapce, and add it to the config file set.
_ => {
let namespace = base_name(file_name)?;
config_file_map.insert(namespace, file_data);
}
}
}
ParseResult::new(&config_file_map, &action_tag_directive_from_tags)
}
fn base_name(path: &Path) -> Result<String, Error> {
if let Some(s) = path.file_stem() {
if let Some(s) = s.to_str() {
return Ok(s.to_owned());
}
}
bail!("Bad path {} - can't find file_stem", path.display())
}
#[cfg(test)]
mod test {
use super::*;
#[fuchsia::test]
fn base_name_works() -> Result<(), Error> {
assert_eq!(base_name(Path::new("foo/bar/baz.ext"))?, "baz".to_string());
Ok(())
}
}