add context to all fs calls (#423)

Adds additional context in error handling to all `std::fs` and `tokio::fs` calls.

Fixes #309
This commit is contained in:
bmc-msft
2021-01-11 15:55:22 -05:00
committed by GitHub
parent d573100a97
commit 465727680d
14 changed files with 166 additions and 52 deletions

View File

@ -36,7 +36,7 @@ use crate::tasks::{
coverage::{recorder::CoverageRecorder, total::TotalCoverage}, coverage::{recorder::CoverageRecorder, total::TotalCoverage},
utils::default_bool_true, utils::default_bool_true,
}; };
use anyhow::Result; use anyhow::{Context, Result};
use async_trait::async_trait; use async_trait::async_trait;
use futures::stream::StreamExt; use futures::stream::StreamExt;
use onefuzz::{ use onefuzz::{
@ -124,7 +124,9 @@ impl CoverageTask {
seen_inputs = true; seen_inputs = true;
} }
fs::remove_dir_all(&dir.path).await?; fs::remove_dir_all(&dir.path).await.with_context(|| {
format!("unable to remove readonly_inputs: {}", dir.path.display())
})?;
} }
if seen_inputs { if seen_inputs {
@ -154,7 +156,12 @@ impl CoverageTask {
processor: &mut CoverageProcessor, processor: &mut CoverageProcessor,
corpus_dir: &SyncedDir, corpus_dir: &SyncedDir,
) -> Result<bool> { ) -> Result<bool> {
let mut corpus = fs::read_dir(&corpus_dir.path).await?; let mut corpus = fs::read_dir(&corpus_dir.path).await.with_context(|| {
format!(
"unable to read corpus coverage directory: {}",
corpus_dir.path.display()
)
})?;
let mut seen_inputs = false; let mut seen_inputs = false;
while let Some(input) = corpus.next().await { while let Some(input) = corpus.next().await {
@ -208,7 +215,12 @@ impl CoverageProcessor {
if !self.module_totals.contains_key(&module) { if !self.module_totals.contains_key(&module) {
let parent = &self.config.coverage.path.join("by-module"); let parent = &self.config.coverage.path.join("by-module");
fs::create_dir_all(parent).await?; fs::create_dir_all(parent).await.with_context(|| {
format!(
"unable to create by-module coverage directory: {}",
parent.display()
)
})?;
let module_total = parent.join(&module); let module_total = parent.join(&module);
let total = TotalCoverage::new(module_total); let total = TotalCoverage::new(module_total);
self.module_totals.insert(module.clone(), total); self.module_totals.insert(module.clone(), total);
@ -226,7 +238,9 @@ impl CoverageProcessor {
for file in &files { for file in &files {
verbose!("checking {:?}", file); verbose!("checking {:?}", file);
let mut content = fs::read(file).await?; let mut content = fs::read(file)
.await
.with_context(|| format!("unable to read module coverage: {}", file.display()))?;
self.update_module_total(file, &content).await?; self.update_module_total(file, &content).await?;
sum.append(&mut content); sum.append(&mut content);
} }
@ -234,7 +248,9 @@ impl CoverageProcessor {
let mut combined = path.as_os_str().to_owned(); let mut combined = path.as_os_str().to_owned();
combined.push(".cov"); combined.push(".cov");
fs::write(&combined, sum).await?; fs::write(&combined, sum)
.await
.with_context(|| format!("unable to write combined coverage file: {:?}", combined))?;
Ok(combined.into()) Ok(combined.into())
} }

View File

@ -8,7 +8,7 @@ use std::{
sync::Arc, sync::Arc,
}; };
use anyhow::Result; use anyhow::{Context, Result};
use onefuzz::{ use onefuzz::{
fs::{has_files, OwnedDir}, fs::{has_files, OwnedDir},
sha256::digest_file, sha256::digest_file,
@ -47,7 +47,12 @@ impl CoverageRecorder {
self.config.coverage.path.join("inputs").join(digest) self.config.coverage.path.join("inputs").join(digest)
}; };
fs::create_dir_all(&coverage_path).await?; fs::create_dir_all(&coverage_path).await.with_context(|| {
format!(
"unable to create coverage path: {}",
coverage_path.display()
)
})?;
let script = self.invoke_debugger_script(test_input, &coverage_path)?; let script = self.invoke_debugger_script(test_input, &coverage_path)?;
let output = script.wait_with_output().await?; let output = script.wait_with_output().await?;
@ -77,7 +82,14 @@ impl CoverageRecorder {
} }
if !has_files(&coverage_path).await? { if !has_files(&coverage_path).await? {
tokio::fs::remove_dir(&coverage_path).await?; tokio::fs::remove_dir(&coverage_path)
.await
.with_context(|| {
format!(
"unable to remove coverage path: {}",
coverage_path.display()
)
})?;
bail!("no coverage files for input: {}", test_input.display()); bail!("no coverage files for input: {}", test_input.display());
} }

View File

@ -2,7 +2,7 @@
// Licensed under the MIT License. // Licensed under the MIT License.
use crate::tasks::{config::CommonConfig, heartbeat::HeartbeatSender, utils::default_bool_true}; use crate::tasks::{config::CommonConfig, heartbeat::HeartbeatSender, utils::default_bool_true};
use anyhow::Result; use anyhow::{Context, Result};
use futures::{future::try_join_all, stream::StreamExt}; use futures::{future::try_join_all, stream::StreamExt};
use onefuzz::{ use onefuzz::{
fs::list_files, fs::list_files,
@ -120,7 +120,15 @@ impl LibFuzzerFuzzTask {
let mut entries = tokio::fs::read_dir(local_input_dir.path()).await?; let mut entries = tokio::fs::read_dir(local_input_dir.path()).await?;
while let Some(Ok(entry)) = entries.next().await { while let Some(Ok(entry)) = entries.next().await {
let destination_path = self.config.inputs.path.clone().join(entry.file_name()); let destination_path = self.config.inputs.path.clone().join(entry.file_name());
tokio::fs::rename(entry.path(), destination_path).await?; tokio::fs::rename(&entry.path(), &destination_path)
.await
.with_context(|| {
format!(
"unable to move crashing input into results directory: {} - {}?",
entry.path().display(),
destination_path.display()
)
})?;
} }
} }
} }

View File

@ -1,13 +1,19 @@
// Copyright (c) Microsoft Corporation. // Copyright (c) Microsoft Corporation.
// Licensed under the MIT License. // Licensed under the MIT License.
use anyhow::{Error, Result}; use anyhow::{Context, Error, Result};
use onefuzz::telemetry::EventData; use onefuzz::telemetry::EventData;
use std::path::Path; use std::path::Path;
use tokio::io::AsyncBufReadExt; use tokio::io::AsyncBufReadExt;
pub async fn read_stats(output_path: impl AsRef<Path>) -> Result<Vec<EventData>, Error> { pub async fn read_stats(output_path: impl AsRef<Path>) -> Result<Vec<EventData>, Error> {
let f = tokio::fs::File::open(output_path).await?; let output_path = output_path.as_ref();
let f = tokio::fs::File::open(&output_path).await.with_context(|| {
format!(
"unable to open AFL stats for read: {}",
output_path.display()
)
})?;
let mut stats = Vec::new(); let mut stats = Vec::new();
let reader = tokio::io::BufReader::new(f); let reader = tokio::io::BufReader::new(f);

View File

@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation. // Copyright (c) Microsoft Corporation.
// Licensed under the MIT License. // Licensed under the MIT License.
use anyhow::Result; use anyhow::{Context, Result};
use onefuzz::{ use onefuzz::{
http::{is_auth_error_code, ResponseExt}, http::{is_auth_error_code, ResponseExt},
jitter::delay_with_jitter, jitter::delay_with_jitter,
@ -83,7 +83,9 @@ impl StaticConfig {
} }
pub fn from_file(config_path: impl AsRef<Path>) -> Result<Self> { pub fn from_file(config_path: impl AsRef<Path>) -> Result<Self> {
let data = std::fs::read(config_path)?; let config_path = config_path.as_ref();
let data = std::fs::read(config_path)
.with_context(|| format!("unable to read config file: {}", config_path.display()))?;
Self::new(&data) Self::new(&data)
} }
@ -151,14 +153,18 @@ impl DynamicConfig {
pub async fn save(&self) -> Result<()> { pub async fn save(&self) -> Result<()> {
let path = Self::save_path()?; let path = Self::save_path()?;
let data = serde_json::to_vec(&self)?; let data = serde_json::to_vec(&self)?;
fs::write(&path, &data).await?; fs::write(&path, &data)
.await
.with_context(|| format!("unable to save dynamic config: {}", path.display()))?;
info!("saved dynamic-config: {}", path.display()); info!("saved dynamic-config: {}", path.display());
Ok(()) Ok(())
} }
pub async fn load() -> Result<Self> { pub async fn load() -> Result<Self> {
let path = Self::save_path()?; let path = Self::save_path()?;
let data = fs::read(&path).await?; let data = fs::read(&path)
.await
.with_context(|| format!("unable to load dynamic config: {}", path.display()))?;
let ctx: Self = serde_json::from_slice(&data)?; let ctx: Self = serde_json::from_slice(&data)?;
info!("loaded dynamic-config: {}", path.display()); info!("loaded dynamic-config: {}", path.display());
Ok(ctx) Ok(ctx)

View File

@ -4,12 +4,15 @@
use std::fs::metadata; use std::fs::metadata;
use std::path::PathBuf; use std::path::PathBuf;
use anyhow::Result; use anyhow::{Context, Result};
use onefuzz::fs::onefuzz_root; use onefuzz::fs::onefuzz_root;
use tokio::fs; use tokio::fs;
pub async fn set_done_lock() -> Result<()> { pub async fn set_done_lock() -> Result<()> {
fs::write(done_path()?, "").await?; let path = done_path()?;
fs::write(&path, "")
.await
.with_context(|| format!("unable to write done lock: {}", path.display()))?;
Ok(()) Ok(())
} }

View File

@ -4,7 +4,7 @@
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Command; use std::process::Command;
use anyhow::Result; use anyhow::{Context, Result};
use downcast_rs::Downcast; use downcast_rs::Downcast;
use tokio::fs; use tokio::fs;
@ -40,13 +40,14 @@ pub struct Reboot;
impl Reboot { impl Reboot {
pub async fn save_context(&mut self, ctx: RebootContext) -> Result<()> { pub async fn save_context(&mut self, ctx: RebootContext) -> Result<()> {
info!( let path = reboot_context_path()?;
"saving reboot context to: {}",
reboot_context_path()?.display() info!("saving reboot context to: {}", path.display());
);
let data = serde_json::to_vec(&ctx)?; let data = serde_json::to_vec(&ctx)?;
fs::write(reboot_context_path()?, &data).await?; fs::write(&path, &data)
.await
.with_context(|| format!("unable to save reboot context: {}", path.display()))?;
verbose!("reboot context saved"); verbose!("reboot context saved");
@ -72,7 +73,9 @@ impl Reboot {
let data = data?; let data = data?;
let ctx = serde_json::from_slice(&data)?; let ctx = serde_json::from_slice(&data)?;
fs::remove_file(&path).await?; fs::remove_file(&path)
.await
.with_context(|| format!("unable to remove reboot context: {}", path.display()))?;
info!("loaded reboot context"); info!("loaded reboot context");
Ok(Some(ctx)) Ok(Some(ctx))

View File

@ -4,7 +4,7 @@
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::Stdio; use std::process::Stdio;
use anyhow::Result; use anyhow::{Context, Result};
use downcast_rs::Downcast; use downcast_rs::Downcast;
use onefuzz::az_copy; use onefuzz::az_copy;
use onefuzz::process::Output; use onefuzz::process::Output;
@ -48,7 +48,9 @@ impl SetupRunner {
.join(setup_dir); .join(setup_dir);
// `azcopy sync` requires the local dir to exist. // `azcopy sync` requires the local dir to exist.
fs::create_dir_all(&setup_dir).await?; fs::create_dir_all(&setup_dir).await.with_context(|| {
format!("unable to create setup container: {}", setup_dir.display())
})?;
az_copy::sync(setup_url.to_string(), &setup_dir, false).await?; az_copy::sync(setup_url.to_string(), &setup_dir, false).await?;
verbose!( verbose!(
@ -106,8 +108,15 @@ async fn create_setup_symlink(setup_dir: &Path, work_unit: &WorkUnit) -> Result<
use std::os::windows::fs::symlink_dir; use std::os::windows::fs::symlink_dir;
use tokio::task::spawn_blocking; use tokio::task::spawn_blocking;
fs::create_dir(&work_unit.working_dir()?).await?; let working_dir = work_unit.working_dir()?;
let task_setup_dir = work_unit.working_dir()?.join("setup");
fs::create_dir(&working_dir).await.with_context(|| {
format!(
"unable to create working directory: {}",
working_dir.display()
)
})?;
let task_setup_dir = working_dir.join("setup");
// Tokio does not ship async versions of the `std::fs::os` symlink // Tokio does not ship async versions of the `std::fs::os` symlink
// functions (unlike the Unix equivalents). // functions (unlike the Unix equivalents).
@ -129,10 +138,27 @@ async fn create_setup_symlink(setup_dir: &Path, work_unit: &WorkUnit) -> Result<
async fn create_setup_symlink(setup_dir: &Path, work_unit: &WorkUnit) -> Result<()> { async fn create_setup_symlink(setup_dir: &Path, work_unit: &WorkUnit) -> Result<()> {
use tokio::fs::os::unix::symlink; use tokio::fs::os::unix::symlink;
tokio::fs::create_dir_all(work_unit.working_dir()?).await?; let working_dir = work_unit.working_dir()?;
let task_setup_dir = work_unit.working_dir()?.join("setup"); tokio::fs::create_dir_all(&working_dir)
symlink(&setup_dir, &task_setup_dir).await?; .await
.with_context(|| {
format!(
"unable to create working directory: {}",
working_dir.display()
)
})?;
let task_setup_dir = working_dir.join("setup");
symlink(&setup_dir, &task_setup_dir)
.await
.with_context(|| {
format!(
"unable to create symlink from {} to {}",
setup_dir.display(),
task_setup_dir.display()
)
})?;
verbose!( verbose!(
"created symlink from {} to {}", "created symlink from {} to {}",

View File

@ -4,7 +4,7 @@
use std::io::ErrorKind; use std::io::ErrorKind;
use std::path::PathBuf; use std::path::PathBuf;
use anyhow::Result; use anyhow::{Context, Result};
use downcast_rs::Downcast; use downcast_rs::Downcast;
use onefuzz::{blob::BlobContainerUrl, http::is_auth_error}; use onefuzz::{blob::BlobContainerUrl, http::is_auth_error};
use storage_queue::QueueClient; use storage_queue::QueueClient;
@ -63,7 +63,9 @@ impl WorkSet {
info!("saving workset context: {}", path.display()); info!("saving workset context: {}", path.display());
let data = serde_json::to_vec(&self)?; let data = serde_json::to_vec(&self)?;
fs::write(path, &data).await?; fs::write(&path, &data)
.await
.with_context(|| format!("unable to save WorkSet context: {}", path.display()))?;
Ok(()) Ok(())
} }

View File

@ -3,7 +3,7 @@
use std::process::{Child, Command, Stdio}; use std::process::{Child, Command, Stdio};
use anyhow::Result; use anyhow::{Context as AnyhowContext, Result};
use downcast_rs::Downcast; use downcast_rs::Downcast;
use onefuzz::process::{ExitStatus, Output}; use onefuzz::process::{ExitStatus, Output};
use tokio::fs; use tokio::fs;
@ -189,13 +189,20 @@ impl IWorkerRunner for WorkerRunner {
verbose!("worker working dir = {}", working_dir.display()); verbose!("worker working dir = {}", working_dir.display());
fs::create_dir_all(&working_dir).await?; fs::create_dir_all(&working_dir).await.with_context(|| {
format!(
"unable to create working directory: {}",
working_dir.display()
)
})?;
verbose!("created worker working dir: {}", working_dir.display()); verbose!("created worker working dir: {}", working_dir.display());
let config_path = work.config_path()?; let config_path = work.config_path()?;
fs::write(&config_path, work.config.expose_ref()).await?; fs::write(&config_path, work.config.expose_ref())
.await
.with_context(|| format!("unable to save task config: {}", config_path.display()))?;
verbose!( verbose!(
"wrote worker config to config_path = {}", "wrote worker config to config_path = {}",

View File

@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation. // Copyright (c) Microsoft Corporation.
// Licensed under the MIT License. // Licensed under the MIT License.
use anyhow::Result; use anyhow::{Context, Result};
use futures::stream::StreamExt; use futures::stream::StreamExt;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
@ -25,13 +25,19 @@ pub fn onefuzz_etc() -> Result<PathBuf> {
} }
pub async fn has_files(path: impl AsRef<Path>) -> Result<bool> { pub async fn has_files(path: impl AsRef<Path>) -> Result<bool> {
let mut paths = fs::read_dir(&path).await?; let path = path.as_ref();
let mut paths = fs::read_dir(&path)
.await
.with_context(|| format!("unable to check if directory has files: {}", path.display()))?;
let result = paths.next_entry().await?.is_some(); let result = paths.next_entry().await?.is_some();
Ok(result) Ok(result)
} }
pub async fn list_files(path: impl AsRef<Path>) -> Result<Vec<PathBuf>> { pub async fn list_files(path: impl AsRef<Path>) -> Result<Vec<PathBuf>> {
let paths = fs::read_dir(&path).await?; let path = path.as_ref();
let paths = fs::read_dir(&path)
.await
.with_context(|| format!("unable to list files: {}", path.display()))?;
let mut files = paths let mut files = paths
.filter_map(|x| async { .filter_map(|x| async {
@ -106,8 +112,12 @@ pub async fn write_file(path: impl AsRef<Path>, content: &str) -> Result<()> {
let parent = path let parent = path
.parent() .parent()
.ok_or_else(|| format_err!("no parent for: {}", path.display()))?; .ok_or_else(|| format_err!("no parent for: {}", path.display()))?;
fs::create_dir_all(parent).await?; fs::create_dir_all(parent)
fs::write(path, content).await?; .await
.with_context(|| format!("unable to create nested path: {}", parent.display()))?;
fs::write(path, content)
.await
.with_context(|| format!("unable to write file: {}", path.display()))?;
Ok(()) Ok(())
} }
@ -115,10 +125,14 @@ pub async fn reset_dir(dir: impl AsRef<Path>) -> Result<()> {
let dir = dir.as_ref(); let dir = dir.as_ref();
if exists(dir).await? { if exists(dir).await? {
fs::remove_dir_all(dir).await?; fs::remove_dir_all(dir).await.with_context(|| {
format!("unable to remove directory and contents: {}", dir.display())
})?;
} }
fs::create_dir_all(dir).await?; fs::create_dir_all(dir)
.await
.with_context(|| format!("unable to create directory: {}", dir.display()))?;
Ok(()) Ok(())
} }
@ -143,7 +157,9 @@ impl OwnedDir {
} }
pub async fn create_if_missing(&self) -> Result<()> { pub async fn create_if_missing(&self) -> Result<()> {
fs::create_dir_all(self.path()).await?; fs::create_dir_all(self.path())
.await
.with_context(|| format!("unable to create directory: {}", self.path().display()))?;
Ok(()) Ok(())
} }

View File

@ -2,8 +2,9 @@
// Licensed under the MIT License. // Licensed under the MIT License.
use crate::fs::{onefuzz_etc, write_file}; use crate::fs::{onefuzz_etc, write_file};
use anyhow::Result; use anyhow::{Context, Result};
use reqwest_retry::SendRetry; use reqwest_retry::SendRetry;
use std::path::Path;
use std::time::Duration; use std::time::Duration;
use tokio::fs; use tokio::fs;
use uuid::Uuid; use uuid::Uuid;
@ -83,7 +84,10 @@ pub async fn get_scaleset_name() -> Result<Option<String>> {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
pub async fn get_os_machine_id() -> Result<Uuid> { pub async fn get_os_machine_id() -> Result<Uuid> {
let contents = fs::read_to_string("/etc/machine-id").await?; let path = Path::new("/etc/machine-id");
let contents = fs::read_to_string(&path)
.await
.with_context(|| format!("unable to read machine_id: {}", path.display()))?;
let uuid = Uuid::parse_str(contents.trim())?; let uuid = Uuid::parse_str(contents.trim())?;
Ok(uuid) Ok(uuid)
} }

View File

@ -3,7 +3,7 @@
use std::path::Path; use std::path::Path;
use anyhow::Result; use anyhow::{Context, Result};
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use tokio::fs; use tokio::fs;
@ -22,7 +22,10 @@ pub fn digest_iter(data: impl IntoIterator<Item = impl AsRef<[u8]>>) -> String {
} }
pub async fn digest_file(file: impl AsRef<Path>) -> Result<String> { pub async fn digest_file(file: impl AsRef<Path>) -> Result<String> {
let data = fs::read(file).await?; let file = file.as_ref();
let data = fs::read(file)
.await
.with_context(|| format!("unable to read file to generate digest: {}", file.display()))?;
Ok(hex::encode(Sha256::digest(&data))) Ok(hex::encode(Sha256::digest(&data)))
} }

View File

@ -9,7 +9,7 @@ use crate::{
telemetry::{Event, EventData}, telemetry::{Event, EventData},
uploader::BlobUploader, uploader::BlobUploader,
}; };
use anyhow::Result; use anyhow::{Context, Result};
use futures::stream::StreamExt; use futures::stream::StreamExt;
use std::{path::PathBuf, str, time::Duration}; use std::{path::PathBuf, str, time::Duration};
use tokio::fs; use tokio::fs;
@ -55,7 +55,9 @@ impl SyncedDir {
anyhow::bail!("File with name '{}' already exists", self.path.display()); anyhow::bail!("File with name '{}' already exists", self.path.display());
} }
} }
Err(_) => fs::create_dir(&self.path).await.map_err(|e| e.into()), Err(_) => fs::create_dir(&self.path).await.with_context(|| {
format!("unable to create local SyncedDir: {}", self.path.display())
}),
} }
} }