diff options
author | Luke Shumaker <lukeshu@lukeshu.com> | 2022-07-10 17:24:51 -0600 |
---|---|---|
committer | Luke Shumaker <lukeshu@lukeshu.com> | 2022-07-11 00:44:30 -0600 |
commit | bde202f286461ab575dc7e3d83f996d9a5f4a6ec (patch) | |
tree | 64782c354c15f64a164996125a06c1bca30c9aa7 /lib | |
parent | d2da99882ea49cc67780c0255bf624698898e7fe (diff) |
Have a go at rearranging things in to a lib/btrfsprogs
Diffstat (limited to 'lib')
-rw-r--r-- | lib/btrfsprogs/btrfsinspect/mount.go | 407 | ||||
-rw-r--r-- | lib/btrfsprogs/btrfsinspect/print_tree.go (renamed from lib/btrfsmisc/print_tree.go) | 90 | ||||
-rw-r--r-- | lib/btrfsprogs/btrfsrepair/clearnodes.go | 91 | ||||
-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" |