On Sun, Jul 14, 2024 at 02:02:25PM GMT, Thomas Bertschinger wrote: > This introduces a new command, "debug", that is used for directly > manipulating bkeys in the underlying btrees. > > It has a subcommand, "dump", which takes a btree and bpos and > prints the data from that bkey.
But we already have that functionality - the 'list' subcommand... Did you just want an easy way to print a single key? > > For example: > > $ bcachefs debug ~/test-img -c "dump inodes 0:4096:U32_MAX" > u64s 17 type inode_v3 0:4096:U32_MAX len 0 ver 0: mode=40755 > flags= (16300000) > journal_seq=9 > ... > > Signed-off-by: Thomas Bertschinger <[email protected]> > --- > c_src/bcachefs.c | 4 +- > c_src/cmd_debug.c | 38 ++++++++++++ > c_src/cmds.h | 2 + > src/bcachefs.rs | 1 + > src/commands/debug/mod.rs | 108 +++++++++++++++++++++++++++++++++++ > src/commands/debug/parser.rs | 57 ++++++++++++++++++ > src/commands/mod.rs | 1 + > 7 files changed, 210 insertions(+), 1 deletion(-) > create mode 100644 c_src/cmd_debug.c > create mode 100644 src/commands/debug/parser.rs > > diff --git a/c_src/bcachefs.c b/c_src/bcachefs.c > index c5b61097..311de278 100644 > --- a/c_src/bcachefs.c > +++ b/c_src/bcachefs.c > @@ -86,6 +86,7 @@ void bcachefs_usage(void) > "\n" > "Debug:\n" > "These commands work on offline, unmounted filesystems\n" > + " debug Operate directly on the underlying > btrees of a filesystem\n" > " dump Dump filesystem metadata to a qcow2 > image\n" > " list List filesystem metadata in textual > form\n" > " list_journal List contents of journal\n" > @@ -94,7 +95,8 @@ void bcachefs_usage(void) > " fusemount Mount a filesystem via FUSE\n" > "\n" > "Miscellaneous:\n" > - " completions Generate shell completions\n" > + " completions Generate shell completions\n" > + " list_bkeys List all bkey types known to the > current bcachefs version\n" > " version Display the version of the invoked > bcachefs tool\n"); > } > > diff --git a/c_src/cmd_debug.c b/c_src/cmd_debug.c > new file mode 100644 > index 00000000..73ba3995 > --- /dev/null > +++ b/c_src/cmd_debug.c > @@ -0,0 +1,38 @@ > +#include <stdio.h> > + > +#include "libbcachefs/bkey_types.h" > +#include "libbcachefs/btree_update.h" > +#include "libbcachefs/printbuf.h" > + > +#include "cmds.h" > + > +int cmd_dump_bkey(struct bch_fs *c, enum btree_id id, struct bpos pos) > +{ > + struct btree_trans *trans = bch2_trans_get(c); > + struct btree_iter iter = { NULL }; > + struct printbuf buf = PRINTBUF; > + int ret = 0; > + > + bch2_trans_iter_init(trans, &iter, id, pos, BTREE_ITER_all_snapshots); > + > + struct bkey_s_c k = bch2_btree_iter_peek(&iter); > + if ((ret = bkey_err(k))) { > + fprintf(stderr, "bch2_btree_iter_peek() failed: %s\n", > bch2_err_str(ret)); > + goto out; > + } > + if (!k.k || !bpos_eq(pos, k.k->p)) { > + bch2_bpos_to_text(&buf, pos); > + printf("no key at pos %s\n", buf.buf); > + ret = 1; > + goto out; > + } > + > + bch2_bkey_val_to_text(&buf, c, k); > + printf("%s\n", buf.buf); > + > +out: > + bch2_trans_iter_exit(trans, &iter); > + bch2_trans_put(trans); > + > + return ret; > +} > diff --git a/c_src/cmds.h b/c_src/cmds.h > index 64267dc4..d55a1440 100644 > --- a/c_src/cmds.h > +++ b/c_src/cmds.h > @@ -54,6 +54,8 @@ int cmd_subvolume_snapshot(int argc, char *argv[]); > > int cmd_fusemount(int argc, char *argv[]); > > +int cmd_dump_bkey(struct bch_fs *, enum btree_id, struct bpos); > + > void bcachefs_usage(void); > int device_cmds(int argc, char *argv[]); > int fs_cmds(int argc, char *argv[]); > diff --git a/src/bcachefs.rs b/src/bcachefs.rs > index 4b8cef49..0087334d 100644 > --- a/src/bcachefs.rs > +++ b/src/bcachefs.rs > @@ -102,6 +102,7 @@ fn main() -> ExitCode { > }; > > match cmd { > + "debug" => commands::debug(args[1..].to_vec()).report(), > "completions" => { > commands::completions(args[1..].to_vec()); > ExitCode::SUCCESS > diff --git a/src/commands/debug/mod.rs b/src/commands/debug/mod.rs > index 30ffd16b..31b23c7e 100644 > --- a/src/commands/debug/mod.rs > +++ b/src/commands/debug/mod.rs > @@ -1,7 +1,115 @@ > +use clap::Parser; > +use std::io::{BufRead, Write}; > + > +use bch_bindgen::bcachefs; > +use bch_bindgen::c; > +use bch_bindgen::fs::Fs; > + > mod bkey_types; > +mod parser; > + > +use bch_bindgen::c::bpos; > > use anyhow::Result; > > +/// Debug a bcachefs filesystem. > +#[derive(Parser, Debug)] > +pub struct Cli { > + #[arg(required(true))] > + devices: Vec<std::path::PathBuf>, > + > + #[arg(short, long)] > + command: Option<String>, > +} > + > +#[derive(Debug)] > +enum DebugCommand { > + Dump(DumpCommand), > +} > + > +#[derive(Debug)] > +struct DumpCommand { > + btree: String, > + bpos: bpos, > +} > + > +fn dump(fs: &Fs, cmd: DumpCommand) { > + let id: bch_bindgen::c::btree_id = match cmd.btree.parse() { > + Ok(b) => b, > + Err(_) => { > + eprintln!("unknown btree '{}'", cmd.btree); > + return; > + } > + }; > + > + unsafe { > + c::cmd_dump_bkey(fs.raw, id, cmd.bpos); > + } > +} > + > +fn usage() { > + println!("Usage:"); > + println!(" dump <btree_type> <bpos>"); > +} > + > +fn do_command(fs: &Fs, cmd: &str) -> i32 { > + match parser::parse_command(cmd) { > + Ok(cmd) => { > + match cmd { > + DebugCommand::Dump(cmd) => dump(fs, cmd), > + }; > + > + 0 > + } > + Err(e) => { > + println!("{e}"); > + usage(); > + > + 1 > + } > + } > +} > + > +pub fn debug(argv: Vec<String>) -> Result<()> { > + fn prompt() { > + print!("bcachefs> "); > + std::io::stdout().flush().unwrap(); > + } > + > + let opt = Cli::parse_from(argv); > + let fs_opts: bcachefs::bch_opts = Default::default(); > + > + if let Some(cmd) = opt.command { > + return match parser::parse_command(&cmd) { > + Ok(cmd) => { > + let fs = Fs::open(&opt.devices, fs_opts)?; > + match cmd { > + DebugCommand::Dump(cmd) => dump(&fs, cmd), > + } > + > + Ok(()) > + } > + Err(e) => { > + println!("{e}"); > + usage(); > + > + Ok(()) > + } > + }; > + } > + > + let fs = Fs::open(&opt.devices, fs_opts)?; > + > + prompt(); > + let stdin = std::io::stdin(); > + for line in stdin.lock().lines() { > + do_command(&fs, &line.unwrap()); > + prompt(); > + } > + > + Ok(()) > +} > + > pub fn list_bkeys() -> Result<()> { > print!("{}", bkey_types::get_bkey_type_info()?); > > diff --git a/src/commands/debug/parser.rs b/src/commands/debug/parser.rs > new file mode 100644 > index 00000000..fa036447 > --- /dev/null > +++ b/src/commands/debug/parser.rs > @@ -0,0 +1,57 @@ > +use nom::branch::alt; > +use nom::bytes::complete::tag; > +use nom::character::complete::{alpha1, char, space1, u32, u64}; > +use nom::combinator::{all_consuming, value}; > +use nom::sequence::tuple; > +use nom::IResult; > + > +use bch_bindgen::c::bpos; > + > +use crate::commands::debug::{DebugCommand, DumpCommand}; > + > +fn parse_bpos(input: &str) -> IResult<&str, bpos> { > + let (input, (inode, _, offset, _, snapshot)) = tuple(( > + u64, > + char(':'), > + u64, > + char(':'), > + alt((u32, value(u32::MAX, tag("U32_MAX")))), > + ))(input)?; > + > + Ok(( > + input, > + bpos { > + inode, > + offset, > + snapshot, > + }, > + )) > +} > + > +fn parse_dump_cmd(input: &str) -> IResult<&str, DebugCommand> { > + let (input, (_, btree, _, bpos)) = > + all_consuming(tuple((space1, alpha1, space1, parse_bpos)))(input)?; > + > + Ok(( > + input, > + DebugCommand::Dump(DumpCommand { > + btree: btree.to_string(), > + bpos, > + }), > + )) > +} > + > +fn parse_command_inner(input: &str) -> IResult<&str, DebugCommand> { > + let (input, _) = tag("dump")(input)?; > + > + parse_dump_cmd(input) > +} > + > +/// Given an input string, tries to parse it into a valid > +/// command to the debug tool. > +pub fn parse_command(input: &str) -> anyhow::Result<DebugCommand> { > + match parse_command_inner(input) { > + Ok((_, c)) => Ok(c), > + Err(e) => Err(anyhow::anyhow!("{e}")), > + } > +} > diff --git a/src/commands/mod.rs b/src/commands/mod.rs > index 9365f981..425e0849 100644 > --- a/src/commands/mod.rs > +++ b/src/commands/mod.rs > @@ -7,6 +7,7 @@ pub mod mount; > pub mod subvolume; > > pub use completions::completions; > +pub use debug::debug; > pub use debug::list_bkeys; > pub use list::list; > pub use mount::mount; > -- > 2.45.2 >
