summaryrefslogtreecommitdiff
path: root/cmd/btrfs-rec
diff options
context:
space:
mode:
authorLuke Shumaker <lukeshu@lukeshu.com>2022-07-11 21:10:54 -0600
committerLuke Shumaker <lukeshu@lukeshu.com>2022-07-11 21:17:09 -0600
commit839dfa5d0aeadee9cb0f8581341922138f9595f0 (patch)
tree42130616613f57f0b9d40a7490a0f576aa94f35a /cmd/btrfs-rec
parent0f853e1ead10347be8c5715d6bb797dd5e643e2f (diff)
Move all of the small-ish tools to be part of btrfs-rec
Diffstat (limited to 'cmd/btrfs-rec')
-rw-r--r--cmd/btrfs-rec/inspect_dumptrees.go31
-rw-r--r--cmd/btrfs-rec/inspect_lsfiles.go187
-rw-r--r--cmd/btrfs-rec/inspect_lstrees.go68
-rw-r--r--cmd/btrfs-rec/inspect_mount.go26
-rw-r--r--cmd/btrfs-rec/main.go2
-rw-r--r--cmd/btrfs-rec/repair_clearbadnodes.go28
6 files changed, 341 insertions, 1 deletions
diff --git a/cmd/btrfs-rec/inspect_dumptrees.go b/cmd/btrfs-rec/inspect_dumptrees.go
new file mode 100644
index 0000000..5ddff60
--- /dev/null
+++ b/cmd/btrfs-rec/inspect_dumptrees.go
@@ -0,0 +1,31 @@
+// Copyright (C) 2022 Luke Shumaker <lukeshu@lukeshu.com>
+//
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package main
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/datawire/ocibuild/pkg/cliutil"
+ "github.com/spf13/cobra"
+
+ "git.lukeshu.com/btrfs-progs-ng/lib/btrfs"
+ "git.lukeshu.com/btrfs-progs-ng/lib/btrfsprogs/btrfsinspect"
+)
+
+func init() {
+ inspectors = append(inspectors, subcommand{
+ Command: cobra.Command{
+ Use: "dump-trees",
+ Short: "A clone of `btrfs inspect-internal dump-tree`",
+ Args: cliutil.WrapPositionalArgs(cobra.NoArgs),
+ },
+ RunE: func(fs *btrfs.FS, _ *cobra.Command, _ []string) error {
+ const version = "5.18.1"
+ fmt.Printf("btrfs-progs v%v\n", version)
+ return btrfsinspect.DumpTrees(os.Stdout, os.Stderr, fs)
+ },
+ })
+}
diff --git a/cmd/btrfs-rec/inspect_lsfiles.go b/cmd/btrfs-rec/inspect_lsfiles.go
new file mode 100644
index 0000000..2da31aa
--- /dev/null
+++ b/cmd/btrfs-rec/inspect_lsfiles.go
@@ -0,0 +1,187 @@
+// Copyright (C) 2022 Luke Shumaker <lukeshu@lukeshu.com>
+//
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package main
+
+import (
+ "fmt"
+ "reflect"
+ "strings"
+
+ "github.com/datawire/dlib/derror"
+ "github.com/datawire/ocibuild/pkg/cliutil"
+ "github.com/spf13/cobra"
+
+ "git.lukeshu.com/btrfs-progs-ng/lib/btrfs"
+ "git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfsitem"
+ "git.lukeshu.com/btrfs-progs-ng/lib/util"
+)
+
+func init() {
+ inspectors = append(inspectors, subcommand{
+ Command: cobra.Command{
+ Use: "ls-files",
+ Short: "A listing of all files in the filesystem",
+ Args: cliutil.WrapPositionalArgs(cobra.NoArgs),
+ },
+ RunE: func(fs *btrfs.FS, _ *cobra.Command, _ []string) error {
+ printSubvol(fs, "", "", "/", btrfs.Key{
+ ObjectID: btrfs.FS_TREE_OBJECTID,
+ ItemType: btrfsitem.ROOT_ITEM_KEY,
+ Offset: 0,
+ })
+ return nil
+ },
+ })
+}
+
+const (
+ tS = "    "
+ tl = "│   "
+ tT = "├── "
+ tL = "└── "
+)
+
+func printSubvol(fs *btrfs.FS, prefix0, prefix1, name string, key btrfs.Key) {
+ root, err := fs.TreeLookup(btrfs.ROOT_TREE_OBJECTID, key)
+ if err != nil {
+ fmt.Printf("%s%q error: could not look up root %v: %v\n", prefix0, name, key, err)
+ return
+ }
+ rootBody := root.Body.(btrfsitem.Root)
+
+ printDir(fs, root.Head.Key.ObjectID, prefix0, prefix1, name, rootBody.RootDirID)
+}
+
+func printDir(fs *btrfs.FS, fsTree btrfs.ObjID, prefix0, prefix1, dirName string, dirInode btrfs.ObjID) {
+ var errs derror.MultiError
+ items, err := fs.TreeSearchAll(fsTree, func(key btrfs.Key) int {
+ return util.CmpUint(dirInode, key.ObjectID)
+ })
+ if err != nil {
+ errs = append(errs, fmt.Errorf("read dir: %w", err))
+ }
+ var dirInodeDat btrfsitem.Inode
+ var dirInodeDatOK bool
+ membersByIndex := make(map[uint64]btrfsitem.DirEntry)
+ membersByNameHash := make(map[uint64]btrfsitem.DirEntry)
+ for _, item := range items {
+ switch item.Head.Key.ItemType {
+ case btrfsitem.INODE_ITEM_KEY:
+ if dirInodeDatOK {
+ if !reflect.DeepEqual(dirInodeDat, item.Body.(btrfsitem.Inode)) {
+ errs = append(errs, fmt.Errorf("read dir: multiple inodes"))
+ }
+ continue
+ }
+ dirInodeDat = item.Body.(btrfsitem.Inode)
+ dirInodeDatOK = true
+ case btrfsitem.INODE_REF_KEY:
+ // TODO
+ case btrfsitem.DIR_ITEM_KEY:
+ entry := item.Body.(btrfsitem.DirEntry)
+ namehash := btrfsitem.NameHash(entry.Name)
+ if namehash != item.Head.Key.Offset {
+ errs = append(errs, fmt.Errorf("read dir: direntry crc32c mismatch: key=%#x crc32c(%q)=%#x",
+ item.Head.Key.Offset, entry.Name, namehash))
+ continue
+ }
+ if other, exists := membersByNameHash[namehash]; exists {
+ if !reflect.DeepEqual(entry, other) {
+ if string(entry.Name) == string(other.Name) {
+ errs = append(errs, fmt.Errorf("read dir: multiple instances of direntry crc32c(%q)=%#x",
+ entry.Name, namehash))
+ } else {
+ errs = append(errs, fmt.Errorf("read dir: multiple instances of direntry crc32c(%q|%q)=%#x",
+ other.Name, entry.Name, namehash))
+ }
+ }
+ continue
+ }
+ membersByNameHash[btrfsitem.NameHash(entry.Name)] = entry
+ case btrfsitem.DIR_INDEX_KEY:
+ index := item.Head.Key.Offset
+ entry := item.Body.(btrfsitem.DirEntry)
+ if other, exists := membersByIndex[index]; exists {
+ if !reflect.DeepEqual(entry, other) {
+ errs = append(errs, fmt.Errorf("read dir: multiple instances of direntry index %v", index))
+ }
+ continue
+ }
+ membersByIndex[index] = entry
+ //case btrfsitem.XATTR_ITEM_KEY:
+ default:
+ errs = append(errs, fmt.Errorf("TODO: handle item type %v", item.Head.Key.ItemType))
+ }
+ }
+ fmt.Printf("%s%q\t[ino=%d",
+ prefix0, dirName, dirInode)
+ if dirInodeDatOK {
+ fmt.Printf("\tuid=%d\tgid=%d\tsize=%d]\n",
+ dirInodeDat.UID, dirInodeDat.GID, dirInodeDat.Size)
+ } else {
+ err := fmt.Errorf("read dir: no inode data")
+ if len(items) == 0 && len(errs) == 1 {
+ err = errs[0]
+ errs = nil
+ }
+ fmt.Printf("]\terror: %v\n", err)
+ }
+ for i, index := range util.SortedMapKeys(membersByIndex) {
+ entry := membersByIndex[index]
+ namehash := btrfsitem.NameHash(entry.Name)
+ if other, ok := membersByNameHash[namehash]; ok {
+ if !reflect.DeepEqual(entry, other) {
+ errs = append(errs, fmt.Errorf("read dir: index=%d disagrees with crc32c(%q)=%#x",
+ index, entry.Name, namehash))
+ }
+ delete(membersByNameHash, namehash)
+ } else {
+ errs = append(errs, fmt.Errorf("read dir: no DIR_ITEM crc32c(%q)=%#x for DIR_INDEX index=%d",
+ entry.Name, namehash, index))
+ }
+ p0, p1 := tT, tl
+ if (i == len(membersByIndex)-1) && (len(membersByNameHash) == 0) && (len(errs) == 0) {
+ p0, p1 = tL, tS
+ }
+ printDirEntry(fs, fsTree, prefix1+p0, prefix1+p1, entry)
+ }
+ for _, namehash := range util.SortedMapKeys(membersByNameHash) {
+ entry := membersByNameHash[namehash]
+ errs = append(errs, fmt.Errorf("read dir: no DIR_INDEX for DIR_ITEM crc32c(%q)=%#x",
+ entry.Name, namehash))
+ printDirEntry(fs, fsTree, prefix1+tT, prefix1+tl, entry)
+ }
+ for i, err := range errs {
+ p0, p1 := tT, tl
+ if i == len(errs)-1 {
+ p0, p1 = tL, tS
+ }
+ fmt.Printf("%serror: %s\n", prefix1+p0, strings.ReplaceAll(err.Error(), "\n", prefix1+p1+" \n"))
+ }
+}
+
+func printDirEntry(fs *btrfs.FS, fsTree btrfs.ObjID, prefix0, prefix1 string, entry btrfsitem.DirEntry) {
+ if len(entry.Data) != 0 {
+ fmt.Printf("%s%q: error: TODO: I don't know how to handle dirent.data\n",
+ prefix0, entry.Name)
+ return
+ }
+ switch entry.Type {
+ case btrfsitem.FT_DIR:
+ switch entry.Location.ItemType {
+ case btrfsitem.INODE_ITEM_KEY:
+ printDir(fs, fsTree, prefix0, prefix1, string(entry.Name), entry.Location.ObjectID)
+ case btrfsitem.ROOT_ITEM_KEY:
+ key := entry.Location
+ key.Offset = 0
+ printSubvol(fs, prefix0, prefix1, string(entry.Name), key)
+ default:
+ fmt.Printf("%s%q\t[location=%v type=%v] error: I'm not sure how to print a %v directory\n",
+ prefix0, entry.Name, entry.Location, entry.Type, entry.Location.ItemType)
+ }
+ default:
+ fmt.Printf("%s%q\t[location=%v type=%v]\n", prefix0, entry.Name, entry.Location, entry.Type)
+ }
+}
diff --git a/cmd/btrfs-rec/inspect_lstrees.go b/cmd/btrfs-rec/inspect_lstrees.go
new file mode 100644
index 0000000..976514f
--- /dev/null
+++ b/cmd/btrfs-rec/inspect_lstrees.go
@@ -0,0 +1,68 @@
+// Copyright (C) 2022 Luke Shumaker <lukeshu@lukeshu.com>
+//
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package main
+
+import (
+ "fmt"
+ "os"
+ "strconv"
+ "text/tabwriter"
+
+ "github.com/datawire/ocibuild/pkg/cliutil"
+ "github.com/spf13/cobra"
+
+ "git.lukeshu.com/btrfs-progs-ng/lib/btrfs"
+ "git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfsitem"
+ "git.lukeshu.com/btrfs-progs-ng/lib/btrfsprogs/btrfsutil"
+ "git.lukeshu.com/btrfs-progs-ng/lib/util"
+)
+
+func init() {
+ inspectors = append(inspectors, subcommand{
+ Command: cobra.Command{
+ Use: "ls-trees",
+ Short: "A brief view what types of items are in each tree",
+ Args: cliutil.WrapPositionalArgs(cobra.NoArgs),
+ },
+ RunE: func(fs *btrfs.FS, _ *cobra.Command, _ []string) error {
+ var treeErrCnt int
+ var treeItemCnt map[btrfsitem.Type]int
+ btrfsutil.WalkAllTrees(fs, btrfsutil.WalkAllTreesHandler{
+ PreTree: func(name string, treeID btrfs.ObjID) {
+ treeErrCnt = 0
+ treeItemCnt = make(map[btrfsitem.Type]int)
+ fmt.Printf("tree id=%v name=%q\n", treeID, name)
+ },
+ Err: func(_ error) {
+ treeErrCnt++
+ },
+ TreeWalkHandler: btrfs.TreeWalkHandler{
+ Item: func(_ btrfs.TreePath, item btrfs.Item) error {
+ typ := item.Head.Key.ItemType
+ treeItemCnt[typ] = treeItemCnt[typ] + 1
+ return nil
+ },
+ },
+ PostTree: func(_ string, _ btrfs.ObjID) {
+ totalItems := 0
+ for _, cnt := range treeItemCnt {
+ totalItems += cnt
+ }
+ numWidth := len(strconv.Itoa(util.Max(treeErrCnt, totalItems)))
+
+ table := tabwriter.NewWriter(os.Stdout, 0, 8, 2, ' ', 0)
+ fmt.Fprintf(table, " errors\t% *s\n", numWidth, strconv.Itoa(treeErrCnt))
+ for _, typ := range util.SortedMapKeys(treeItemCnt) {
+ fmt.Fprintf(table, " %v items\t% *s\n", typ, numWidth, strconv.Itoa(treeItemCnt[typ]))
+ }
+ fmt.Fprintf(table, " total items\t% *s\n", numWidth, strconv.Itoa(totalItems))
+ table.Flush()
+ },
+ })
+
+ return nil
+ },
+ })
+}
diff --git a/cmd/btrfs-rec/inspect_mount.go b/cmd/btrfs-rec/inspect_mount.go
new file mode 100644
index 0000000..5aae54a
--- /dev/null
+++ b/cmd/btrfs-rec/inspect_mount.go
@@ -0,0 +1,26 @@
+// Copyright (C) 2022 Luke Shumaker <lukeshu@lukeshu.com>
+//
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package main
+
+import (
+ "github.com/datawire/ocibuild/pkg/cliutil"
+ "github.com/spf13/cobra"
+
+ "git.lukeshu.com/btrfs-progs-ng/lib/btrfs"
+ "git.lukeshu.com/btrfs-progs-ng/lib/btrfsprogs/btrfsinspect"
+)
+
+func init() {
+ inspectors = append(inspectors, subcommand{
+ Command: cobra.Command{
+ Use: "mount MOUNTPOINT",
+ Short: "Mount the filesystem read-only",
+ Args: cliutil.WrapPositionalArgs(cobra.ExactArgs(1)),
+ },
+ RunE: func(fs *btrfs.FS, cmd *cobra.Command, args []string) error {
+ return btrfsinspect.MountRO(cmd.Context(), fs, args[0])
+ },
+ })
+}
diff --git a/cmd/btrfs-rec/main.go b/cmd/btrfs-rec/main.go
index 9b285b1..b68bdd8 100644
--- a/cmd/btrfs-rec/main.go
+++ b/cmd/btrfs-rec/main.go
@@ -74,7 +74,7 @@ func main() {
var openFlag int = os.O_RDONLY
argparserInspect := &cobra.Command{
- Use: "inpsect {[flags]|SUBCOMMAND}",
+ Use: "inspect {[flags]|SUBCOMMAND}",
Short: "Inspect (but don't modify) a broken btrfs filesystem",
Args: cliutil.WrapPositionalArgs(cliutil.OnlySubcommands),
diff --git a/cmd/btrfs-rec/repair_clearbadnodes.go b/cmd/btrfs-rec/repair_clearbadnodes.go
new file mode 100644
index 0000000..58b8bd6
--- /dev/null
+++ b/cmd/btrfs-rec/repair_clearbadnodes.go
@@ -0,0 +1,28 @@
+// Copyright (C) 2022 Luke Shumaker <lukeshu@lukeshu.com>
+//
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package main
+
+import (
+ "os"
+
+ "github.com/datawire/ocibuild/pkg/cliutil"
+ "github.com/spf13/cobra"
+
+ "git.lukeshu.com/btrfs-progs-ng/lib/btrfs"
+ "git.lukeshu.com/btrfs-progs-ng/lib/btrfsprogs/btrfsrepair"
+)
+
+func init() {
+ repairers = append(repairers, subcommand{
+ Command: cobra.Command{
+ Use: "clear-bad-nodes",
+ Short: "Overwrite corrupt nodes with empty nodes",
+ Args: cliutil.WrapPositionalArgs(cobra.NoArgs),
+ },
+ RunE: func(fs *btrfs.FS, _ *cobra.Command, _ []string) error {
+ return btrfsrepair.ClearBadNodes(os.Stdout, os.Stderr, fs)
+ },
+ })
+}