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
> 

Reply via email to