blob: d722aacd5d572ed33188d4c659376b6940dd228e [file] [log] [blame]
// Copyright 2022 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},
chrono::{TimeZone, Utc},
fxfs::{
crypt::Crypt,
filesystem::OpenFxFilesystem,
fsck,
object_handle::{GetProperties, ObjectHandle, ReadObjectHandle, WriteObjectHandle},
object_store::{
directory::replace_child,
transaction::{Options, TransactionHandler},
volume::root_volume,
Directory, HandleOptions, ObjectDescriptor, ObjectStore,
},
},
std::{io::Write, path::Path, sync::Arc},
};
const DEFAULT_VOLUME: &str = "default";
pub async fn print_ls(dir: &Directory<ObjectStore>) -> Result<(), Error> {
const DATE_FMT: &str = "%b %d %Y %T+00";
let layer_set = dir.store().tree().layer_set();
let mut merger = layer_set.merger();
let mut iter = dir.iter(&mut merger).await?;
while let Some((name, object_id, descriptor)) = iter.get() {
match descriptor {
ObjectDescriptor::File => {
let handle = ObjectStore::open_object(
dir.owner(),
object_id,
HandleOptions::default(),
None,
)
.await?;
let properties = handle.get_properties().await?;
let size = properties.data_attribute_size;
let mtime = Utc.timestamp(
properties.modification_time.secs as i64,
properties.modification_time.nanos,
);
println!(
"-rwx------ 1 nobody nogroup {:>8} {:>12} {}",
size,
mtime.format(DATE_FMT),
name
);
}
ObjectDescriptor::Directory => {
let mtime = Utc.timestamp(0, 0);
println!(
"d--------- 1 nobody nogroup 0 {:>12} {}",
mtime.format(DATE_FMT),
name
);
}
ObjectDescriptor::Volume => unimplemented!(),
}
iter.advance().await?;
}
Ok(())
}
/// Opens a volume on a device and returns a Directory to it's root.
pub async fn open_volume(
fs: &OpenFxFilesystem,
crypt: Arc<dyn Crypt>,
) -> Result<Arc<ObjectStore>, Error> {
let root_volume = root_volume(fs).await?;
root_volume.volume(DEFAULT_VOLUME, Some(crypt)).await.map(|v| v.into())
}
/// Walks a directory path from a given root.
pub async fn walk_dir(
vol: &Arc<ObjectStore>,
path: &Path,
) -> Result<Directory<ObjectStore>, Error> {
let mut dir: Directory<ObjectStore> =
Directory::open(vol, vol.root_directory_object_id()).await?;
for path in path.to_str().unwrap().split('/') {
if path.len() == 0 {
continue;
}
if let Some((object_id, descriptor)) = dir.lookup(&path).await? {
if descriptor != ObjectDescriptor::Directory {
bail!("Not a directory: {}", path);
}
dir = Directory::open(&dir.owner(), object_id).await?;
} else {
bail!("Not found: {}", path);
}
}
Ok(dir)
}
pub async fn unlink(
fs: &OpenFxFilesystem,
vol: &Arc<ObjectStore>,
path: &Path,
) -> Result<(), Error> {
let dir = walk_dir(vol, path.parent().unwrap()).await?;
let mut transaction = (*fs).clone().new_transaction(&[], Options::default()).await?;
replace_child(&mut transaction, None, (&dir, path.file_name().unwrap().to_str().unwrap()))
.await?;
transaction.commit().await?;
Ok(())
}
// Used to fsck after mutating operations.
pub async fn fsck(
fs: &OpenFxFilesystem,
crypt: Arc<dyn Crypt>,
verbose: bool,
) -> Result<(), Error> {
// Re-open the filesystem to ensure it's locked.
let options = fsck::FsckOptions {
fail_on_warning: false,
halt_on_error: false,
do_slow_passes: true,
on_error: |err| eprintln!("{:?}", err.to_string()),
verbose,
};
fsck::fsck_with_options(fs, Some(crypt), options).await
}
/// Read a file's contents into a Vec and return it.
pub async fn get(vol: &Arc<ObjectStore>, src: &Path) -> Result<Vec<u8>, Error> {
let dir = walk_dir(vol, src.parent().unwrap()).await?;
if let Some((object_id, descriptor)) =
dir.lookup(&src.file_name().unwrap().to_str().unwrap()).await?
{
if descriptor != ObjectDescriptor::File {
bail!("Expected File. Found {:?}", descriptor);
}
let handle =
ObjectStore::open_object(dir.owner(), object_id, HandleOptions::default(), None)
.await?;
let mut out: Vec<u8> = Vec::new();
let mut buf = handle.allocate_buffer(handle.block_size() as usize);
let mut ofs = 0;
loop {
let bytes = handle.read(ofs, buf.as_mut()).await?;
ofs += bytes as u64;
out.write_all(&buf.as_ref().as_slice()[..bytes])?;
if bytes as u64 != handle.block_size() {
break;
}
}
Ok(out)
} else {
bail!("File not found");
}
}
/// Write the contents of a Vec to a file int he image.
pub async fn put(
fs: &OpenFxFilesystem,
vol: &Arc<ObjectStore>,
dst: &Path,
data: Vec<u8>,
) -> Result<(), Error> {
let dir = walk_dir(vol, dst.parent().unwrap()).await?;
let filename = dst.file_name().unwrap().to_str().unwrap();
let mut transaction = (*fs).clone().new_transaction(&[], Options::default()).await?;
if let Some(_) = dir.lookup(filename).await? {
bail!("{} already exists", filename);
}
let handle = dir.create_child_file(&mut transaction, &filename).await?;
transaction.commit().await?;
let mut buf = handle.allocate_buffer(data.len());
buf.as_mut_slice().copy_from_slice(&data);
handle.write_or_append(Some(0), buf.as_ref()).await?;
handle.flush().await
}
/// Create a directory.
pub async fn mkdir(
fs: &OpenFxFilesystem,
vol: &Arc<ObjectStore>,
path: &Path,
) -> Result<(), Error> {
let dir = walk_dir(vol, path.parent().unwrap()).await?;
let filename = path.file_name().unwrap().to_str().unwrap();
let mut transaction = (*fs).clone().new_transaction(&[], Options::default()).await?;
if let Some(_) = dir.lookup(filename).await? {
bail!("{} already exists", filename);
}
dir.create_child_dir(&mut transaction, &filename).await?;
transaction.commit().await?;
Ok(())
}