blob: 1201c5b7bb855ca1b1c7e1dcf5b418d5f1c75571 [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.
//! Library for filesystem management in rust.
//!
//! This library is analogous to the fs-management library in zircon. It provides support for
//! formatting, mounting, unmounting, and fsck-ing. It is implemented in a similar way to the C++
//! version - it uses the blobfs command line tool present in the base image. In order to use this
//! library inside of a sandbox, the following must be added to the relevant component manifest
//! file -
//!
//! ```
//! "sandbox": {
//! "services": [
//! "fuchsia.process.Launcher",
//! "fuchsia.tracing.provider.Registry"
//! ]
//! }
//! ```
//!
//! and the projects BUILD.gn file must contain
//!
//! ```
//! package("foo") {
//! deps = [
//! "//src/storage/bin/blobfs",
//! "//src/storage/bin/minfs",
//! ...
//! ]
//! binaries = [
//! { name = "blobfs" },
//! { name = "minfs" },
//! ...
//! ]
//! ...
//! }
//! ```
//!
//! for components v1. For components v2, add `/svc/fuchsia.process.Launcher` to `use` and add the
//! binaries as dependencies to your component.
//!
//! This library currently doesn't work outside of a component (the filesystem utility binary paths
//! are hard-coded strings).
pub mod asynchronous;
mod error;
use {
anyhow::{bail, format_err, Context as _, Error},
cstr::cstr,
fdio::{service_connect, service_connect_at, spawn_etc, Namespace, SpawnAction, SpawnOptions},
fidl::endpoints::DiscoverableProtocolMarker,
fidl_fuchsia_fs::AdminSynchronousProxy,
fidl_fuchsia_io as fio,
fuchsia_runtime::{HandleInfo, HandleType},
fuchsia_zircon::{self as zx, AsHandleRef, Task},
fuchsia_zircon_status as zx_status,
std::{ffi::CStr, sync::Arc},
};
// Re-export errors as public.
pub use error::{
BindError, CommandError, KillError, LaunchProcessError, QueryError, ServeError, ShutdownError,
};
/// Constants for fuchsia.io/FilesystemInfo.fs_type
/// Keep in sync with VFS_TYPE_* types in //zircon/system/public/zircon/device/vfs.h
pub mod vfs_type {
pub const BLOBFS: u32 = 0x9e694d21;
pub const FATFS: u32 = 0xce694d21;
pub const MINFS: u32 = 0x6e694d21;
pub const MEMFS: u32 = 0x3e694d21;
pub const FACTORYFS: u32 = 0x1e694d21;
pub const FXFS: u32 = 0x73667866;
pub const F2FS: u32 = 0xfe694d21;
}
/// Stores state of the mounted filesystem instance
struct FSInstance {
process: zx::Process,
mount_point: String,
export_root: zx::Channel,
}
impl FSInstance {
/// Mount the filesystem partition that exists on the provided block device, allowing it to
/// receive requests on the root channel. In order to be mounted in the traditional sense, the
/// client side of the provided root channel needs to be bound to a path in a namespace
/// somewhere.
fn mount(
block_device: zx::Channel,
args: Vec<&CStr>,
mount_point: &str,
crypt_client: Option<zx::Channel>,
) -> Result<Self, Error> {
let (export_root, server_end) = fidl::endpoints::create_endpoints::<fio::NodeMarker>()?;
let export_root = fio::DirectorySynchronousProxy::new(export_root.into_channel());
let mut actions = vec![
// export root handle is passed in as a PA_DIRECTORY_REQUEST handle at argument 0
SpawnAction::add_handle(
HandleInfo::new(HandleType::DirectoryRequest, 0),
server_end.into(),
),
// device handle is passed in as a PA_USER0 handle at argument 1
SpawnAction::add_handle(HandleInfo::new(HandleType::User0, 1), block_device.into()),
];
if let Some(crypt_client) = crypt_client {
actions.push(SpawnAction::add_handle(
HandleInfo::new(HandleType::User0, 2),
crypt_client.into(),
));
}
let process = launch_process(&args, actions)?;
// Wait until the filesystem is ready to take incoming requests. We want
// mount errors to show before we bind to the namespace.
let (root_dir, server_end) = fidl::endpoints::create_endpoints::<fio::NodeMarker>()?;
export_root.open(
fio::OpenFlags::RIGHT_READABLE
| fio::OpenFlags::POSIX_EXECUTABLE
| fio::OpenFlags::POSIX_WRITABLE,
0,
"root",
server_end.into(),
)?;
let root_dir = fio::DirectorySynchronousProxy::new(root_dir.into_channel());
let _: fio::NodeInfo = root_dir.describe(zx::Time::INFINITE).context("failed to mount")?;
let namespace = Namespace::installed().context("failed to get installed namespace")?;
namespace
.bind(mount_point, root_dir.into_channel())
.context("failed to bind client channel into default namespace")?;
Ok(Self {
process,
mount_point: mount_point.to_string(),
export_root: export_root.into_channel(),
})
}
/// Unmount the filesystem partition. The partition must already be mounted.
fn unmount(self) -> Result<(), Error> {
let (client_chan, server_chan) = zx::Channel::create()?;
service_connect_at(
&self.export_root,
fidl_fuchsia_fs::AdminMarker::PROTOCOL_NAME,
server_chan,
)?;
let admin_proxy = AdminSynchronousProxy::new(client_chan);
admin_proxy.shutdown(zx::Time::INFINITE)?;
let namespace = Namespace::installed().context("failed to get installed namespace")?;
namespace
.unbind(&self.mount_point)
.context("failed to unbind filesystem from default namespace")
}
/// Get `FileSystemInfo` struct from which one can find out things like
/// free space, used space, block size, etc.
fn query_filesystem(&self) -> Result<Box<fio::FilesystemInfo>, Error> {
let (client_chan, server_chan) = zx::Channel::create()?;
service_connect(&self.mount_point, server_chan)
.context("failed to connect to filesystem")?;
let proxy = fio::DirectorySynchronousProxy::new(client_chan);
let (status, result) = proxy
.query_filesystem(zx::Time::INFINITE)
.context("failed to query filesystem info")?;
zx_status::Status::ok(status).context("failed to query filesystem info")?;
result.ok_or(format_err!("querying filesystem info got empty result"))
}
/// Terminate the filesystem process and force unmount the mount point
fn kill(self) -> Result<(), Error> {
let namespace = Namespace::installed().context("failed to get installed namespace")?;
namespace
.unbind(&self.mount_point)
.context("failed to unbind filesystem from default namespace")?;
self.process.kill().context("Could not kill filesystem process")
}
}
fn launch_process(
args: &[&CStr],
mut actions: Vec<SpawnAction<'_>>,
) -> Result<zx::Process, LaunchProcessError> {
match spawn_etc(
&zx::Handle::invalid().into(),
SpawnOptions::CLONE_ALL,
args[0],
args,
None,
&mut actions,
) {
Ok(process) => Ok(process),
Err((status, message)) => Err(LaunchProcessError {
args: args.iter().map(|&a| a.to_owned()).collect(),
status,
message,
}),
}
}
fn run_command_and_wait_for_clean_exit(
args: Vec<&CStr>,
block_device: zx::Channel,
crypt_client: Option<zx::Channel>,
) -> Result<(), Error> {
let mut actions = vec![
// device handle is passed in as a PA_USER0 handle at argument 1
SpawnAction::add_handle(HandleInfo::new(HandleType::User0, 1), block_device.into()),
];
if let Some(crypt_client) = crypt_client {
actions.push(SpawnAction::add_handle(
HandleInfo::new(HandleType::User0, 2),
crypt_client.into(),
));
}
let process = launch_process(&args, actions)?;
let _signals = process
.wait_handle(zx::Signals::PROCESS_TERMINATED, zx::Time::INFINITE)
.context(format!("failed to wait for process to complete"))?;
let info = process.info().context("failed to get process info")?;
if !zx::ProcessInfoFlags::from_bits(info.flags).unwrap().contains(zx::ProcessInfoFlags::EXITED)
|| info.return_code != 0
{
bail!("process returned non-zero exit code ({})", info.return_code);
}
Ok(())
}
/// Describes the configuration for a particular native filesystem.
pub trait FSConfig {
/// If the filesystem runs as a component, Returns the component name in which case the
/// binary_path, and the _args methods are not relevant.
fn component_name(&self) -> Option<&str> {
None
}
/// Path to the filesystem binary
fn binary_path(&self) -> &CStr;
/// Arguments passed to the binary for all subcommands
fn generic_args(&self) -> Vec<&CStr> {
vec![]
}
/// Arguments passed to the binary for formatting
fn format_args(&self) -> Vec<&CStr> {
vec![]
}
/// Arguments passed to the binary for mounting
fn mount_args(&self) -> Vec<&CStr> {
vec![]
}
/// Returns a handle for the crypt service (if any).
fn crypt_client(&self) -> Option<zx::Channel> {
// By default, filesystems don't need a crypt service.
None
}
}
/// Manages a block device for filesystem operations
pub struct Filesystem<FSC: FSConfig> {
device: fio::NodeSynchronousProxy,
config: FSC,
instance: Option<FSInstance>,
}
impl<FSC: FSConfig> Filesystem<FSC> {
/// Manage a filesystem on a device at the given path. The device is not formatted, mounted, or
/// modified at this point.
pub fn from_path(device_path: &str, config: FSC) -> Result<Self, Error> {
let (client_end, server_end) = zx::Channel::create()?;
service_connect(device_path, server_end).context("could not connect to block device")?;
Self::from_channel(client_end, config)
}
/// Manage a filesystem on a device at the given channel. The device is not formatted, mounted,
/// or modified at this point.
pub fn from_channel(client_end: zx::Channel, config: FSC) -> Result<Self, Error> {
let device = fio::NodeSynchronousProxy::new(client_end);
Ok(Self { device, config, instance: None })
}
/// Returns a channel to the block device.
fn get_channel(&mut self) -> Result<zx::Channel, Error> {
let (channel, server) = zx::Channel::create()?;
let () = self
.device
.clone(fio::OpenFlags::CLONE_SAME_RIGHTS, fidl::endpoints::ServerEnd::new(server))?;
Ok(channel)
}
/// Mount the provided block device and bind it to the provided mount_point in the default
/// namespace. The filesystem can't already be mounted, and the mount will fail if the provided
/// mount path doesn't already exist. The path is relative to the root of the default namespace,
/// and can't contain any '.' or '..' entries.
pub fn mount(&mut self, mount_point: &str) -> Result<(), Error> {
if self.instance.is_some() {
bail!("cannot mount. filesystem is already mounted");
}
if self.config.component_name().is_some() {
bail!("Not supported");
}
let block_device = self.get_channel()?;
let mut args = vec![self.config.binary_path()];
args.append(&mut self.config.generic_args());
args.push(cstr!("mount"));
args.append(&mut self.config.mount_args());
self.instance =
Some(FSInstance::mount(block_device, args, mount_point, self.config.crypt_client())?);
Ok(())
}
/// Format the associated device with a fresh filesystem. It must not be mounted.
pub fn format(&mut self) -> Result<(), Error> {
if self.instance.is_some() {
bail!("cannot format! filesystem is mounted");
}
if self.config.component_name().is_some() {
bail!("Not supported");
}
let block_device = self.get_channel()?;
let mut args = vec![self.config.binary_path()];
args.append(&mut self.config.generic_args());
args.push(cstr!("mkfs"));
args.append(&mut self.config.format_args());
run_command_and_wait_for_clean_exit(args, block_device, self.config.crypt_client())
.context("failed to format device")
}
/// Run fsck on the filesystem partition. Returns Ok(()) if fsck succeeds, or the associated
/// error if it doesn't. Will fail if run on a mounted partition.
pub fn fsck(&mut self) -> Result<(), Error> {
if self.instance.is_some() {
bail!("cannot fsck! filesystem is mounted");
}
if self.config.component_name().is_some() {
bail!("Not supported");
}
let block_device = self.get_channel()?;
let mut args = vec![self.config.binary_path()];
args.append(&mut self.config.generic_args());
args.push(cstr!("fsck"));
run_command_and_wait_for_clean_exit(args, block_device, self.config.crypt_client())
.context("failed to fsck device")
}
/// Unmount the filesystem partition. The partition must already be mounted.
pub fn unmount(&mut self) -> Result<(), Error> {
if let Some(instance) = self.instance.take() {
instance.unmount()
} else {
Err(format_err!("cannot unmount. filesystem is not mounted"))
}
}
/// Get `FileSystemInfo` struct from which one can find out things like
/// free space, used space, block size, etc.
pub fn query_filesystem(&self) -> Result<Box<fio::FilesystemInfo>, Error> {
if let Some(instance) = &self.instance {
instance.query_filesystem()
} else {
Err(format_err!("cannot query filesystem. filesystem is not mounted"))
}
}
/// Terminate the filesystem process and force unmount the mount point
pub fn kill(&mut self) -> Result<(), Error> {
if let Some(instance) = self.instance.take() {
instance.kill()
} else {
Err(format_err!("cannot kill. filesystem is not mounted"))
}
}
}
impl<FSC: FSConfig> Drop for Filesystem<FSC> {
fn drop(&mut self) {
if self.instance.is_some() {
// Unmount if possible.
let _ = self.unmount();
}
}
}
///
/// FILESYSTEMS
///
/// Layout of blobs in blobfs
#[derive(Clone)]
pub enum BlobLayout {
/// Merkle tree is stored in a separate block. This is deprecated and used only on Astro
/// devices (it takes more space).
DeprecatedPadded,
/// Merkle tree is appended to the last block of data
Compact,
}
/// Compression used for blobs in blobfs
#[derive(Clone)]
pub enum BlobCompression {
ZSTD,
ZSTDSeekable,
ZSTDChunked,
Uncompressed,
}
/// Eviction policy used for blobs in blobfs
#[derive(Clone)]
pub enum BlobEvictionPolicy {
NeverEvict,
EvictImmediately,
}
/// Blobfs Filesystem Configuration
/// If fields are None or false, they will not be set in arguments.
#[derive(Clone, Default)]
pub struct Blobfs {
pub verbose: bool,
pub readonly: bool,
pub blob_deprecated_padded_format: bool,
pub blob_compression: Option<BlobCompression>,
pub blob_eviction_policy: Option<BlobEvictionPolicy>,
}
impl Blobfs {
/// Manages a block device at a given path using
/// the default configuration.
pub fn new(path: &str) -> Result<Filesystem<Self>, Error> {
Filesystem::from_path(path, Self::default())
}
/// Manages a block device at a given channel using
/// the default configuration.
pub fn from_channel(channel: zx::Channel) -> Result<Filesystem<Self>, Error> {
Filesystem::from_channel(channel, Self::default())
}
}
impl FSConfig for Blobfs {
fn binary_path(&self) -> &CStr {
cstr!("/pkg/bin/blobfs")
}
fn generic_args(&self) -> Vec<&CStr> {
let mut args = vec![];
if self.verbose {
args.push(cstr!("--verbose"));
}
args
}
fn format_args(&self) -> Vec<&CStr> {
let mut args = vec![];
if self.blob_deprecated_padded_format {
args.push(cstr!("--deprecated_padded_format"));
}
args
}
fn mount_args(&self) -> Vec<&CStr> {
let mut args = vec![];
if self.readonly {
args.push(cstr!("--readonly"));
}
if let Some(compression) = &self.blob_compression {
args.push(cstr!("--compression"));
args.push(match compression {
BlobCompression::ZSTD => cstr!("ZSTD"),
BlobCompression::ZSTDSeekable => cstr!("ZSTD_SEEKABLE"),
BlobCompression::ZSTDChunked => cstr!("ZSTD_CHUNKED"),
BlobCompression::Uncompressed => cstr!("UNCOMPRESSED"),
});
}
if let Some(eviction_policy) = &self.blob_eviction_policy {
args.push(cstr!("--eviction_policy"));
args.push(match eviction_policy {
BlobEvictionPolicy::NeverEvict => cstr!("NEVER_EVICT"),
BlobEvictionPolicy::EvictImmediately => cstr!("EVICT_IMMEDIATELY"),
})
}
args
}
}
/// Minfs Filesystem Configuration
/// If fields are None or false, they will not be set in arguments.
#[derive(Clone, Default)]
pub struct Minfs {
// TODO(xbhatnag): Add support for fvm_data_slices
pub verbose: bool,
pub readonly: bool,
pub fsck_after_every_transaction: bool,
}
impl Minfs {
/// Manages a block device at a given path using
/// the default configuration.
pub fn new(path: &str) -> Result<Filesystem<Self>, Error> {
Filesystem::from_path(path, Self::default())
}
/// Manages a block device at a given channel using
/// the default configuration.
pub fn from_channel(channel: zx::Channel) -> Result<Filesystem<Self>, Error> {
Filesystem::from_channel(channel, Self::default())
}
}
impl FSConfig for Minfs {
fn binary_path(&self) -> &CStr {
cstr!("/pkg/bin/minfs")
}
fn generic_args(&self) -> Vec<&CStr> {
let mut args = vec![];
if self.verbose {
args.push(cstr!("--verbose"));
}
args
}
fn mount_args(&self) -> Vec<&CStr> {
let mut args = vec![];
if self.readonly {
args.push(cstr!("--readonly"));
}
if self.fsck_after_every_transaction {
args.push(cstr!("--fsck_after_every_transaction"));
}
args
}
}
type CryptClientFn = Arc<dyn Fn() -> zx::Channel + Send + Sync>;
/// Fxfs Filesystem Configuration
/// If fields are None or false, they will not be set in arguments.
#[derive(Clone)]
pub struct Fxfs {
pub verbose: bool,
pub readonly: bool,
pub crypt_client_fn: CryptClientFn,
}
impl Fxfs {
pub fn with_crypt_client(crypt_client_fn: CryptClientFn) -> Self {
Fxfs { verbose: false, readonly: false, crypt_client_fn }
}
/// Manages a block device at a given path using
/// the default configuration.
pub fn new(path: &str, crypt_client_fn: CryptClientFn) -> Result<Filesystem<Self>, Error> {
Filesystem::from_path(path, Self::with_crypt_client(crypt_client_fn))
}
/// Manages a block device at a given channel using
/// the default configuration.
pub fn from_channel(
channel: zx::Channel,
crypt_client_fn: CryptClientFn,
) -> Result<Filesystem<Self>, Error> {
Filesystem::from_channel(channel, Self::with_crypt_client(crypt_client_fn))
}
}
impl FSConfig for Fxfs {
fn component_name(&self) -> Option<&str> {
Some("fxfs")
}
fn binary_path(&self) -> &CStr {
cstr!("")
}
fn crypt_client(&self) -> Option<zx::Channel> {
Some((self.crypt_client_fn)())
}
}
/// Factoryfs Filesystem Configuration
/// If fields are None or false, they will not be set in arguments.
#[derive(Clone, Default)]
pub struct Factoryfs {
pub verbose: bool,
}
impl Factoryfs {
/// Manages a block device at a given path using
/// the default configuration.
pub fn new(path: &str) -> Result<Filesystem<Self>, Error> {
Filesystem::from_path(path, Self::default())
}
/// Manages a block device at a given channel using
/// the default configuration.
pub fn from_channel(channel: zx::Channel) -> Result<Filesystem<Self>, Error> {
Filesystem::from_channel(channel, Self::default())
}
}
impl FSConfig for Factoryfs {
fn binary_path(&self) -> &CStr {
cstr!("/pkg/bin/factoryfs")
}
fn generic_args(&self) -> Vec<&CStr> {
let mut args = vec![];
if self.verbose {
args.push(cstr!("--verbose"));
}
args
}
}
#[cfg(test)]
mod tests {
use {
super::{BlobCompression, BlobEvictionPolicy, Blobfs, Factoryfs, Filesystem, Minfs},
fuchsia_zircon::HandleBased,
ramdevice_client::RamdiskClient,
std::io::{Read, Seek, Write},
};
fn ramdisk(block_size: u64) -> RamdiskClient {
ramdevice_client::wait_for_device(
"/dev/sys/platform/00:00:2d/ramctl",
std::time::Duration::from_secs(60),
)
.unwrap();
RamdiskClient::create(block_size, 1 << 16).unwrap()
}
fn blobfs(ramdisk: &RamdiskClient) -> Filesystem<Blobfs> {
let device = ramdisk.open().unwrap();
Blobfs::from_channel(device).unwrap()
}
#[test]
fn blobfs_custom_config() {
let block_size = 512;
let mount_point = "/test-fs-root";
let ramdisk = ramdisk(block_size);
let device = ramdisk.open().unwrap();
let config = Blobfs {
verbose: true,
readonly: true,
blob_deprecated_padded_format: false,
blob_compression: Some(BlobCompression::Uncompressed),
blob_eviction_policy: Some(BlobEvictionPolicy::EvictImmediately),
};
let mut blobfs = Filesystem::from_channel(device, config).unwrap();
blobfs.format().expect("failed to format blobfs");
blobfs.fsck().expect("failed to fsck blobfs");
blobfs.mount(mount_point).expect("failed to mount blobfs");
ramdisk.destroy().expect("failed to destroy ramdisk");
}
#[test]
fn blobfs_format_fsck_success() {
let block_size = 512;
let ramdisk = ramdisk(block_size);
let mut blobfs = blobfs(&ramdisk);
blobfs.format().expect("failed to format blobfs");
blobfs.fsck().expect("failed to fsck blobfs");
ramdisk.destroy().expect("failed to destroy ramdisk");
}
#[test]
fn blobfs_format_fsck_error() {
let block_size = 512;
let ramdisk = ramdisk(block_size);
let mut blobfs = blobfs(&ramdisk);
blobfs.format().expect("failed to format blobfs");
// force fsck to fail by stomping all over one of blobfs's metadata blocks after formatting
// TODO(fxbug.dev/35860): corrupt something other than the superblock
let device_channel = ramdisk.open().expect("failed to get channel to device");
let mut file = fdio::create_fd::<std::fs::File>(device_channel.into_handle())
.expect("failed to convert to file descriptor");
let mut bytes: Vec<u8> = std::iter::repeat(0xff).take(block_size as usize).collect();
file.write_all(&mut bytes).expect("failed to write to device");
blobfs.fsck().expect_err("fsck succeeded when it shouldn't have");
ramdisk.destroy().expect("failed to destroy ramdisk");
}
#[test]
fn blobfs_format_mount_write_query_remount_read_unmount() {
let block_size = 512;
let mount_point = "/test-fs-root";
let ramdisk = ramdisk(block_size);
let mut blobfs = blobfs(&ramdisk);
blobfs.format().expect("failed to format blobfs");
blobfs.mount(mount_point).expect("failed to mount blobfs the first time");
// snapshot of FilesystemInfo
let fs_info1 =
blobfs.query_filesystem().expect("failed to query filesystem info after first mount");
// pre-generated merkle test fixture data
let merkle = "be901a14ec42ee0a8ee220eb119294cdd40d26d573139ee3d51e4430e7d08c28";
let content = String::from("test content").into_bytes();
let path = format!("{}/{}", mount_point, merkle);
{
let mut test_file = std::fs::File::create(&path).expect("failed to create test file");
test_file.set_len(content.len() as u64).expect("failed to truncate file");
test_file.write_all(&content).expect("failed to write to test file");
}
// check against the snapshot FilesystemInfo
let fs_info2 =
blobfs.query_filesystem().expect("failed to query filesystem info after write");
assert_eq!(
fs_info2.used_bytes - fs_info1.used_bytes,
fs_info2.block_size as u64 // assuming content < 8K
);
blobfs.unmount().expect("failed to unmount blobfs the first time");
blobfs
.query_filesystem()
.expect_err("filesystem query on an unmounted filesystem didn't fail");
blobfs.mount(mount_point).expect("failed to mount blobfs the second time");
{
let mut test_file = std::fs::File::open(&path).expect("failed to open test file");
let mut read_content = Vec::new();
test_file.read_to_end(&mut read_content).expect("failed to read from test file");
assert_eq!(content, read_content);
}
// once more check against the snapshot FilesystemInfo
let fs_info3 =
blobfs.query_filesystem().expect("failed to query filesystem info after read");
assert_eq!(
fs_info3.used_bytes - fs_info1.used_bytes,
fs_info3.block_size as u64 // assuming content < 8K
);
blobfs.unmount().expect("failed to unmount blobfs the second time");
ramdisk.destroy().expect("failed to destroy ramdisk");
}
fn minfs(ramdisk: &RamdiskClient) -> Filesystem<Minfs> {
let device = ramdisk.open().unwrap();
Minfs::from_channel(device).unwrap()
}
#[test]
fn minfs_custom_config() {
let block_size = 512;
let mount_point = "/test-fs-root";
let ramdisk = ramdisk(block_size);
let device = ramdisk.open().unwrap();
let config = Minfs { verbose: true, readonly: true, fsck_after_every_transaction: true };
let mut minfs = Filesystem::from_channel(device, config).unwrap();
minfs.format().expect("failed to format minfs");
minfs.fsck().expect("failed to fsck minfs");
minfs.mount(mount_point).expect("failed to mount minfs");
ramdisk.destroy().expect("failed to destroy ramdisk");
}
#[test]
fn minfs_format_fsck_success() {
let block_size = 8192;
let ramdisk = ramdisk(block_size);
let mut minfs = minfs(&ramdisk);
minfs.format().expect("failed to format minfs");
minfs.fsck().expect("failed to fsck minfs");
ramdisk.destroy().expect("failed to destroy ramdisk");
}
#[test]
fn minfs_format_fsck_error() {
let block_size = 8192;
let ramdisk = ramdisk(block_size);
let mut minfs = minfs(&ramdisk);
minfs.format().expect("failed to format minfs");
// force fsck to fail by stomping all over one of minfs's metadata blocks after formatting
let device_channel = ramdisk.open().expect("failed to get channel to device");
let mut file = fdio::create_fd::<std::fs::File>(device_channel.into_handle())
.expect("failed to convert to file descriptor");
// when minfs isn't on an fvm, the location for it's bitmap offset is the 8th block.
// TODO(fxbug.dev/35861): parse the superblock for this offset and the block size.
let bitmap_block_offset = 8;
let bitmap_offset = block_size * bitmap_block_offset;
let mut stomping_bytes: Vec<u8> =
std::iter::repeat(0xff).take(block_size as usize).collect();
let actual_offset =
file.seek(std::io::SeekFrom::Start(bitmap_offset)).expect("failed to seek to bitmap");
assert_eq!(actual_offset, bitmap_offset);
file.write_all(&mut stomping_bytes).expect("failed to write to device");
minfs.fsck().expect_err("fsck succeeded when it shouldn't have");
ramdisk.destroy().expect("failed to destroy ramdisk");
}
#[test]
fn minfs_format_mount_write_query_remount_read_unmount() {
let block_size = 8192;
let mount_point = "/test-fs-root";
let ramdisk = ramdisk(block_size);
let mut minfs = minfs(&ramdisk);
minfs.format().expect("failed to format minfs");
minfs.mount(mount_point).expect("failed to mount minfs the first time");
// snapshot of FilesystemInfo
let fs_info1 =
minfs.query_filesystem().expect("failed to query filesystem info after first mount");
let filename = "test_file";
let content = String::from("test content").into_bytes();
let path = format!("{}/{}", mount_point, filename);
{
let mut test_file = std::fs::File::create(&path).expect("failed to create test file");
test_file.write_all(&content).expect("failed to write to test file");
}
// check against the snapshot FilesystemInfo
let fs_info2 =
minfs.query_filesystem().expect("failed to query filesystem info after write");
assert_eq!(
fs_info2.used_bytes - fs_info1.used_bytes,
fs_info2.block_size as u64 // assuming content < 8K
);
minfs.unmount().expect("failed to unmount minfs the first time");
minfs
.query_filesystem()
.expect_err("filesystem query on an unmounted filesystem didn't fail");
minfs.mount(mount_point).expect("failed to mount minfs the second time");
{
let mut test_file = std::fs::File::open(&path).expect("failed to open test file");
let mut read_content = Vec::new();
test_file.read_to_end(&mut read_content).expect("failed to read from test file");
assert_eq!(content, read_content);
}
// once more check against the snapshot FilesystemInfo
let fs_info3 =
minfs.query_filesystem().expect("failed to query filesystem info after read");
assert_eq!(
fs_info3.used_bytes - fs_info1.used_bytes,
fs_info3.block_size as u64 // assuming content < 8K
);
minfs.unmount().expect("failed to unmount minfs the second time");
ramdisk.destroy().expect("failed to destroy ramdisk");
}
fn factoryfs(ramdisk: &RamdiskClient) -> Filesystem<Factoryfs> {
let device = ramdisk.open().unwrap();
Factoryfs::from_channel(device).unwrap()
}
#[test]
fn factoryfs_custom_config() {
let block_size = 512;
let mount_point = "/test-fs-root";
let ramdisk = ramdisk(block_size);
let device = ramdisk.open().unwrap();
let config = Factoryfs { verbose: true };
let mut factoryfs = Filesystem::from_channel(device, config).unwrap();
factoryfs.format().expect("failed to format factoryfs");
factoryfs.fsck().expect("failed to fsck factoryfs");
factoryfs.mount(mount_point).expect("failed to mount factoryfs");
ramdisk.destroy().expect("failed to destroy ramdisk");
}
#[test]
fn factoryfs_format_fsck_success() {
let block_size = 512;
let ramdisk = ramdisk(block_size);
let mut factoryfs = factoryfs(&ramdisk);
factoryfs.format().expect("failed to format factoryfs");
factoryfs.fsck().expect("failed to fsck factoryfs");
ramdisk.destroy().expect("failed to destroy ramdisk");
}
#[test]
fn factoryfs_format_mount_unmount() {
let block_size = 512;
let mount_point = "/test-fs-root";
let ramdisk = ramdisk(block_size);
let mut factoryfs = factoryfs(&ramdisk);
factoryfs.format().expect("failed to format factoryfs");
factoryfs.mount(mount_point).expect("failed to mount factoryfs");
factoryfs.unmount().expect("failed to unmount factoryfs");
ramdisk.destroy().expect("failed to destroy ramdisk");
}
}