Rust now integrated into bcachefs binary

Rust is now required for building the bcachefs tool, and rust code is
now fully integrated with the C codebase - meaning it is possible to
call back and forth.

The mount helper is now a subcommand, 'mount.bcachefs' is now a small
shell wrapper that invokes 'bcachefs mount'.

This will make it easier to start rewriting other subcommands in rust,
and eventually the whole command line interface.

Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
This commit is contained in:
Kent Overstreet 2023-01-03 22:31:36 -05:00
parent da6a356895
commit 28f703cc25
18 changed files with 151 additions and 173 deletions

4
.gitignore vendored
View File

@ -6,8 +6,6 @@ bcachefs.5
*.so
*.d
*.a
/rust-src/mount/result
/rust-src/bch_bindgen/result
tags
TAGS
cscope*
@ -21,6 +19,4 @@ tests/__pycache__/
!.travis.yml
!.editorconfig
mount/target
mount.bcachefs
bcachefs-principles-of-operation.*

View File

@ -90,10 +90,11 @@ else
endif
.PHONY: all
all: bcachefs lib
all: bcachefs
.PHONY: lib
lib: libbcachefs.so
.PHONY: debug
debug: CFLAGS+=-Werror -DCONFIG_BCACHEFS_DEBUG=y -DCONFIG_VALGRIND=y
debug: bcachefs
.PHONY: tests
tests: tests/test_helper
@ -123,30 +124,17 @@ OBJS=$(SRCS:.c=.o)
@echo " [CC] $@"
$(Q)$(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
bcachefs: $(filter-out ./tests/%.o, $(OBJS))
bcachefs: libbcachefs.a rust-src/target/release/libbcachefs_rust.a
@echo " [LD] $@"
$(Q)$(CC) $(LDFLAGS) $+ $(LOADLIBES) $(LDLIBS) -o $@
$(Q)$(CC) $(LDFLAGS) -Wl,--whole-archive $+ $(LOADLIBES) -Wl,--no-whole-archive $(LDLIBS) -o $@
libbcachefs.a: $(filter-out ./tests/%.o, $(OBJS))
@echo " [AR] $@"
$(Q)ar -rc $@ $+
RUST_SRCS=$(shell find rust-src/ -type f -iname '*.rs')
MOUNT_SRCS=$(filter %mount, $(RUST_SRCS))
debug: CFLAGS+=-Werror -DCONFIG_BCACHEFS_DEBUG=y -DCONFIG_VALGRIND=y
debug: bcachefs
MOUNT_OBJ=$(filter-out ./bcachefs.o ./tests/%.o ./cmd_%.o , $(OBJS))
libbcachefs.so: LDFLAGS+=-shared
libbcachefs.so: $(MOUNT_OBJ)
@echo " [CC] $@"
$(Q)$(CC) $(LDFLAGS) $+ -o $@ $(LDLIBS)
MOUNT_TOML=rust-src/mount/Cargo.toml
mount.bcachefs: lib $(MOUNT_SRCS)
LIBBCACHEFS_LIB=$(CURDIR) \
LIBBCACHEFS_INCLUDE=$(CURDIR) \
$(CARGO_BUILD) --manifest-path $(MOUNT_TOML)
ln -f rust-src/mount/target/$(CARGO_PROFILE)/bcachefs-mount $@
rust-src/target/release/libbcachefs_rust.a: libbcachefs.a $(RUST_SRCS)
$(CARGO_BUILD) --manifest-path rust-src/Cargo.toml
tests/test_helper: $(filter ./tests/%.o, $(OBJS))
@echo " [LD] $@"
@ -166,15 +154,14 @@ cmd_version.o : .version
.PHONY: install
install: INITRAMFS_HOOK=$(INITRAMFS_DIR)/hooks/bcachefs
install: INITRAMFS_SCRIPT=$(INITRAMFS_DIR)/scripts/local-premount/bcachefs
install: bcachefs lib
install: bcachefs
$(INSTALL) -m0755 -D bcachefs -t $(DESTDIR)$(ROOT_SBINDIR)
$(INSTALL) -m0755 fsck.bcachefs $(DESTDIR)$(ROOT_SBINDIR)
$(INSTALL) -m0755 mkfs.bcachefs $(DESTDIR)$(ROOT_SBINDIR)
$(INSTALL) -m0755 mount.bcachefs $(DESTDIR)$(ROOT_SBINDIR)
$(INSTALL) -m0644 -D bcachefs.8 -t $(DESTDIR)$(PREFIX)/share/man/man8/
$(INSTALL) -m0755 -D initramfs/script $(DESTDIR)$(INITRAMFS_SCRIPT)
$(INSTALL) -m0755 -D initramfs/hook $(DESTDIR)$(INITRAMFS_HOOK)
$(INSTALL) -m0755 -D mount.bcachefs.sh $(DESTDIR)$(ROOT_SBINDIR)
$(INSTALL) -m0755 -D libbcachefs.so -t $(DESTDIR)$(PREFIX)/lib/
sed -i '/^# Note: make install replaces/,$$d' $(DESTDIR)$(INITRAMFS_HOOK)
echo "copy_exec $(ROOT_SBINDIR)/bcachefs /sbin/bcachefs" >> $(DESTDIR)$(INITRAMFS_HOOK)
@ -182,7 +169,7 @@ install: bcachefs lib
.PHONY: clean
clean:
@echo "Cleaning all"
$(Q)$(RM) bcachefs mount.bcachefs libbcachefs.so libbcachefs_mount.a tests/test_helper .version *.tar.xz $(OBJS) $(DEPS) $(DOCGENERATED)
$(Q)$(RM) bcachefs libbcachefs.a tests/test_helper .version *.tar.xz $(OBJS) $(DEPS) $(DOCGENERATED)
$(Q)$(RM) -rf rust-src/*/target
.PHONY: deb

View File

@ -177,8 +177,21 @@ int main(int argc, char *argv[])
setvbuf(stdout, NULL, _IOLBF, 0);
char *cmd = pop_cmd(&argc, argv);
if (argc < 1) {
if (argc < 2) {
puts("missing command\n");
goto usage;
}
/* Rust commands first - rust can't handle us mutating argv */
char *cmd = argv[1];
if (!strcmp(cmd, "mount")) {
cmd_mount();
return 0;
}
cmd = pop_cmd(&argc, argv);
if (!cmd) {
puts("missing command\n");
goto usage;
}

1
cmds.h
View File

@ -61,5 +61,6 @@ int cmd_subvolume_delete(int argc, char *argv[]);
int cmd_subvolume_snapshot(int argc, char *argv[]);
int cmd_fusemount(int argc, char *argv[]);
void cmd_mount(void);
#endif /* _CMDS_H */

4
mount.bcachefs Executable file
View File

@ -0,0 +1,4 @@
#!/bin/sh
SDIR="$(readlink -f "$0")"
exec "${SDIR%/*}/bcachefs" mount "$@"

View File

@ -44,7 +44,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bcachefs-mount"
name = "bcachefs-rust"
version = "0.3.1"
dependencies = [
"anyhow",

View File

@ -1,10 +1,11 @@
[package]
name = "bcachefs-mount"
name = "bcachefs-rust"
version = "0.3.1"
authors = ["Yuxuan Shui <yshuiv7@gmail.com>", "Kayla Firestack <dev@kaylafire.me>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["staticlib"]
[dependencies]
atty = "0.2.14"
@ -23,5 +24,5 @@ parse-display = "0.1"
errno = "0.2"
either = "1.5"
rpassword = "4"
bch_bindgen = { path = "../bch_bindgen" }
bch_bindgen = { path = "bch_bindgen" }
byteorder = "1.3"

View File

@ -7,13 +7,8 @@ fn main() {
let top_dir: PathBuf = std::env::var_os("CARGO_MANIFEST_DIR")
.expect("ENV Var 'CARGO_MANIFEST_DIR' Expected")
.into();
let libbcachefs_inc_dir = std::env::var("LIBBCACHEFS_INCLUDE")
.unwrap_or_else(|_| top_dir.join("libbcachefs").display().to_string());
let libbcachefs_inc_dir = std::path::Path::new(&libbcachefs_inc_dir);
println!("{}", libbcachefs_inc_dir.display());
println!("cargo:rustc-link-lib=dylib=bcachefs");
println!("cargo:rustc-link-search={}", env!("LIBBCACHEFS_LIB"));
let libbcachefs_inc_dir = std::path::Path::new("../..");
let _libbcachefs_dir = top_dir.join("libbcachefs").join("libbcachefs");
let bindings = bindgen::builder()

View File

@ -1,53 +0,0 @@
use bcachefs_mount::Cli;
use bch_bindgen::{error, info};
use clap::Parser;
use colored::Colorize;
fn main() {
let opt = Cli::parse();
bch_bindgen::log::set_verbose_level(opt.verbose + bch_bindgen::log::ERROR);
colored::control::set_override(opt.colorize);
if let Err(e) = crate::main_inner(opt) {
error!("Fatal error: {}", e);
}
}
pub fn main_inner(opt: Cli) -> anyhow::Result<()> {
use bcachefs_mount::{filesystem, key};
unsafe {
libc::setvbuf(filesystem::stdout, std::ptr::null_mut(), libc::_IONBF, 0);
// libc::fflush(filesystem::stdout);
}
let fss = filesystem::probe_filesystems()?;
let fs = fss
.get(&opt.uuid)
.ok_or_else(|| anyhow::anyhow!("filesystem was not found"))?;
info!("found filesystem {}", fs);
if fs.encrypted() {
let key = opt
.key_location
.0
.ok_or_else(|| anyhow::anyhow!("no keyoption specified for locked filesystem"))?;
key::prepare_key(&fs, key)?;
}
let mountpoint = opt
.mountpoint
.ok_or_else(|| anyhow::anyhow!("mountpoint option was not specified"))?;
fs.mount(&mountpoint, &opt.options)?;
Ok(())
}
#[cfg(test)]
mod test {
// use insta::assert_debug_snapshot;
// #[test]
// fn snapshot_testing() {
// insta::assert_debug_snapshot!();
// }
}

View File

@ -1,66 +1,11 @@
use anyhow::anyhow;
use atty::Stream;
use bch_bindgen::{error, info};
use clap::Parser;
use colored::Colorize;
use atty::Stream;
use uuid::Uuid;
pub mod err {
pub enum GError {
Unknown {
message: std::borrow::Cow<'static, String>,
},
}
pub type GResult<T, E, OE> = ::core::result::Result<::core::result::Result<T, E>, OE>;
pub type Result<T, E> = GResult<T, E, GError>;
}
#[macro_export]
macro_rules! c_str {
($lit:expr) => {
unsafe {
std::ffi::CStr::from_ptr(concat!($lit, "\0").as_ptr() as *const std::os::raw::c_char)
.to_bytes_with_nul()
.as_ptr() as *const std::os::raw::c_char
}
};
}
#[derive(Debug)]
struct ErrnoError(errno::Errno);
impl std::fmt::Display for ErrnoError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
self.0.fmt(f)
}
}
impl std::error::Error for ErrnoError {}
#[derive(Clone, Debug)]
pub enum KeyLocation {
Fail,
Wait,
Ask,
}
#[derive(Clone, Debug)]
pub struct KeyLoc(pub Option<KeyLocation>);
impl std::ops::Deref for KeyLoc {
type Target = Option<KeyLocation>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::str::FromStr for KeyLoc {
type Err = anyhow::Error;
fn from_str(s: &str) -> anyhow::Result<Self> {
// use anyhow::anyhow;
match s {
"" => Ok(KeyLoc(None)),
"fail" => Ok(KeyLoc(Some(KeyLocation::Fail))),
"wait" => Ok(KeyLoc(Some(KeyLocation::Wait))),
"ask" => Ok(KeyLoc(Some(KeyLocation::Ask))),
_ => Err(anyhow!("invalid password option")),
}
}
}
use crate::filesystem;
use crate::key;
use crate::key::KeyLoc;
fn parse_fstab_uuid(uuid_raw: &str) -> Result<Uuid, uuid::Error> {
let mut uuid = String::from(uuid_raw);
@ -114,12 +59,41 @@ pub struct Cli {
pub verbose: u8,
}
pub mod filesystem;
pub mod key;
// pub fn mnt_in_use()
#[test]
fn verify_cli() {
use clap::CommandFactory;
Cli::command().debug_assert()
pub fn cmd_mount_inner(opt: Cli) -> anyhow::Result<()> {
unsafe {
libc::setvbuf(filesystem::stdout, std::ptr::null_mut(), libc::_IONBF, 0);
}
let fss = filesystem::probe_filesystems()?;
let fs = fss
.get(&opt.uuid)
.ok_or_else(|| anyhow::anyhow!("filesystem was not found"))?;
info!("found filesystem {}", fs);
if fs.encrypted() {
let key = opt
.key_location
.0
.ok_or_else(|| anyhow::anyhow!("no keyoption specified for locked filesystem"))?;
key::prepare_key(&fs, key)?;
}
let mountpoint = opt
.mountpoint
.ok_or_else(|| anyhow::anyhow!("mountpoint option was not specified"))?;
fs.mount(&mountpoint, &opt.options)?;
Ok(())
}
#[no_mangle]
pub extern "C" fn cmd_mount() {
let opt = Cli::parse();
bch_bindgen::log::set_verbose_level(opt.verbose + bch_bindgen::log::ERROR);
colored::control::set_override(opt.colorize);
if let Err(e) = cmd_mount_inner(opt) {
error!("Fatal error: {}", e);
}
}

View File

@ -1,5 +1,36 @@
use bch_bindgen::info;
use colored::Colorize;
use crate::c_str;
use anyhow::anyhow;
#[derive(Clone, Debug)]
pub enum KeyLocation {
Fail,
Wait,
Ask,
}
#[derive(Clone, Debug)]
pub struct KeyLoc(pub Option<KeyLocation>);
impl std::ops::Deref for KeyLoc {
type Target = Option<KeyLocation>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::str::FromStr for KeyLoc {
type Err = anyhow::Error;
fn from_str(s: &str) -> anyhow::Result<Self> {
match s {
"" => Ok(KeyLoc(None)),
"fail" => Ok(KeyLoc(Some(KeyLocation::Fail))),
"wait" => Ok(KeyLoc(Some(KeyLocation::Wait))),
"ask" => Ok(KeyLoc(Some(KeyLocation::Ask))),
_ => Err(anyhow!("invalid password option")),
}
}
}
fn check_for_key(key_name: &std::ffi::CStr) -> anyhow::Result<bool> {
use bch_bindgen::keyutils::{self, keyctl_search};
@ -31,7 +62,6 @@ fn wait_for_key(uuid: &uuid::Uuid) -> anyhow::Result<()> {
const BCH_KEY_MAGIC: &str = "bch**key";
use crate::filesystem::FileSystem;
fn ask_for_key(fs: &FileSystem) -> anyhow::Result<()> {
use anyhow::anyhow;
use bch_bindgen::bcachefs::{self, bch2_chacha_encrypt_key, bch_encrypted_key, bch_key};
use byteorder::{LittleEndian, ReadBytesExt};
use std::os::raw::c_char;
@ -84,14 +114,11 @@ fn ask_for_key(fs: &FileSystem) -> anyhow::Result<()> {
}
}
pub fn prepare_key(fs: &FileSystem, password: crate::KeyLocation) -> anyhow::Result<()> {
use crate::KeyLocation::*;
use anyhow::anyhow;
pub fn prepare_key(fs: &FileSystem, password: KeyLocation) -> anyhow::Result<()> {
info!("checking if key exists for filesystem {}", fs.uuid());
match password {
Fail => Err(anyhow!("no key available")),
Wait => Ok(wait_for_key(fs.uuid())?),
Ask => ask_for_key(fs),
KeyLocation::Fail => Err(anyhow!("no key available")),
KeyLocation::Wait => Ok(wait_for_key(fs.uuid())?),
KeyLocation::Ask => ask_for_key(fs),
}
}

33
rust-src/src/lib.rs Normal file
View File

@ -0,0 +1,33 @@
pub mod filesystem;
pub mod key;
pub mod cmd_mount;
pub mod err {
pub enum GError {
Unknown {
message: std::borrow::Cow<'static, String>,
},
}
pub type GResult<T, E, OE> = ::core::result::Result<::core::result::Result<T, E>, OE>;
pub type Result<T, E> = GResult<T, E, GError>;
}
#[macro_export]
macro_rules! c_str {
($lit:expr) => {
unsafe {
std::ffi::CStr::from_ptr(concat!($lit, "\0").as_ptr() as *const std::os::raw::c_char)
.to_bytes_with_nul()
.as_ptr() as *const std::os::raw::c_char
}
};
}
#[derive(Debug)]
struct ErrnoError(errno::Errno);
impl std::fmt::Display for ErrnoError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
self.0.fmt(f)
}
}
impl std::error::Error for ErrnoError {}