public inbox for linux-bcachefs@vger.kernel.org
 help / color / mirror / Atom feed
From: Thomas Bertschinger <tahbertschinger@gmail.com>
To: kent.overstreet@linux.dev, linux-bcachefs@vger.kernel.org,
	bfoster@redhat.com
Cc: Thomas Bertschinger <tahbertschinger@gmail.com>
Subject: [PATCH TOOLS v2 4/5] introduce "debug" command and "dump" subcommand
Date: Sun, 14 Jul 2024 14:02:25 -0600	[thread overview]
Message-ID: <20240714200226.746471-5-tahbertschinger@gmail.com> (raw)
In-Reply-To: <20240714200226.746471-1-tahbertschinger@gmail.com>

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 <tahbertschinger@gmail.com>
---
 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


  parent reply	other threads:[~2024-07-14 20:03 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-07-14 20:02 [PATCH TOOLS v2 0/5] new debug command Thomas Bertschinger
2024-07-14 20:02 ` [PATCH TOOLS v2 1/5] include debuginfo in bcachefs binary by default Thomas Bertschinger
2024-07-14 20:20   ` Kent Overstreet
2024-07-14 20:02 ` [PATCH TOOLS v2 2/5] update minimum Rust version to 1.74.0 Thomas Bertschinger
2024-07-14 20:02 ` [PATCH TOOLS v2 3/5] introduce "list_bkeys" command Thomas Bertschinger
2024-07-14 20:02 ` Thomas Bertschinger [this message]
2024-07-14 20:19   ` [PATCH TOOLS v2 4/5] introduce "debug" command and "dump" subcommand Kent Overstreet
2024-07-14 20:02 ` [PATCH TOOLS v2 5/5] introduce new "debug update" command Thomas Bertschinger
2024-07-14 20:21 ` [PATCH TOOLS v2 0/5] new debug command Kent Overstreet

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20240714200226.746471-5-tahbertschinger@gmail.com \
    --to=tahbertschinger@gmail.com \
    --cc=bfoster@redhat.com \
    --cc=kent.overstreet@linux.dev \
    --cc=linux-bcachefs@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox