diff --git a/Cargo.lock b/Cargo.lock index 22ac0e3b..50e39366 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 853123ee..1efc66be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/c_src/cmd_debug.c b/c_src/cmd_debug.c new file mode 100644 index 00000000..9d5def1a --- /dev/null +++ b/c_src/cmd_debug.c @@ -0,0 +1,115 @@ +#include + +#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; +} diff --git a/c_src/cmds.h b/c_src/cmds.h index 64267dc4..cb76f677 100644 --- a/c_src/cmds.h +++ b/c_src/cmds.h @@ -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[]); diff --git a/src/bcachefs.rs b/src/bcachefs.rs index e8099ffa..dbe79578 100644 --- a/src/bcachefs.rs +++ b/src/bcachefs.rs @@ -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, 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, 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), }; diff --git a/src/commands/debug/bkey_types.rs b/src/commands/debug/bkey_types.rs new file mode 100644 index 00000000..340e89cf --- /dev/null +++ b/src/commands/debug/bkey_types.rs @@ -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); + +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, +} + +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 { + Ok(self.0.relocate(offset as u64, value)) + } + + fn relocate_offset(&self, offset: usize, value: usize) -> gimli::Result { + ::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, &'data RelocationMap>; + +fn process_file( + object: &object::File, + struct_list: &mut BkeyTypes, +) -> Result<(), Box> { + 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, Box> { + 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(§ion.data), endian); + gimli::RelocateReader::new(slice, §ion.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) { + 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, + unit: &gimli::Unit, + struct_list: &mut BkeyTypes, + bkey_types: &mut HashSet, +) -> 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, + unit: &gimli::Unit, + node: gimli::EntriesTreeNode, + struct_list: &mut BkeyTypes, + bkey_types: &mut HashSet, +) -> 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, + unit: &gimli::Unit, + node: gimli::EntriesTreeNode, + 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, + unit: &gimli::Unit, + node: gimli::EntriesTreeNode, +) -> Option { + let entry = node.entry(); + + let name: Option = 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 = 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, + entry: &gimli::DebuggingInformationEntry, +) -> Option { + 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 +} diff --git a/src/commands/debug/mod.rs b/src/commands/debug/mod.rs new file mode 100644 index 00000000..e5651921 --- /dev/null +++ b/src/commands/debug/mod.rs @@ -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, +} + +#[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) -> 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 + DebugCommand::Dump(cmd) => dump(&fs, cmd), + // usage: update .= + DebugCommand::Update(cmd) => update(&fs, &type_list, cmd), + } + } else { + println!("failed to parse a command"); + }; + prompt(); + } + + 0 +} diff --git a/src/commands/debug/parser.rs b/src/commands/debug/parser.rs new file mode 100644 index 00000000..550860c9 --- /dev/null +++ b/src/commands/debug/parser.rs @@ -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 { + match parse_command_inner(input) { + Ok((_, c)) => Some(c), + Err(_) => None, + } +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index c7645926..831aaf5a 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -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")]