From 72b20c1d798551bfa2bb46f5521553144b45c09f Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Thu, 2 Mar 2023 16:02:42 -0700 Subject: btrfstree: Rethink the API, but leave the old API in place --- lib/btrfsutil/old_rebuilt_forrest.go | 46 +++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 11 deletions(-) (limited to 'lib/btrfsutil') diff --git a/lib/btrfsutil/old_rebuilt_forrest.go b/lib/btrfsutil/old_rebuilt_forrest.go index d17aa4a..e853be7 100644 --- a/lib/btrfsutil/old_rebuilt_forrest.go +++ b/lib/btrfsutil/old_rebuilt_forrest.go @@ -166,7 +166,7 @@ func (bt *OldRebuiltForrest) rawTreeWalk(ctx context.Context, treeID btrfsprim.O cacheEntry.RootErr = err return } - root, err := btrfstree.LookupTreeRoot(ctx, bt, *sb, treeID) + root, err := btrfstree.OldLookupTreeRoot(ctx, bt.TreeSearch, *sb, treeID) if err != nil { cacheEntry.RootErr = err return @@ -263,20 +263,21 @@ func (bt *OldRebuiltForrest) readNode(nodeInfo nodeInfo) *btrfstree.Node { // TreeLookup implements btrfstree.TreeOperator. func (bt *OldRebuiltForrest) TreeLookup(treeID btrfsprim.ObjID, key btrfsprim.Key) (btrfstree.Item, error) { - return bt.RebuiltTree(bt.ctx, treeID).treeLookup(bt.ctx, key) + return bt.RebuiltTree(bt.ctx, treeID).TreeLookup(bt.ctx, key) } -func (tree oldRebuiltTree) treeLookup(ctx context.Context, key btrfsprim.Key) (btrfstree.Item, error) { - return tree.treeSearch(ctx, btrfstree.SearchExactKey(key)) +// TreeLookup implements btrfstree.Tree. +func (tree oldRebuiltTree) TreeLookup(ctx context.Context, key btrfsprim.Key) (btrfstree.Item, error) { + return tree.TreeSearch(ctx, btrfstree.SearchExactKey(key)) } // TreeSearch implements btrfstree.TreeOperator. func (bt *OldRebuiltForrest) TreeSearch(treeID btrfsprim.ObjID, searcher btrfstree.TreeSearcher) (btrfstree.Item, error) { - return bt.RebuiltTree(bt.ctx, treeID).treeSearch(bt.ctx, searcher) + return bt.RebuiltTree(bt.ctx, treeID).TreeSearch(bt.ctx, searcher) } // TreeSearch implements btrfstree.Tree. -func (tree oldRebuiltTree) treeSearch(_ context.Context, searcher btrfstree.TreeSearcher) (btrfstree.Item, error) { +func (tree oldRebuiltTree) TreeSearch(_ context.Context, searcher btrfstree.TreeSearcher) (btrfstree.Item, error) { if tree.RootErr != nil { return btrfstree.Item{}, tree.RootErr } @@ -299,6 +300,26 @@ func (tree oldRebuiltTree) treeSearch(_ context.Context, searcher btrfstree.Tree return item, nil } +// TreeRange implements btrfstree.Tree. +func (tree oldRebuiltTree) TreeRange(_ context.Context, handleFn func(btrfstree.Item) bool) error { + if tree.RootErr != nil { + return tree.RootErr + } + + var node *btrfstree.Node + tree.Items.Range( + func(rbnode *containers.RBNode[oldRebuiltTreeValue]) bool { + if node == nil || node.Head.Addr != rbnode.Value.Node.LAddr { + tree.forrest.inner.ReleaseNode(node) + node = tree.forrest.readNode(rbnode.Value.Node) + } + return handleFn(node.BodyLeaf[rbnode.Value.Slot]) + }) + tree.forrest.inner.ReleaseNode(node) + + return tree.addErrs(func(btrfsprim.Key, uint32) int { return 0 }, nil) +} + // TreeSearchAll implements btrfstree.TreeOperator. func (bt *OldRebuiltForrest) TreeSearchAll(treeID btrfsprim.ObjID, searcher btrfstree.TreeSearcher) ([]btrfstree.Item, error) { tree := bt.RebuiltTree(bt.ctx, treeID) @@ -307,7 +328,7 @@ func (bt *OldRebuiltForrest) TreeSearchAll(treeID btrfsprim.ObjID, searcher btrf } var ret []btrfstree.Item - err := tree.treeSubrange(bt.ctx, 1, searcher, func(item btrfstree.Item) bool { + err := tree.TreeSubrange(bt.ctx, 1, searcher, func(item btrfstree.Item) bool { item.Body = item.Body.CloneItem() ret = append(ret, item) return true @@ -316,7 +337,8 @@ func (bt *OldRebuiltForrest) TreeSearchAll(treeID btrfsprim.ObjID, searcher btrf return ret, err } -func (tree oldRebuiltTree) treeSubrange(_ context.Context, min int, searcher btrfstree.TreeSearcher, handleFn func(btrfstree.Item) bool) error { +// TreeSubrange implements btrfstree.Tree. +func (tree oldRebuiltTree) TreeSubrange(_ context.Context, min int, searcher btrfstree.TreeSearcher, handleFn func(btrfstree.Item) bool) error { var node *btrfstree.Node var cnt int tree.Items.Subrange( @@ -355,10 +377,12 @@ func (bt *OldRebuiltForrest) TreeWalk(ctx context.Context, treeID btrfsprim.ObjI }) return } - tree.treeWalk(ctx, cbs) + tree.TreeWalk(ctx, cbs) } -func (tree oldRebuiltTree) treeWalk(ctx context.Context, cbs btrfstree.TreeWalkHandler) { +// TreeWalk implements btrfstree.Tree. It doesn't actually visit +// nodes or keypointers (just items). +func (tree oldRebuiltTree) TreeWalk(ctx context.Context, cbs btrfstree.TreeWalkHandler) { if cbs.Item == nil && cbs.BadItem == nil { return } @@ -439,7 +463,7 @@ func (tree oldRebuiltTree) TreeCheckOwner(ctx context.Context, failOpen bool, ow return nil //nolint:nilerr // fail open } } - parentIDItem, err := uuidTree.treeLookup(ctx, btrfsitem.UUIDToKey(tree.ParentUUID)) + parentIDItem, err := uuidTree.TreeLookup(ctx, btrfsitem.UUIDToKey(tree.ParentUUID)) if err != nil { if failOpen { return nil -- cgit v1.2.3-2-g168b From 933220af61abd26cb660febac15ed214479e2ba7 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Sat, 18 Mar 2023 00:49:01 -0400 Subject: btrfsutil: OldRebuiltForrest: Implement the new btrfstree.Forrest --- lib/btrfsutil/old_rebuilt_forrest.go | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) (limited to 'lib/btrfsutil') diff --git a/lib/btrfsutil/old_rebuilt_forrest.go b/lib/btrfsutil/old_rebuilt_forrest.go index e853be7..a462a60 100644 --- a/lib/btrfsutil/old_rebuilt_forrest.go +++ b/lib/btrfsutil/old_rebuilt_forrest.go @@ -96,7 +96,10 @@ type OldRebuiltForrest struct { trees map[btrfsprim.ObjID]oldRebuiltTree } -var _ btrfstree.TreeOperator = (*OldRebuiltForrest)(nil) +var ( + _ btrfstree.TreeOperator = (*OldRebuiltForrest)(nil) + _ btrfs.ReadableFS = (*OldRebuiltForrest)(nil) +) // NewOldRebuiltForrest wraps a *btrfs.FS to support looking up // information from broken trees. @@ -123,8 +126,18 @@ func NewOldRebuiltForrest(ctx context.Context, inner *btrfs.FS) *OldRebuiltForre } } -// RebuiltTree returns a handle for an individual tree. An error is -// indicated by the ret.RootErr member. +// ForrestLookup implements btrfstree.Forrest. +func (bt *OldRebuiltForrest) ForrestLookup(ctx context.Context, treeID btrfsprim.ObjID) (btrfstree.Tree, error) { + tree := bt.RebuiltTree(ctx, treeID) + if tree.RootErr != nil { + return nil, tree.RootErr + } + return tree, nil +} + +// RebuiltTree is a variant of ForrestLookup that returns a concrete +// type instead of an interface. An error is indicated by the +// ret.RootErr member. func (bt *OldRebuiltForrest) RebuiltTree(ctx context.Context, treeID btrfsprim.ObjID) oldRebuiltTree { if treeID == btrfsprim.ROOT_TREE_OBJECTID { bt.rootTreeMu.Lock() @@ -439,6 +452,11 @@ func (bt *OldRebuiltForrest) ReadAt(p []byte, off btrfsvol.LogicalAddr) (int, er return bt.inner.ReadAt(p, off) } +// Name implements btrfs.ReadableFS. +func (bt *OldRebuiltForrest) Name() string { + return bt.inner.Name() +} + // TreeCheckOwner implements btrfstree.Tree. func (tree oldRebuiltTree) TreeCheckOwner(ctx context.Context, failOpen bool, owner btrfsprim.ObjID, gen btrfsprim.Generation) error { var uuidTree oldRebuiltTree -- cgit v1.2.3-2-g168b From 87e107c3a5ed5793609ef8cc7c0f43209026902d Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Wed, 22 Mar 2023 10:22:43 -0400 Subject: btrfsutil: OldRebuiltForrest: Migrate to consume the new btree.Forrest API --- lib/btrfsutil/old_rebuilt_forrest.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/btrfsutil') diff --git a/lib/btrfsutil/old_rebuilt_forrest.go b/lib/btrfsutil/old_rebuilt_forrest.go index a462a60..ae63001 100644 --- a/lib/btrfsutil/old_rebuilt_forrest.go +++ b/lib/btrfsutil/old_rebuilt_forrest.go @@ -179,13 +179,13 @@ func (bt *OldRebuiltForrest) rawTreeWalk(ctx context.Context, treeID btrfsprim.O cacheEntry.RootErr = err return } - root, err := btrfstree.OldLookupTreeRoot(ctx, bt.TreeSearch, *sb, treeID) + root, err := btrfstree.LookupTreeRoot(ctx, bt, *sb, treeID) if err != nil { cacheEntry.RootErr = err return } tree := &btrfstree.RawTree{ - Forrest: btrfstree.TreeOperatorImpl{NodeSource: bt.inner}, + Forrest: btrfstree.RawForrest{NodeSource: bt.inner}, TreeRoot: *root, } -- cgit v1.2.3-2-g168b From f9fd200f70d5746e6e5b64e1c6e7ed2474081964 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Thu, 2 Mar 2023 16:02:42 -0700 Subject: btrfsutil, cmd: Migrate to the new btrfstree.Forrest API --- lib/btrfsutil/walk.go | 53 +++++++++++++++++++++------------------------------ 1 file changed, 22 insertions(+), 31 deletions(-) (limited to 'lib/btrfsutil') diff --git a/lib/btrfsutil/walk.go b/lib/btrfsutil/walk.go index b05218c..caa1e4c 100644 --- a/lib/btrfsutil/walk.go +++ b/lib/btrfsutil/walk.go @@ -8,35 +8,23 @@ import ( "context" "fmt" + "git.lukeshu.com/btrfs-progs-ng/lib/btrfs" "git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfsitem" "git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfsprim" "git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfstree" ) -type WalkError struct { - TreeName string - Err *btrfstree.TreeError -} - -func (e *WalkError) Unwrap() error { return e.Err } - -func (e *WalkError) Error() string { - return fmt.Sprintf("%v: %v", e.TreeName, e.Err) -} - type WalkAllTreesHandler struct { - Err func(*WalkError) - // Callbacks for entire trees PreTree func(name string, id btrfsprim.ObjID) + BadTree func(name string, id btrfsprim.ObjID, err error) + Tree btrfstree.TreeWalkHandler PostTree func(name string, id btrfsprim.ObjID) - // Callbacks for nodes or smaller - btrfstree.TreeWalkHandler } -// WalkAllTrees walks all trees in a *btrfs.FS. Rather than returning -// an error, it calls errCb each time an error is encountered. The -// error will always be of type WalkError. -func WalkAllTrees(ctx context.Context, fs btrfstree.TreeOperator, cbs WalkAllTreesHandler) { +// WalkAllTrees walks all trees in a btrfs.ReadableFS. Rather than +// returning an error, it calls the appropriate "BadXXX" callback +// (BadTree, BadNode, BadItem) each time an error is encountered. +func WalkAllTrees(ctx context.Context, fs btrfs.ReadableFS, cbs WalkAllTreesHandler) { var treeName string trees := []struct { @@ -60,8 +48,8 @@ func WalkAllTrees(ctx context.Context, fs btrfstree.TreeOperator, cbs WalkAllTre ID: btrfsprim.BLOCK_GROUP_TREE_OBJECTID, }, } - origItem := cbs.Item - cbs.Item = func(path btrfstree.Path, item btrfstree.Item) { + origItem := cbs.Tree.Item + cbs.Tree.Item = func(path btrfstree.Path, item btrfstree.Item) { if item.Key.ItemType == btrfsitem.ROOT_ITEM_KEY { trees = append(trees, struct { Name string @@ -78,19 +66,22 @@ func WalkAllTrees(ctx context.Context, fs btrfstree.TreeOperator, cbs WalkAllTre } for i := 0; i < len(trees); i++ { - tree := trees[i] - treeName = tree.Name + treeInfo := trees[i] + treeName = treeInfo.Name if cbs.PreTree != nil { - cbs.PreTree(treeName, tree.ID) + cbs.PreTree(treeName, treeInfo.ID) + } + tree, err := fs.ForrestLookup(ctx, treeInfo.ID) + switch { + case err != nil: + if cbs.BadTree != nil { + cbs.BadTree(treeName, treeInfo.ID, err) + } + default: + tree.TreeWalk(ctx, cbs.Tree) } - fs.TreeWalk( - ctx, - tree.ID, - func(err *btrfstree.TreeError) { cbs.Err(&WalkError{TreeName: treeName, Err: err}) }, - cbs.TreeWalkHandler, - ) if cbs.PostTree != nil { - cbs.PostTree(treeName, tree.ID) + cbs.PostTree(treeName, treeInfo.ID) } } } -- cgit v1.2.3-2-g168b From c7b6460ee9b3c07c13c973cbc8c8f690560fefc6 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Thu, 2 Mar 2023 16:02:42 -0700 Subject: tree-wide: Drop the old btrfstree.TreeOperator API --- lib/btrfsutil/old_rebuilt_forrest.go | 89 ++++++++---------------------------- 1 file changed, 20 insertions(+), 69 deletions(-) (limited to 'lib/btrfsutil') diff --git a/lib/btrfsutil/old_rebuilt_forrest.go b/lib/btrfsutil/old_rebuilt_forrest.go index ae63001..58333df 100644 --- a/lib/btrfsutil/old_rebuilt_forrest.go +++ b/lib/btrfsutil/old_rebuilt_forrest.go @@ -85,7 +85,6 @@ func newOldRebuiltTree() oldRebuiltTree { } type OldRebuiltForrest struct { - ctx context.Context //nolint:containedctx // don't have an option while keeping the same API inner *btrfs.FS // btrfsprim.ROOT_TREE_OBJECTID @@ -96,32 +95,27 @@ type OldRebuiltForrest struct { trees map[btrfsprim.ObjID]oldRebuiltTree } -var ( - _ btrfstree.TreeOperator = (*OldRebuiltForrest)(nil) - _ btrfs.ReadableFS = (*OldRebuiltForrest)(nil) -) +var _ btrfs.ReadableFS = (*OldRebuiltForrest)(nil) // NewOldRebuiltForrest wraps a *btrfs.FS to support looking up // information from broken trees. // -// Of the btrfstree.TreeOperator methods: +// Of the btrfstree.Tree methods: // -// - TreeWalk works on broken trees -// - TreeLookup relies on the tree being properly ordered (which a +// - TreeRange works on broken trees +// - TreeSubrange relies on the tree being properly ordered (which a // broken tree might not be). // - TreeSearch relies on the tree being properly ordered (which a // broken tree might not be). -// - TreeSearchAll relies on the tree being properly ordered (which a -// broken tree might not be), and a bad node may cause it to not -// return a truncated list of results. +// - TreeLookup relies on the tree being properly ordered (which a +// broken tree might not be). // -// NewOldRebuiltForrest attempts to remedy these deficiencies by using -// .TreeWalk to build an out-of-FS index of all of the items in the -// tree, and re-implements TreeLookup, TreeSearch, and TreeSearchAll +// NewOldRebuiltForrest attempts to remedy these deficiencies by +// building an out-of-FS index of all of the items in the tree, and +// re-implements TreeLookup, TreeSearch, TreeSubrange, and TreeRange // using that index. -func NewOldRebuiltForrest(ctx context.Context, inner *btrfs.FS) *OldRebuiltForrest { +func NewOldRebuiltForrest(inner *btrfs.FS) *OldRebuiltForrest { return &OldRebuiltForrest{ - ctx: ctx, inner: inner, } } @@ -251,8 +245,8 @@ func (tree oldRebuiltTree) addErrs(fn func(btrfsprim.Key, uint32) int, err error return errs } -func (bt *OldRebuiltForrest) readNode(nodeInfo nodeInfo) *btrfstree.Node { - node, err := bt.inner.AcquireNode(bt.ctx, nodeInfo.LAddr, btrfstree.NodeExpectations{ +func (bt *OldRebuiltForrest) readNode(ctx context.Context, nodeInfo nodeInfo) *btrfstree.Node { + node, err := bt.inner.AcquireNode(ctx, nodeInfo.LAddr, btrfstree.NodeExpectations{ LAddr: containers.OptionalValue(nodeInfo.LAddr), Level: containers.OptionalValue(nodeInfo.Level), Generation: containers.OptionalValue(nodeInfo.Generation), @@ -274,23 +268,13 @@ func (bt *OldRebuiltForrest) readNode(nodeInfo nodeInfo) *btrfstree.Node { return node } -// TreeLookup implements btrfstree.TreeOperator. -func (bt *OldRebuiltForrest) TreeLookup(treeID btrfsprim.ObjID, key btrfsprim.Key) (btrfstree.Item, error) { - return bt.RebuiltTree(bt.ctx, treeID).TreeLookup(bt.ctx, key) -} - // TreeLookup implements btrfstree.Tree. func (tree oldRebuiltTree) TreeLookup(ctx context.Context, key btrfsprim.Key) (btrfstree.Item, error) { return tree.TreeSearch(ctx, btrfstree.SearchExactKey(key)) } -// TreeSearch implements btrfstree.TreeOperator. -func (bt *OldRebuiltForrest) TreeSearch(treeID btrfsprim.ObjID, searcher btrfstree.TreeSearcher) (btrfstree.Item, error) { - return bt.RebuiltTree(bt.ctx, treeID).TreeSearch(bt.ctx, searcher) -} - // TreeSearch implements btrfstree.Tree. -func (tree oldRebuiltTree) TreeSearch(_ context.Context, searcher btrfstree.TreeSearcher) (btrfstree.Item, error) { +func (tree oldRebuiltTree) TreeSearch(ctx context.Context, searcher btrfstree.TreeSearcher) (btrfstree.Item, error) { if tree.RootErr != nil { return btrfstree.Item{}, tree.RootErr } @@ -302,7 +286,7 @@ func (tree oldRebuiltTree) TreeSearch(_ context.Context, searcher btrfstree.Tree return btrfstree.Item{}, fmt.Errorf("item with %s: %w", searcher, tree.addErrs(searcher.Search, btrfstree.ErrNoItem)) } - node := tree.forrest.readNode(indexItem.Value.Node) + node := tree.forrest.readNode(ctx, indexItem.Value.Node) defer tree.forrest.inner.ReleaseNode(node) item := node.BodyLeaf[indexItem.Value.Slot] @@ -314,7 +298,7 @@ func (tree oldRebuiltTree) TreeSearch(_ context.Context, searcher btrfstree.Tree } // TreeRange implements btrfstree.Tree. -func (tree oldRebuiltTree) TreeRange(_ context.Context, handleFn func(btrfstree.Item) bool) error { +func (tree oldRebuiltTree) TreeRange(ctx context.Context, handleFn func(btrfstree.Item) bool) error { if tree.RootErr != nil { return tree.RootErr } @@ -324,7 +308,7 @@ func (tree oldRebuiltTree) TreeRange(_ context.Context, handleFn func(btrfstree. func(rbnode *containers.RBNode[oldRebuiltTreeValue]) bool { if node == nil || node.Head.Addr != rbnode.Value.Node.LAddr { tree.forrest.inner.ReleaseNode(node) - node = tree.forrest.readNode(rbnode.Value.Node) + node = tree.forrest.readNode(ctx, rbnode.Value.Node) } return handleFn(node.BodyLeaf[rbnode.Value.Slot]) }) @@ -333,25 +317,8 @@ func (tree oldRebuiltTree) TreeRange(_ context.Context, handleFn func(btrfstree. return tree.addErrs(func(btrfsprim.Key, uint32) int { return 0 }, nil) } -// TreeSearchAll implements btrfstree.TreeOperator. -func (bt *OldRebuiltForrest) TreeSearchAll(treeID btrfsprim.ObjID, searcher btrfstree.TreeSearcher) ([]btrfstree.Item, error) { - tree := bt.RebuiltTree(bt.ctx, treeID) - if tree.RootErr != nil { - return nil, tree.RootErr - } - - var ret []btrfstree.Item - err := tree.TreeSubrange(bt.ctx, 1, searcher, func(item btrfstree.Item) bool { - item.Body = item.Body.CloneItem() - ret = append(ret, item) - return true - }) - - return ret, err -} - // TreeSubrange implements btrfstree.Tree. -func (tree oldRebuiltTree) TreeSubrange(_ context.Context, min int, searcher btrfstree.TreeSearcher, handleFn func(btrfstree.Item) bool) error { +func (tree oldRebuiltTree) TreeSubrange(ctx context.Context, min int, searcher btrfstree.TreeSearcher, handleFn func(btrfstree.Item) bool) error { var node *btrfstree.Node var cnt int tree.Items.Subrange( @@ -362,7 +329,7 @@ func (tree oldRebuiltTree) TreeSubrange(_ context.Context, min int, searcher btr cnt++ if node == nil || node.Head.Addr != rbNode.Value.Node.LAddr { tree.forrest.inner.ReleaseNode(node) - node = tree.forrest.readNode(rbNode.Value.Node) + node = tree.forrest.readNode(ctx, rbNode.Value.Node) } return handleFn(node.BodyLeaf[rbNode.Value.Slot]) }) @@ -379,20 +346,6 @@ func (tree oldRebuiltTree) TreeSubrange(_ context.Context, min int, searcher btr return err } -// TreeWalk implements btrfstree.TreeOperator. It doesn't actually -// visit nodes or keypointers (just items). -func (bt *OldRebuiltForrest) TreeWalk(ctx context.Context, treeID btrfsprim.ObjID, errHandle func(*btrfstree.TreeError), cbs btrfstree.TreeWalkHandler) { - tree := bt.RebuiltTree(ctx, treeID) - if tree.RootErr != nil { - errHandle(&btrfstree.TreeError{ - Path: btrfstree.Path{btrfstree.PathRoot{TreeID: treeID}}, - Err: tree.RootErr, - }) - return - } - tree.TreeWalk(ctx, cbs) -} - // TreeWalk implements btrfstree.Tree. It doesn't actually visit // nodes or keypointers (just items). func (tree oldRebuiltTree) TreeWalk(ctx context.Context, cbs btrfstree.TreeWalkHandler) { @@ -404,12 +357,10 @@ func (tree oldRebuiltTree) TreeWalk(ctx context.Context, cbs btrfstree.TreeWalkH if ctx.Err() != nil { return false } - if tree.forrest.ctx.Err() != nil { - return false - } + if node == nil || node.Head.Addr != indexItem.Value.Node.LAddr { tree.forrest.inner.ReleaseNode(node) - node = tree.forrest.readNode(indexItem.Value.Node) + node = tree.forrest.readNode(ctx, indexItem.Value.Node) } item := node.BodyLeaf[indexItem.Value.Slot] -- cgit v1.2.3-2-g168b