WIP: bcachefs: new debug command

Signed-off-by: Thomas Bertschinger <tahbertschinger@gmail.com>
This commit is contained in:
Thomas Bertschinger 2024-04-23 21:43:25 -06:00
parent fbb2233089
commit 16f2849433
9 changed files with 778 additions and 10 deletions

158
Cargo.lock generated
View File

@ -2,6 +2,12 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aho-corasick"
version = "1.1.2"
@ -83,8 +89,12 @@ dependencies = [
"colored",
"either",
"errno 0.2.8",
"gimli",
"libc",
"log",
"memmap2",
"nom",
"object",
"rpassword",
"udev",
"uuid",
@ -124,7 +134,7 @@ dependencies = [
"regex",
"rustc-hash",
"shlex",
"syn",
"syn 2.0.48",
"which",
]
@ -228,7 +238,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
"syn 2.0.48",
]
[[package]]
@ -253,12 +263,38 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "crc32fast"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa"
dependencies = [
"cfg-if",
]
[[package]]
name = "derive_more"
version = "0.99.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "either"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
version = "0.2.8"
@ -290,12 +326,45 @@ dependencies = [
"libc",
]
[[package]]
name = "fallible-iterator"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
[[package]]
name = "flate2"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "gimli"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
dependencies = [
"fallible-iterator",
"indexmap",
"stable_deref_trait",
]
[[package]]
name = "glob"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "hashbrown"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
[[package]]
name = "heck"
version = "0.4.1"
@ -311,6 +380,16 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "indexmap"
version = "2.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "itertools"
version = "0.12.1"
@ -376,6 +455,15 @@ version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
[[package]]
name = "memmap2"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322"
dependencies = [
"libc",
]
[[package]]
name = "memoffset"
version = "0.8.0"
@ -391,6 +479,15 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
dependencies = [
"adler",
]
[[package]]
name = "nom"
version = "7.1.3"
@ -401,6 +498,17 @@ dependencies = [
"minimal-lexical",
]
[[package]]
name = "object"
version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8ec7ab813848ba4522158d5517a6093db1ded27575b070f4177b8d12b41db5e"
dependencies = [
"flate2",
"memchr",
"ruzstd",
]
[[package]]
name = "once_cell"
version = "1.19.0"
@ -426,7 +534,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5"
dependencies = [
"proc-macro2",
"syn",
"syn 2.0.48",
]
[[package]]
@ -516,18 +624,52 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "ruzstd"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5174a470eeb535a721ae9fdd6e291c2411a906b96592182d05217591d5c5cf7b"
dependencies = [
"byteorder",
"derive_more",
"twox-hash",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.48"
@ -549,6 +691,16 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "twox-hash"
version = "1.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
dependencies = [
"cfg-if",
"static_assertions",
]
[[package]]
name = "udev"
version = "0.7.0"

View File

@ -23,3 +23,7 @@ either = "1.5"
rpassword = "7"
bch_bindgen = { path = "bch_bindgen" }
byteorder = "1.3"
gimli = "0.29.0"
object = "0.35.0"
memmap2 = "0.9.4"
nom = "7.1.3"

115
c_src/cmd_debug.c Normal file
View File

@ -0,0 +1,115 @@
#include <stdio.h>
#include "libbcachefs/bkey_types.h"
#include "libbcachefs/btree_update.h"
#include "libbcachefs/printbuf.h"
#include "libbcachefs/inode.h"
#include "cmds.h"
void write_field(void *base, u64 size, u64 offset, u64 value)
{
u8 *field8;
u16 *field16;
u32 *field32;
u64 *field64;
switch (size) {
case 1:
field8 = (u8 *) base + offset;
*field8 = (u8) value;
break;
case 2:
field16 = (u16 *) ((u8 *) base + offset);
*field16 = (u16) value;
break;
case 4:
field32 = (u32 *) ((u8 *) base + offset);
*field32 = (u32) value;
break;
case 8:
field64 = (u64 *) ((u8 *) base + offset);
*field64 = value;
break;
default:
fprintf(stderr, "can't handle size %llu\n", size);
}
}
int cmd_dump_bkey(struct bch_fs *c, enum btree_id id, struct bpos pos)
{
struct printbuf buf = PRINTBUF;
struct btree_trans *trans = bch2_trans_get(c);
struct btree_iter iter = { NULL };
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 (!bpos_eq(pos, k.k->p)) {
printf("no such key\n");
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;
}
int cmd_update_bkey(struct bch_fs *c, struct bkey_update u, struct bpos pos)
{
struct btree_trans *trans = bch2_trans_get(c);
struct btree_iter iter = { NULL };
int ret = 0;
set_bit(BCH_FS_no_invalid_checks, &c->flags);
if (!strcmp(u.bkey, "bch_inode_unpacked")) {
bch2_trans_iter_init(trans, &iter, BTREE_ID_inodes, pos,
BTREE_ITER_ALL_SNAPSHOTS);
struct bkey_s_c k = bch2_btree_iter_peek(&iter);
if (bkey_err(k)) {
// TODO: is this proper error handling?
printf("error getting key: %s\n", bch2_err_str(PTR_ERR(k.k)));
ret = 1;
goto out;
}
struct bch_inode_unpacked inode;
// TODO: check error
bch2_inode_unpack(k, &inode);
write_field(&inode, u.size, u.offset, u.value);
ret = bch2_inode_write(trans, &iter, &inode) ?:
bch2_trans_commit(trans, NULL, NULL, BCH_TRANS_COMMIT_no_invalid_checks);
if (ret != 0) {
printf("ret = %s (%d)\n", bch2_err_str(ret), ret);
}
} else {
// TODO can this return an error?
struct bkey_i *k = bch2_bkey_get_mut(trans, &iter, u.id, pos,
BTREE_ITER_ALL_SNAPSHOTS);
// TODO: check bkey type to confirm it matches specified type?
bch2_trans_unlock(trans);
write_field(&k->v, u.size, u.offset, u.value);
ret = bch2_btree_insert(c, u.id, k, NULL, BCH_TRANS_COMMIT_no_invalid_checks);
if (ret != 0) {
printf("ret = %s (%d)\n", bch2_err_str(ret), ret);
}
}
out:
bch2_trans_iter_exit(trans, &iter);
bch2_trans_put(trans);
return ret;
}

View File

@ -9,6 +9,14 @@
#include "tools-util.h"
struct bkey_update {
enum btree_id id;
const char *bkey;
u64 offset;
u64 size;
u64 value;
};
int cmd_format(int argc, char *argv[]);
int cmd_show_super(int argc, char *argv[]);
int cmd_reset_counters(int argc, char *argv[]);
@ -54,6 +62,9 @@ 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);
int cmd_update_bkey(struct bch_fs *, struct bkey_update, struct bpos);
void bcachefs_usage(void);
int device_cmds(int argc, char *argv[]);
int fs_cmds(int argc, char *argv[]);

View File

@ -1,11 +1,11 @@
mod wrappers;
mod commands;
mod key;
mod wrappers;
use std::ffi::{c_char, CString};
use commands::logger::SimpleLogger;
use bch_bindgen::c;
use commands::logger::SimpleLogger;
#[derive(Debug)]
pub struct ErrnoError(pub errno::Errno);
@ -25,10 +25,7 @@ fn handle_c_command(mut argv: Vec<String>, symlink_cmd: Option<&str>) -> i32 {
let argc: i32 = argv.len().try_into().unwrap();
let argv: Vec<_> = argv
.into_iter()
.map(|s| CString::new(s).unwrap())
.collect();
let argv: Vec<_> = argv.into_iter().map(|s| CString::new(s).unwrap()).collect();
let mut argv = argv
.into_iter()
.map(|s| Box::into_raw(s.into_boxed_c_str()) as *mut c_char)
@ -41,7 +38,7 @@ fn handle_c_command(mut argv: Vec<String>, symlink_cmd: Option<&str>) -> i32 {
"--help" => {
c::bcachefs_usage();
0
},
}
"data" => c::data_cmds(argc, argv),
"device" => c::device_cmds(argc, argv),
"dump" => c::cmd_dump(argc, argv),
@ -110,6 +107,7 @@ fn main() {
"list" => commands::list(args[1..].to_vec()),
"mount" => commands::mount(args, symlink_cmd),
"subvolume" => commands::subvolume(args[1..].to_vec()),
"debug" => commands::debug(args[1..].to_vec()),
_ => handle_c_command(args, symlink_cmd),
};

View File

@ -0,0 +1,289 @@
//! Representation of the bcachefs bkey types, derived from DWARF debug info.
//!
//! This is adapted from `gimli/crates/examples/src/bin/simple.rs`.
use gimli::Reader as _;
use object::{Object, ObjectSection};
use std::{borrow, error, fs};
use std::collections::HashSet;
/// A list of the known bcachefs bkey types.
#[derive(Debug)]
pub struct BkeyTypes(Vec<BchStruct>);
impl BkeyTypes {
pub fn new() -> Self {
BkeyTypes(Vec::new())
}
/// Given a struct name and a member name, return the size and offset of
/// the member within the struct, or None if it does not exist.
pub fn get_member_layout(&self, outer: &str, member: &str) -> Option<(u64, u64)> {
for bkey_type in self.0.iter() {
if bkey_type.name == *outer {
return bkey_type.member_layout(member);
}
}
None
}
}
#[derive(Debug)]
pub struct BchStruct {
name: String,
pub members: Vec<BchMember>,
}
impl BchStruct {
pub fn member_layout(&self, name: &str) -> Option<(u64, u64)> {
for memb in self.members.iter() {
if memb.name == *name {
return Some((memb.size, memb.offset));
}
}
None
}
}
#[derive(Debug)]
pub struct BchMember {
name: String,
size: u64,
offset: u64,
}
// This is a simple wrapper around `object::read::RelocationMap` that implements
// `gimli::read::Relocate` for use with `gimli::RelocateReader`.
// You only need this if you are parsing relocatable object files.
#[derive(Debug, Default)]
struct RelocationMap(object::read::RelocationMap);
impl<'a> gimli::read::Relocate for &'a RelocationMap {
fn relocate_address(&self, offset: usize, value: u64) -> gimli::Result<u64> {
Ok(self.0.relocate(offset as u64, value))
}
fn relocate_offset(&self, offset: usize, value: usize) -> gimli::Result<usize> {
<usize as gimli::ReaderOffset>::from_u64(self.0.relocate(offset as u64, value as u64))
}
}
// The section data that will be stored in `DwarfSections` and `DwarfPackageSections`.
#[derive(Default)]
struct Section<'data> {
data: borrow::Cow<'data, [u8]>,
relocations: RelocationMap,
}
// The reader type that will be stored in `Dwarf` and `DwarfPackage`.
// If you don't need relocations, you can use `gimli::EndianSlice` directly.
type Reader<'data> =
gimli::RelocateReader<gimli::EndianSlice<'data, gimli::RunTimeEndian>, &'data RelocationMap>;
fn process_file(
object: &object::File,
struct_list: &mut BkeyTypes,
) -> Result<(), Box<dyn error::Error>> {
let endian = if object.is_little_endian() {
gimli::RunTimeEndian::Little
} else {
gimli::RunTimeEndian::Big
};
// Load a `Section` that may own its data.
fn load_section<'data>(
object: &object::File<'data>,
name: &str,
) -> Result<Section<'data>, Box<dyn error::Error>> {
Ok(match object.section_by_name(name) {
Some(section) => Section {
data: section.uncompressed_data()?,
relocations: section.relocation_map().map(RelocationMap)?,
},
None => Default::default(),
})
}
// Borrow a `Section` to create a `Reader`.
fn borrow_section<'data>(
section: &'data Section<'data>,
endian: gimli::RunTimeEndian,
) -> Reader<'data> {
let slice = gimli::EndianSlice::new(borrow::Cow::as_ref(&section.data), endian);
gimli::RelocateReader::new(slice, &section.relocations)
}
// Load all of the sections.
let dwarf_sections = gimli::DwarfSections::load(|id| load_section(object, id.name()))?;
// Create `Reader`s for all of the sections and do preliminary parsing.
// Alternatively, we could have used `Dwarf::load` with an owned type such as `EndianRcSlice`.
let dwarf = dwarf_sections.borrow(|section| borrow_section(section, endian));
let mut bkey_types = HashSet::new();
load_bkey_types(&mut bkey_types);
let mut iter = dwarf.units();
while let Some(header) = iter.next()? {
let unit = dwarf.unit(header)?;
process_unit(&dwarf, &unit, struct_list, &mut bkey_types)?;
}
Ok(())
}
fn load_bkey_types(bkey_types: &mut HashSet<String>) {
let mut ptr: *const *const i8 = unsafe { bch_bindgen::c::bch2_bkey_types.as_ptr() };
unsafe {
while !(*ptr).is_null() {
let mut bkey_name = String::from("bch_");
bkey_name.push_str(std::ffi::CStr::from_ptr(*ptr).to_str().unwrap());
bkey_types.insert(bkey_name);
ptr = ptr.offset(1);
}
}
// This key type is not included in BCH2_BKEY_TYPES.
bkey_types.insert("bch_inode_unpacked".to_string());
}
fn process_unit(
dwarf: &gimli::Dwarf<Reader>,
unit: &gimli::Unit<Reader>,
struct_list: &mut BkeyTypes,
bkey_types: &mut HashSet<String>,
) -> Result<(), gimli::Error> {
let mut tree = unit.entries_tree(None)?;
process_tree(dwarf, unit, tree.root()?, struct_list, bkey_types)?;
Ok(())
}
fn process_tree(
dwarf: &gimli::Dwarf<Reader>,
unit: &gimli::Unit<Reader>,
node: gimli::EntriesTreeNode<Reader>,
struct_list: &mut BkeyTypes,
bkey_types: &mut HashSet<String>,
) -> gimli::Result<()> {
let entry = node.entry();
if entry.tag() == gimli::DW_TAG_structure_type {
if let Some(name) = entry.attr(gimli::DW_AT_name)? {
if let Ok(name) = dwarf.attr_string(unit, name.value()) {
let name = name.to_string_lossy()?.into_owned();
if bkey_types.remove(&name.clone()) {
process_struct(name, dwarf, unit, node, struct_list)?;
}
}
}
} else {
let mut children = node.children();
while let Some(child) = children.next()? {
process_tree(dwarf, unit, child, struct_list, bkey_types)?;
}
}
Ok(())
}
fn process_struct(
name: std::string::String,
dwarf: &gimli::Dwarf<Reader>,
unit: &gimli::Unit<Reader>,
node: gimli::EntriesTreeNode<Reader>,
struct_list: &mut BkeyTypes,
) -> gimli::Result<()> {
let mut bch_struct = BchStruct {
name,
members: Vec::new(),
};
let mut children = node.children();
while let Some(child) = children.next()? {
if let Some(member) = process_struct_member(dwarf, unit, child) {
bch_struct.members.push(member);
}
}
struct_list.0.push(bch_struct);
Ok(())
}
fn process_struct_member(
dwarf: &gimli::Dwarf<Reader>,
unit: &gimli::Unit<Reader>,
node: gimli::EntriesTreeNode<Reader>,
) -> Option<BchMember> {
let entry = node.entry();
let name: Option<String> = entry.attr(gimli::DW_AT_name).ok()?.map(|name| {
if let Ok(name) = dwarf.attr_string(unit, name.value()) {
Some(name.to_string_lossy().ok()?.into_owned())
} else {
None
}
})?;
let Some(name) = name else {
return None;
};
let offset: Option<u64> = entry
.attr(gimli::DW_AT_data_member_location)
.ok()?
.map(|offset| offset.value().udata_value())?;
let Some(offset) = offset else {
return None;
};
let size = entry.attr(gimli::DW_AT_type).ok()?.map(|ty| {
if let gimli::AttributeValue::UnitRef(offset) = ty.value() {
let mut ty_entry = unit.entries_at_offset(offset).ok()?;
ty_entry.next_entry().ok()?;
if let Some(t) = ty_entry.current() {
return get_size(unit, t);
}
}
None
})?;
let Some(size) = size else {
return None;
};
Some(BchMember { name, offset, size })
}
fn get_size(
unit: &gimli::Unit<Reader>,
entry: &gimli::DebuggingInformationEntry<Reader>,
) -> Option<u64> {
if let Some(size) = entry.attr(gimli::DW_AT_byte_size).ok()? {
return size.udata_value();
}
if let Some(ref_type) = entry.attr(gimli::DW_AT_type).ok()? {
if let gimli::AttributeValue::UnitRef(offset) = ref_type.value() {
let mut type_entry = unit.entries_at_offset(offset).ok()?;
type_entry.next_entry().ok()?;
if let Some(t) = type_entry.current() {
return get_size(unit, t);
}
}
}
None
}
/// Return a list of the known bkey types.
pub fn get_bkey_type_info() -> BkeyTypes {
let path = fs::read_link("/proc/self/exe").unwrap();
let file = fs::File::open(path).unwrap();
let mmap = unsafe { memmap2::Mmap::map(&file).unwrap() };
let object = object::File::parse(&*mmap).unwrap();
let mut struct_list = BkeyTypes::new();
process_file(&object, &mut struct_list).unwrap();
struct_list
}

108
src/commands/debug/mod.rs Normal file
View File

@ -0,0 +1,108 @@
use clap::Parser;
use std::ffi::{c_char, CString};
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;
/// Debug a bcachefs filesystem.
#[derive(Parser, Debug)]
pub struct Cli {
#[arg(required(true))]
devices: Vec<std::path::PathBuf>,
}
#[derive(Debug)]
enum DebugCommand {
Dump(DumpCommand),
Update(UpdateCommand),
}
#[derive(Debug)]
struct DumpCommand {
btree: String,
bpos: bpos,
}
#[derive(Debug)]
struct UpdateCommand {
btree: String,
bpos: bpos,
bkey: String,
field: String,
value: u64,
}
fn update(fs: &Fs, type_list: &bkey_types::BkeyTypes, cmd: UpdateCommand) {
let bkey = CString::new(cmd.bkey.clone()).unwrap();
let bkey = bkey.as_ptr() as *const c_char;
let id: bch_bindgen::c::btree_id = cmd.btree.parse().expect("no such btree");
if let Some((size, offset)) = type_list.get_member_layout(&cmd.bkey, &cmd.field) {
let update = c::bkey_update {
id,
bkey,
offset,
size,
value: cmd.value,
};
unsafe {
c::cmd_update_bkey(fs.raw, update, cmd.bpos);
}
} else {
println!("unknown field '{}'", cmd.field);
}
}
fn dump(fs: &Fs, cmd: DumpCommand) {
let id: bch_bindgen::c::btree_id = cmd.btree.parse().expect("no such btree");
unsafe {
c::cmd_dump_bkey(fs.raw, id, cmd.bpos);
}
}
pub fn debug(argv: Vec<String>) -> i32 {
fn prompt() {
print!("bcachefs> ");
std::io::stdout().flush().unwrap();
}
let opt = Cli::parse_from(argv);
let fs_opts: bcachefs::bch_opts = Default::default();
let fs = match Fs::open(&opt.devices, fs_opts) {
Ok(fs) => fs,
Err(_) => {
return 1;
}
};
let type_list = bkey_types::get_bkey_type_info();
prompt();
let stdin = std::io::stdin();
for line in stdin.lock().lines() {
let line = line.unwrap();
if let Some(cmd) = parser::parse_command(&line) {
match cmd {
// usage: dump <btree_type> <bpos>
DebugCommand::Dump(cmd) => dump(&fs, cmd),
// usage: update <btree_type> <bpos> <bkey_type>.<field>=<value>
DebugCommand::Update(cmd) => update(&fs, &type_list, cmd),
}
} else {
println!("failed to parse a command");
};
prompt();
}
0
}

View File

@ -0,0 +1,89 @@
use nom::branch::alt;
use nom::bytes::complete::{tag, take_while};
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, UpdateCommand};
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 symbol_name(input: &str) -> IResult<&str, &str> {
take_while(|c: char| c.is_alphabetic() || c == '_')(input)
}
fn parse_update_cmd(input: &str) -> IResult<&str, DebugCommand> {
let (input, (_, btree, _, bpos, _, bkey, _, field, _, value)) = all_consuming(tuple((
space1,
alpha1,
space1,
parse_bpos,
space1,
symbol_name,
char('.'),
symbol_name,
char('='),
u64,
)))(input)?;
Ok((
input,
DebugCommand::Update(UpdateCommand {
btree: btree.to_string(),
bpos,
bkey: bkey.to_string(),
field: field.to_string(),
value,
}),
))
}
fn parse_command_inner(input: &str) -> IResult<&str, DebugCommand> {
let (input, cmd) = alt((tag("dump"), tag("update")))(input)?;
match cmd {
"dump" => parse_dump_cmd(input),
"update" => parse_update_cmd(input),
_ => unreachable!(),
}
}
pub fn parse_command(input: &str) -> Option<DebugCommand> {
match parse_command_inner(input) {
Ok((_, c)) => Some(c),
Err(_) => None,
}
}

View File

@ -5,11 +5,13 @@ pub mod mount;
pub mod list;
pub mod completions;
pub mod subvolume;
pub mod debug;
pub use mount::mount;
pub use list::list;
pub use completions::completions;
pub use subvolume::subvolume;
pub use debug::debug;
#[derive(clap::Parser, Debug)]
#[command(name = "bcachefs")]