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.

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