summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorLuke Shumaker <lukeshu@lukeshu.com>2022-07-10 17:24:51 -0600
committerLuke Shumaker <lukeshu@lukeshu.com>2022-07-11 00:44:30 -0600
commitbde202f286461ab575dc7e3d83f996d9a5f4a6ec (patch)
tree64782c354c15f64a164996125a06c1bca30c9aa7 /lib
parentd2da99882ea49cc67780c0255bf624698898e7fe (diff)
Have a go at rearranging things in to a lib/btrfsprogs
Diffstat (limited to 'lib')
-rw-r--r--lib/btrfsprogs/btrfsinspect/mount.go407
-rw-r--r--lib/btrfsprogs/btrfsinspect/print_tree.go (renamed from lib/btrfsmisc/print_tree.go)90
-rw-r--r--lib/btrfsprogs/btrfsrepair/clearnodes.go91
-rw-r--r--lib/btrfsprogs/btrfsutil/open.go (renamed from lib/btrfsmisc/open.go)2
-rw-r--r--lib/btrfsprogs/btrfsutil/scan.go (renamed from lib/btrfsmisc/fsck.go)2
-rw-r--r--lib/btrfsprogs/btrfsutil/walk.go (renamed from lib/btrfsmisc/walk.go)2
6 files changed, 581 insertions, 13 deletions
diff --git a/lib/btrfsprogs/btrfsinspect/mount.go b/lib/btrfsprogs/btrfsinspect/mount.go
new file mode 100644
index 0000000..641bc64
--- /dev/null
+++ b/lib/btrfsprogs/btrfsinspect/mount.go
@@ -0,0 +1,407 @@
+// Copyright (C) 2022 Luke Shumaker <lukeshu@lukeshu.com>
+//
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package btrfsinspect
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "io"
+ "path/filepath"
+ "sync"
+ "sync/atomic"
+ "syscall"
+
+ "github.com/datawire/dlib/dcontext"
+ "github.com/datawire/dlib/dgroup"
+ "github.com/datawire/dlib/dlog"
+ "github.com/jacobsa/fuse"
+ "github.com/jacobsa/fuse/fuseops"
+ "github.com/jacobsa/fuse/fuseutil"
+
+ "git.lukeshu.com/btrfs-progs-ng/lib/btrfs"
+ "git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfsitem"
+ "git.lukeshu.com/btrfs-progs-ng/lib/linux"
+ "git.lukeshu.com/btrfs-progs-ng/lib/util"
+)
+
+func MountRO(ctx context.Context, fs *btrfs.FS, mountpoint string) error {
+ pvs := fs.LV.PhysicalVolumes()
+ if len(pvs) < 1 {
+ return errors.New("no devices")
+ }
+
+ deviceName := pvs[util.SortedMapKeys(pvs)[0]].Name()
+ if abs, err := filepath.Abs(deviceName); err == nil {
+ deviceName = abs
+ }
+
+ rootSubvol := &subvolume{
+ Subvolume: btrfs.Subvolume{
+ FS: fs,
+ TreeID: btrfs.FS_TREE_OBJECTID,
+ },
+ DeviceName: deviceName,
+ Mountpoint: mountpoint,
+ }
+ return rootSubvol.Run(ctx)
+}
+
+func fuseMount(ctx context.Context, mountpoint string, server fuse.Server, cfg *fuse.MountConfig) error {
+ grp := dgroup.NewGroup(ctx, dgroup.GroupConfig{
+ // Allow mountHandle.Join() returning to cause the
+ // "unmount" goroutine to quit.
+ ShutdownOnNonError: true,
+ })
+ mounted := uint32(1)
+ grp.Go("unmount", func(ctx context.Context) error {
+ <-ctx.Done()
+ var err error
+ var gotNil bool
+ // Keep retrying, because the FS might be busy.
+ for atomic.LoadUint32(&mounted) != 0 {
+ if _err := fuse.Unmount(mountpoint); _err == nil {
+ gotNil = true
+ } else if !gotNil {
+ err = _err
+ }
+ }
+ if gotNil {
+ return nil
+ }
+ return err
+ })
+ grp.Go("mount", func(ctx context.Context) error {
+ defer atomic.StoreUint32(&mounted, 0)
+
+ cfg.OpContext = ctx
+ cfg.ErrorLogger = dlog.StdLogger(ctx, dlog.LogLevelError)
+ cfg.DebugLogger = dlog.StdLogger(ctx, dlog.LogLevelDebug)
+
+ mountHandle, err := fuse.Mount(mountpoint, server, cfg)
+ if err != nil {
+ return err
+ }
+ dlog.Infof(ctx, "mounted %q", mountpoint)
+ return mountHandle.Join(dcontext.HardContext(ctx))
+ })
+ return grp.Wait()
+}
+
+type dirState struct {
+ Dir *btrfs.Dir
+}
+
+type fileState struct {
+ File *btrfs.File
+}
+
+type subvolume struct {
+ btrfs.Subvolume
+ DeviceName string
+ Mountpoint string
+
+ fuseutil.NotImplementedFileSystem
+ lastHandle uint64
+ dirHandles util.SyncMap[fuseops.HandleID, *dirState]
+ fileHandles util.SyncMap[fuseops.HandleID, *fileState]
+
+ subvolMu sync.Mutex
+ subvols map[string]struct{}
+ grp *dgroup.Group
+}
+
+func (sv *subvolume) Run(ctx context.Context) error {
+ sv.grp = dgroup.NewGroup(ctx, dgroup.GroupConfig{})
+ sv.grp.Go("self", func(ctx context.Context) error {
+ cfg := &fuse.MountConfig{
+ FSName: sv.DeviceName,
+ Subtype: "btrfs",
+
+ ReadOnly: true,
+
+ Options: map[string]string{
+ "allow_other": "",
+ },
+ }
+ return fuseMount(ctx, sv.Mountpoint, fuseutil.NewFileSystemServer(sv), cfg)
+ })
+ return sv.grp.Wait()
+}
+
+func (sv *subvolume) newHandle() fuseops.HandleID {
+ return fuseops.HandleID(atomic.AddUint64(&sv.lastHandle, 1))
+}
+
+func inodeItemToFUSE(itemBody btrfsitem.Inode) fuseops.InodeAttributes {
+ return fuseops.InodeAttributes{
+ Size: uint64(itemBody.Size),
+ Nlink: uint32(itemBody.NLink),
+ Mode: uint32(itemBody.Mode),
+ //RDev: itemBody.Rdev, // jacobsa/fuse doesn't expose rdev
+ Atime: itemBody.ATime.ToStd(),
+ Mtime: itemBody.MTime.ToStd(),
+ Ctime: itemBody.CTime.ToStd(),
+ //Crtime: itemBody.OTime,
+ Uid: uint32(itemBody.UID),
+ Gid: uint32(itemBody.GID),
+ }
+}
+
+func (sv *subvolume) LoadDir(inode btrfs.ObjID) (val *btrfs.Dir, err error) {
+ val, err = sv.Subvolume.LoadDir(inode)
+ if val != nil {
+ haveSubvolumes := false
+ for _, index := range util.SortedMapKeys(val.ChildrenByIndex) {
+ entry := val.ChildrenByIndex[index]
+ if entry.Location.ItemType == btrfsitem.ROOT_ITEM_KEY {
+ haveSubvolumes = true
+ break
+ }
+ }
+ if haveSubvolumes {
+ abspath, _err := val.AbsPath()
+ if _err != nil {
+ return
+ }
+ sv.subvolMu.Lock()
+ for _, index := range util.SortedMapKeys(val.ChildrenByIndex) {
+ entry := val.ChildrenByIndex[index]
+ if entry.Location.ItemType != btrfsitem.ROOT_ITEM_KEY {
+ continue
+ }
+ if sv.subvols == nil {
+ sv.subvols = make(map[string]struct{})
+ }
+ subMountpoint := filepath.Join(abspath, string(entry.Name))
+ if _, alreadyMounted := sv.subvols[subMountpoint]; !alreadyMounted {
+ sv.subvols[subMountpoint] = struct{}{}
+ workerName := fmt.Sprintf("%d-%s", val.Inode, filepath.Base(subMountpoint))
+ sv.grp.Go(workerName, func(ctx context.Context) error {
+ subSv := &subvolume{
+ Subvolume: btrfs.Subvolume{
+ FS: sv.FS,
+ TreeID: entry.Location.ObjectID,
+ },
+ DeviceName: sv.DeviceName,
+ Mountpoint: filepath.Join(sv.Mountpoint, subMountpoint[1:]),
+ }
+ return subSv.Run(ctx)
+ })
+ }
+ }
+ sv.subvolMu.Unlock()
+ }
+ }
+ return
+}
+
+func (sv *subvolume) StatFS(_ context.Context, op *fuseops.StatFSOp) error {
+ // See linux.git/fs/btrfs/super.c:btrfs_statfs()
+ sb, err := sv.FS.Superblock()
+ if err != nil {
+ return err
+ }
+
+ op.IoSize = sb.Data.SectorSize
+ op.BlockSize = sb.Data.SectorSize
+ op.Blocks = sb.Data.TotalBytes / uint64(sb.Data.SectorSize) // TODO: adjust for RAID type
+ //op.BlocksFree = TODO
+
+ // btrfs doesn't have a fixed number of inodes
+ op.Inodes = 0
+ op.InodesFree = 0
+
+ // jacobsa/fuse doesn't expose namelen, instead hard-coding it
+ // to 255. Which is fine by us, because that's what it is for
+ // btrfs.
+
+ return nil
+}
+
+func (sv *subvolume) LookUpInode(_ context.Context, op *fuseops.LookUpInodeOp) error {
+ if op.Parent == fuseops.RootInodeID {
+ parent, err := sv.GetRootInode()
+ if err != nil {
+ return err
+ }
+ op.Parent = fuseops.InodeID(parent)
+ }
+
+ dir, err := sv.LoadDir(btrfs.ObjID(op.Parent))
+ if err != nil {
+ return err
+ }
+ entry, ok := dir.ChildrenByName[op.Name]
+ if !ok {
+ return syscall.ENOENT
+ }
+ if entry.Location.ItemType != btrfsitem.INODE_ITEM_KEY {
+ // Subvolume
+ //
+ // Because each subvolume has its own pool of inodes
+ // (as in 2 different subvolumes can have files with
+ // te same inode number), so to represent that to FUSE
+ // we need to have this be a full separate mountpoint.
+ //
+ // I'd want to return EIO or EINTR or something here,
+ // but both the FUSE userspace tools and the kernel
+ // itself stat the mountpoint before mounting it, so
+ // we've got to return something bogus here to let
+ // that mount happen.
+ op.Entry = fuseops.ChildInodeEntry{
+ Child: 2, // an inode number that a real file will never have
+ Attributes: fuseops.InodeAttributes{
+ Nlink: 1,
+ Mode: uint32(linux.ModeFmtDir | 0700),
+ },
+ }
+ return nil
+ }
+ bareInode, err := sv.LoadBareInode(entry.Location.ObjectID)
+ if err != nil {
+ return err
+ }
+ op.Entry = fuseops.ChildInodeEntry{
+ Child: fuseops.InodeID(entry.Location.ObjectID),
+ Generation: fuseops.GenerationNumber(bareInode.InodeItem.Sequence),
+ Attributes: inodeItemToFUSE(*bareInode.InodeItem),
+ }
+ return nil
+}
+
+func (sv *subvolume) GetInodeAttributes(_ context.Context, op *fuseops.GetInodeAttributesOp) error {
+ if op.Inode == fuseops.RootInodeID {
+ inode, err := sv.GetRootInode()
+ if err != nil {
+ return err
+ }
+ op.Inode = fuseops.InodeID(inode)
+ }
+
+ bareInode, err := sv.LoadBareInode(btrfs.ObjID(op.Inode))
+ if err != nil {
+ return err
+ }
+
+ op.Attributes = inodeItemToFUSE(*bareInode.InodeItem)
+ return nil
+}
+
+func (sv *subvolume) OpenDir(_ context.Context, op *fuseops.OpenDirOp) error {
+ if op.Inode == fuseops.RootInodeID {
+ inode, err := sv.GetRootInode()
+ if err != nil {
+ return err
+ }
+ op.Inode = fuseops.InodeID(inode)
+ }
+
+ dir, err := sv.LoadDir(btrfs.ObjID(op.Inode))
+ if err != nil {
+ return err
+ }
+ handle := sv.newHandle()
+ sv.dirHandles.Store(handle, &dirState{
+ Dir: dir,
+ })
+ op.Handle = handle
+ return nil
+}
+func (sv *subvolume) ReadDir(_ context.Context, op *fuseops.ReadDirOp) error {
+ state, ok := sv.dirHandles.Load(op.Handle)
+ if !ok {
+ return syscall.EBADF
+ }
+ origOffset := op.Offset
+ for _, index := range util.SortedMapKeys(state.Dir.ChildrenByIndex) {
+ if index < uint64(origOffset) {
+ continue
+ }
+ entry := state.Dir.ChildrenByIndex[index]
+ n := fuseutil.WriteDirent(op.Dst[op.BytesRead:], fuseutil.Dirent{
+ Offset: fuseops.DirOffset(index + 1),
+ Inode: fuseops.InodeID(entry.Location.ObjectID),
+ Name: string(entry.Name),
+ Type: map[btrfsitem.FileType]fuseutil.DirentType{
+ btrfsitem.FT_UNKNOWN: fuseutil.DT_Unknown,
+ btrfsitem.FT_REG_FILE: fuseutil.DT_File,
+ btrfsitem.FT_DIR: fuseutil.DT_Directory,
+ btrfsitem.FT_CHRDEV: fuseutil.DT_Char,
+ btrfsitem.FT_BLKDEV: fuseutil.DT_Block,
+ btrfsitem.FT_FIFO: fuseutil.DT_FIFO,
+ btrfsitem.FT_SOCK: fuseutil.DT_Socket,
+ btrfsitem.FT_SYMLINK: fuseutil.DT_Link,
+ }[entry.Type],
+ })
+ if n == 0 {
+ break
+ }
+ op.BytesRead += n
+ }
+ return nil
+}
+func (sv *subvolume) ReleaseDirHandle(_ context.Context, op *fuseops.ReleaseDirHandleOp) error {
+ _, ok := sv.dirHandles.LoadAndDelete(op.Handle)
+ if !ok {
+ return syscall.EBADF
+ }
+ return nil
+}
+
+func (sv *subvolume) OpenFile(_ context.Context, op *fuseops.OpenFileOp) error {
+ file, err := sv.LoadFile(btrfs.ObjID(op.Inode))
+ if err != nil {
+ return err
+ }
+ handle := sv.newHandle()
+ sv.fileHandles.Store(handle, &fileState{
+ File: file,
+ })
+ op.Handle = handle
+ op.KeepPageCache = true
+ return nil
+}
+func (sv *subvolume) ReadFile(_ context.Context, op *fuseops.ReadFileOp) error {
+ state, ok := sv.fileHandles.Load(op.Handle)
+ if !ok {
+ return syscall.EBADF
+ }
+
+ var dat []byte
+ if op.Dst != nil {
+ size := util.Min(int64(len(op.Dst)), op.Size)
+ dat = op.Dst[:size]
+ } else {
+ dat = make([]byte, op.Size)
+ op.Data = [][]byte{dat}
+ }
+
+ var err error
+ op.BytesRead, err = state.File.ReadAt(dat, op.Offset)
+ if errors.Is(err, io.EOF) {
+ err = nil
+ }
+
+ return err
+}
+func (sv *subvolume) ReleaseFileHandle(_ context.Context, op *fuseops.ReleaseFileHandleOp) error {
+ _, ok := sv.fileHandles.LoadAndDelete(op.Handle)
+ if !ok {
+ return syscall.EBADF
+ }
+ return nil
+}
+
+func (sv *subvolume) ReadSymlink(_ context.Context, op *fuseops.ReadSymlinkOp) error {
+ return syscall.ENOSYS
+}
+
+func (sv *subvolume) GetXattr(_ context.Context, op *fuseops.GetXattrOp) error { return syscall.ENOSYS }
+func (sv *subvolume) ListXattr(_ context.Context, op *fuseops.ListXattrOp) error {
+ return syscall.ENOSYS
+}
+
+func (sv *subvolume) Destroy() {}
diff --git a/lib/btrfsmisc/print_tree.go b/lib/btrfsprogs/btrfsinspect/print_tree.go
index 0a00c70..72ff13e 100644
--- a/lib/btrfsmisc/print_tree.go
+++ b/lib/btrfsprogs/btrfsinspect/print_tree.go
@@ -2,7 +2,7 @@
//
// SPDX-License-Identifier: GPL-2.0-or-later
-package btrfsmisc
+package btrfsinspect
import (
"fmt"
@@ -16,10 +16,80 @@ import (
"git.lukeshu.com/btrfs-progs-ng/lib/util"
)
-// PrintTree mimics btrfs-progs
+func DumpTrees(fs *btrfs.FS) error {
+ superblock, err := fs.Superblock()
+ if err != nil {
+ return err
+ }
+
+ if superblock.Data.RootTree != 0 {
+ fmt.Printf("root tree\n")
+ if err := printTree(fs, btrfs.ROOT_TREE_OBJECTID); err != nil {
+ return err
+ }
+ }
+ if superblock.Data.ChunkTree != 0 {
+ fmt.Printf("chunk tree\n")
+ if err := printTree(fs, btrfs.CHUNK_TREE_OBJECTID); err != nil {
+ return err
+ }
+ }
+ if superblock.Data.LogTree != 0 {
+ fmt.Printf("log root tree\n")
+ if err := printTree(fs, btrfs.TREE_LOG_OBJECTID); err != nil {
+ return err
+ }
+ }
+ if superblock.Data.BlockGroupRoot != 0 {
+ fmt.Printf("block group tree\n")
+ if err := printTree(fs, btrfs.BLOCK_GROUP_TREE_OBJECTID); err != nil {
+ return err
+ }
+ }
+ if err := fs.TreeWalk(btrfs.ROOT_TREE_OBJECTID, btrfs.TreeWalkHandler{
+ Item: func(_ btrfs.TreePath, item btrfs.Item) error {
+ if item.Head.Key.ItemType != btrfsitem.ROOT_ITEM_KEY {
+ return nil
+ }
+ treeName, ok := map[btrfs.ObjID]string{
+ btrfs.ROOT_TREE_OBJECTID: "root",
+ btrfs.EXTENT_TREE_OBJECTID: "extent",
+ btrfs.CHUNK_TREE_OBJECTID: "chunk",
+ btrfs.DEV_TREE_OBJECTID: "device",
+ btrfs.FS_TREE_OBJECTID: "fs",
+ btrfs.ROOT_TREE_DIR_OBJECTID: "directory",
+ btrfs.CSUM_TREE_OBJECTID: "checksum",
+ btrfs.ORPHAN_OBJECTID: "orphan",
+ btrfs.TREE_LOG_OBJECTID: "log",
+ btrfs.TREE_LOG_FIXUP_OBJECTID: "log fixup",
+ btrfs.TREE_RELOC_OBJECTID: "reloc",
+ btrfs.DATA_RELOC_TREE_OBJECTID: "data reloc",
+ btrfs.EXTENT_CSUM_OBJECTID: "extent checksum",
+ btrfs.QUOTA_TREE_OBJECTID: "quota",
+ btrfs.UUID_TREE_OBJECTID: "uuid",
+ btrfs.FREE_SPACE_TREE_OBJECTID: "free space",
+ btrfs.MULTIPLE_OBJECTIDS: "multiple",
+ btrfs.BLOCK_GROUP_TREE_OBJECTID: "block group",
+ }[item.Head.Key.ObjectID]
+ if !ok {
+ treeName = "file"
+ }
+ fmt.Printf("%v tree %v \n", treeName, fmtKey(item.Head.Key))
+ return printTree(fs, item.Head.Key.ObjectID)
+ },
+ }); err != nil {
+ return err
+ }
+ fmt.Printf("total bytes %v\n", superblock.Data.TotalBytes)
+ fmt.Printf("bytes used %v\n", superblock.Data.BytesUsed)
+ fmt.Printf("uuid %v\n", superblock.Data.FSUUID)
+ return nil
+}
+
+// printTree mimics btrfs-progs
// kernel-shared/print-tree.c:btrfs_print_tree() and
// kernel-shared/print-tree.c:btrfs_print_leaf()
-func PrintTree(fs *btrfs.FS, treeID btrfs.ObjID) error {
+func printTree(fs *btrfs.FS, treeID btrfs.ObjID) error {
return fs.TreeWalk(treeID, btrfs.TreeWalkHandler{
Node: func(path btrfs.TreePath, nodeRef *util.Ref[btrfsvol.LogicalAddr, btrfs.Node], err error) error {
if err != nil {
@@ -32,7 +102,7 @@ func PrintTree(fs *btrfs.FS, treeID btrfs.ObjID) error {
},
PreKeyPointer: func(_ btrfs.TreePath, item btrfs.KeyPointer) error {
fmt.Printf("\t%v block %v gen %v\n",
- FmtKey(item.Key),
+ fmtKey(item.Key),
item.BlockPtr,
item.Generation)
return nil
@@ -41,12 +111,12 @@ func PrintTree(fs *btrfs.FS, treeID btrfs.ObjID) error {
i := path[len(path)-1].ItemIdx
fmt.Printf("\titem %v %v itemoff %v itemsize %v\n",
i,
- FmtKey(item.Head.Key),
+ fmtKey(item.Head.Key),
item.Head.DataOffset,
item.Head.DataSize)
switch body := item.Body.(type) {
case btrfsitem.FreeSpaceHeader:
- fmt.Printf("\t\tlocation %v\n", FmtKey(body.Location))
+ fmt.Printf("\t\tlocation %v\n", fmtKey(body.Location))
fmt.Printf("\t\tcache generation %v entries %v bitmaps %v\n",
body.Generation, body.NumEntries, body.NumBitmaps)
case btrfsitem.Inode:
@@ -69,7 +139,7 @@ func PrintTree(fs *btrfs.FS, treeID btrfs.ObjID) error {
case btrfsitem.DirEntries:
for _, dir := range body {
fmt.Printf("\t\tlocation %v type %v\n",
- FmtKey(dir.Location), dir.Type)
+ fmtKey(dir.Location), dir.Type)
fmt.Printf("\t\ttransid %v data_len %v name_len %v\n",
dir.TransID, dir.DataLen, dir.NameLen)
fmt.Printf("\t\tname: %s\n", dir.Name)
@@ -85,7 +155,7 @@ func PrintTree(fs *btrfs.FS, treeID btrfs.ObjID) error {
fmt.Printf("\t\tlast_snapshot %v flags %v refs %v\n",
body.LastSnapshot, body.Flags, body.Refs)
fmt.Printf("\t\tdrop_progress %v drop_level %v\n",
- FmtKey(body.DropProgress), body.DropLevel)
+ fmtKey(body.DropProgress), body.DropLevel)
fmt.Printf("\t\tlevel %v generation_v2 %v\n",
body.Level, body.GenerationV2)
if body.Generation == body.GenerationV2 {
@@ -116,7 +186,7 @@ func PrintTree(fs *btrfs.FS, treeID btrfs.ObjID) error {
body.Head.Refs, body.Head.Generation, body.Head.Flags)
if body.Head.Flags.Has(btrfsitem.EXTENT_FLAG_TREE_BLOCK) {
fmt.Printf("\t\ttree block %v level %v\n",
- FmtKey(body.Info.Key), body.Info.Level)
+ fmtKey(body.Info.Key), body.Info.Level)
}
printExtentInlineRefs(body.Refs)
case btrfsitem.Metadata:
@@ -340,7 +410,7 @@ func printExtentInlineRefs(refs []btrfsitem.ExtentInlineRef) {
}
// mimics print-tree.c:btrfs_print_key()
-func FmtKey(key btrfs.Key) string {
+func fmtKey(key btrfs.Key) string {
var out strings.Builder
fmt.Fprintf(&out, "key (%v %v", key.ObjectID.Format(key.ItemType), key.ItemType)
switch key.ItemType {
diff --git a/lib/btrfsprogs/btrfsrepair/clearnodes.go b/lib/btrfsprogs/btrfsrepair/clearnodes.go
new file mode 100644
index 0000000..595fef0
--- /dev/null
+++ b/lib/btrfsprogs/btrfsrepair/clearnodes.go
@@ -0,0 +1,91 @@
+// Copyright (C) 2022 Luke Shumaker <lukeshu@lukeshu.com>
+//
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package btrfsrepair
+
+import (
+ "errors"
+ "fmt"
+
+ "git.lukeshu.com/btrfs-progs-ng/lib/btrfs"
+ "git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfsvol"
+ "git.lukeshu.com/btrfs-progs-ng/lib/btrfsprogs/btrfsutil"
+ "git.lukeshu.com/btrfs-progs-ng/lib/util"
+)
+
+func ClearBadNodes(fs *btrfs.FS) error {
+ var uuidsInited bool
+ var metadataUUID, chunkTreeUUID btrfs.UUID
+
+ var treeName string
+ var treeID btrfs.ObjID
+ btrfsutil.WalkAllTrees(fs, btrfsutil.WalkAllTreesHandler{
+ PreTree: func(name string, id btrfs.ObjID) {
+ treeName = name
+ treeID = id
+ },
+ Err: func(err error) {
+ fmt.Printf("error: %v\n", err)
+ },
+ UnsafeNodes: true,
+ TreeWalkHandler: btrfs.TreeWalkHandler{
+ Node: func(path btrfs.TreePath, node *util.Ref[btrfsvol.LogicalAddr, btrfs.Node], err error) error {
+ if err == nil {
+ if !uuidsInited {
+ metadataUUID = node.Data.Head.MetadataUUID
+ chunkTreeUUID = node.Data.Head.ChunkTreeUUID
+ uuidsInited = true
+ }
+ return nil
+ }
+ if !errors.Is(err, btrfs.ErrNotANode) {
+ err = btrfsutil.WalkErr{
+ TreeName: treeName,
+ Path: path,
+ Err: err,
+ }
+ fmt.Printf("error: %v\n", err)
+ return nil
+ }
+ origErr := err
+ if !uuidsInited {
+ // TODO(lukeshu): Is there a better way to get the chunk
+ // tree UUID?
+ return fmt.Errorf("cannot repair node@%v: not (yet?) sure what the chunk tree UUID is", node.Addr)
+ }
+ node.Data = btrfs.Node{
+ Size: node.Data.Size,
+ ChecksumType: node.Data.ChecksumType,
+ Head: btrfs.NodeHeader{
+ //Checksum: filled below,
+ MetadataUUID: metadataUUID,
+ Addr: node.Addr,
+ Flags: btrfs.NodeWritten,
+ BackrefRev: btrfs.MixedBackrefRev,
+ ChunkTreeUUID: chunkTreeUUID,
+ Generation: 0,
+ Owner: treeID,
+ NumItems: 0,
+ Level: path[len(path)-1].NodeLevel,
+ },
+ }
+ node.Data.Head.Checksum, err = node.Data.CalculateChecksum()
+ if err != nil {
+ return btrfsutil.WalkErr{
+ TreeName: treeName,
+ Path: path,
+ Err: err,
+ }
+ }
+ if err := node.Write(); err != nil {
+ return err
+ }
+
+ fmt.Printf("fixed node@%v (err was %v)\n", node.Addr, origErr)
+ return nil
+ },
+ },
+ })
+ return nil
+}
diff --git a/lib/btrfsmisc/open.go b/lib/btrfsprogs/btrfsutil/open.go
index 8646d5a..cc081a6 100644
--- a/lib/btrfsmisc/open.go
+++ b/lib/btrfsprogs/btrfsutil/open.go
@@ -2,7 +2,7 @@
//
// SPDX-License-Identifier: GPL-2.0-or-later
-package btrfsmisc
+package btrfsutil
import (
"fmt"
diff --git a/lib/btrfsmisc/fsck.go b/lib/btrfsprogs/btrfsutil/scan.go
index b0c2ad7..d83525c 100644
--- a/lib/btrfsmisc/fsck.go
+++ b/lib/btrfsprogs/btrfsutil/scan.go
@@ -2,7 +2,7 @@
//
// SPDX-License-Identifier: GPL-2.0-or-later
-package btrfsmisc
+package btrfsutil
import (
"errors"
diff --git a/lib/btrfsmisc/walk.go b/lib/btrfsprogs/btrfsutil/walk.go
index 43275ba..0c54384 100644
--- a/lib/btrfsmisc/walk.go
+++ b/lib/btrfsprogs/btrfsutil/walk.go
@@ -2,7 +2,7 @@
//
// SPDX-License-Identifier: GPL-2.0-or-later
-package btrfsmisc
+package btrfsutil
import (
"fmt"