From 8c7c2a3ce0d164ed3081b2a479270d8ccab9deee Mon Sep 17 00:00:00 2001 From: Nils Wireklint Date: Thu, 8 Feb 2024 12:18:04 +0100 Subject: [PATCH 1/3] Refactor OnScope to Absolute and Relative --- pkg/filesystem/path/absolute_scope_walker.go | 9 ++--- pkg/filesystem/path/builder.go | 22 +++++++----- pkg/filesystem/path/builder_test.go | 6 ++-- pkg/filesystem/path/component_walker.go | 17 +++++----- .../path/loop_detecting_scope_walker.go | 15 ++++++-- .../path/loop_detecting_scope_walker_test.go | 6 ++-- pkg/filesystem/path/relative_scope_walker.go | 9 ++--- pkg/filesystem/path/resolve.go | 8 ++++- pkg/filesystem/path/resolve_test.go | 34 +++++++++---------- pkg/filesystem/path/scope_walker.go | 29 +++++++++------- .../path/virtual_root_scope_walker_factory.go | 22 ++++++------ .../virtual_root_scope_walker_factory_test.go | 16 ++++----- pkg/filesystem/path/void_scope_walker.go | 6 +++- 13 files changed, 115 insertions(+), 84 deletions(-) diff --git a/pkg/filesystem/path/absolute_scope_walker.go b/pkg/filesystem/path/absolute_scope_walker.go index 3e48ad95..ef88131f 100644 --- a/pkg/filesystem/path/absolute_scope_walker.go +++ b/pkg/filesystem/path/absolute_scope_walker.go @@ -17,9 +17,10 @@ func NewAbsoluteScopeWalker(componentWalker ComponentWalker) ScopeWalker { } } -func (pw *absoluteScopeWalker) OnScope(absolute bool) (ComponentWalker, error) { - if !absolute { - return nil, status.Error(codes.InvalidArgument, "Path is relative, while an absolute path was expected") - } +func (pw *absoluteScopeWalker) OnRelative() (ComponentWalker, error) { + return nil, status.Error(codes.InvalidArgument, "Path is relative, while an absolute path was expected") +} + +func (pw *absoluteScopeWalker) OnAbsolute() (ComponentWalker, error) { return pw.componentWalker, nil } diff --git a/pkg/filesystem/path/builder.go b/pkg/filesystem/path/builder.go index 90a4159f..fd0ef5e1 100644 --- a/pkg/filesystem/path/builder.go +++ b/pkg/filesystem/path/builder.go @@ -115,17 +115,23 @@ type buildingScopeWalker struct { b *Builder } -func (w *buildingScopeWalker) OnScope(absolute bool) (ComponentWalker, error) { - componentWalker, err := w.base.OnScope(absolute) +func (w *buildingScopeWalker) OnAbsolute() (ComponentWalker, error) { + componentWalker, err := w.base.OnAbsolute() if err != nil { return nil, err } - if absolute { - *w.b = Builder{ - absolute: true, - components: w.b.components[:0], - suffix: "/", - } + *w.b = Builder{ + absolute: true, + components: w.b.components[:0], + suffix: "/", + } + return w.b.getComponentWalker(componentWalker), nil +} + +func (w *buildingScopeWalker) OnRelative() (ComponentWalker, error) { + componentWalker, err := w.base.OnRelative() + if err != nil { + return nil, err } return w.b.getComponentWalker(componentWalker), nil } diff --git a/pkg/filesystem/path/builder_test.go b/pkg/filesystem/path/builder_test.go index bfa49543..402c6efc 100644 --- a/pkg/filesystem/path/builder_test.go +++ b/pkg/filesystem/path/builder_test.go @@ -89,7 +89,7 @@ func TestBuilder(t *testing.T) { t.Run("Reversible1", func(t *testing.T) { scopeWalker := mock.NewMockScopeWalker(ctrl) componentWalker1 := mock.NewMockComponentWalker(ctrl) - scopeWalker.EXPECT().OnScope(false).Return(componentWalker1, nil) + scopeWalker.EXPECT().OnRelative().Return(componentWalker1, nil) componentWalker2 := mock.NewMockComponentWalker(ctrl) componentWalker1.EXPECT().OnDirectory(path.MustNewComponent("hello")). Return(path.GotDirectory{Child: componentWalker2, IsReversible: true}, nil) @@ -106,7 +106,7 @@ func TestBuilder(t *testing.T) { t.Run("Reversible2", func(t *testing.T) { scopeWalker := mock.NewMockScopeWalker(ctrl) componentWalker1 := mock.NewMockComponentWalker(ctrl) - scopeWalker.EXPECT().OnScope(false).Return(componentWalker1, nil) + scopeWalker.EXPECT().OnRelative().Return(componentWalker1, nil) componentWalker2 := mock.NewMockComponentWalker(ctrl) componentWalker1.EXPECT().OnUp().Return(componentWalker2, nil) componentWalker3 := mock.NewMockComponentWalker(ctrl) @@ -127,7 +127,7 @@ func TestBuilder(t *testing.T) { t.Run("Reversible3", func(t *testing.T) { scopeWalker := mock.NewMockScopeWalker(ctrl) componentWalker1 := mock.NewMockComponentWalker(ctrl) - scopeWalker.EXPECT().OnScope(true).Return(componentWalker1, nil) + scopeWalker.EXPECT().OnAbsolute().Return(componentWalker1, nil) componentWalker2 := mock.NewMockComponentWalker(ctrl) componentWalker1.EXPECT().OnDirectory(path.MustNewComponent("hello")). Return(path.GotDirectory{Child: componentWalker2, IsReversible: false}, nil) diff --git a/pkg/filesystem/path/component_walker.go b/pkg/filesystem/path/component_walker.go index 54792ee1..2652c429 100644 --- a/pkg/filesystem/path/component_walker.go +++ b/pkg/filesystem/path/component_walker.go @@ -49,17 +49,16 @@ type ComponentWalker interface { // OnDirectory is called for every pathname component that must // resolve to a directory or a symbolic link to a directory. // - // If the pathname component refers to a directory, this - // function will return a GotDirectory containing a new - // ComponentWalker against which successive pathname components - // can be resolved. + // If the pathname component refers to a directory, this function + // will return a GotDirectory containing a new ComponentWalker + // against which successive pathname components can be resolved. // // If the pathname component refers to a symbolic link, this - // function will return a GotSymlink containing a ScopeWalker, - // which can be used to perform expansion of the symbolic link. - // The Resolve() function will call into OnScope() to signal - // whether resolution should continue at the root directory or - // at the directory that contained the symbolic link. + // function will return a GotSymlink containing a ScopeWalker, which + // can be used to perform expansion of the symbolic link. The + // Resolve() function will call into OnAbsolute() or OnRealtive() to + // signal whether resolution should continue at the root directory + // or at the directory that contained the symbolic link. OnDirectory(name Component) (GotDirectoryOrSymlink, error) // OnTerminal is called for the potentially last pathname diff --git a/pkg/filesystem/path/loop_detecting_scope_walker.go b/pkg/filesystem/path/loop_detecting_scope_walker.go index fe69cfb6..861e6d78 100644 --- a/pkg/filesystem/path/loop_detecting_scope_walker.go +++ b/pkg/filesystem/path/loop_detecting_scope_walker.go @@ -23,8 +23,19 @@ func NewLoopDetectingScopeWalker(base ScopeWalker) ScopeWalker { } } -func (w *loopDetectingScopeWalker) OnScope(isAbsolute bool) (ComponentWalker, error) { - componentWalker, err := w.base.OnScope(isAbsolute) +func (w *loopDetectingScopeWalker) OnAbsolute() (ComponentWalker, error) { + componentWalker, err := w.base.OnAbsolute() + if err != nil { + return nil, err + } + return &loopDetectingComponentWalker{ + base: componentWalker, + symlinksLeft: w.symlinksLeft, + }, nil +} + +func (w *loopDetectingScopeWalker) OnRelative() (ComponentWalker, error) { + componentWalker, err := w.base.OnRelative() if err != nil { return nil, err } diff --git a/pkg/filesystem/path/loop_detecting_scope_walker_test.go b/pkg/filesystem/path/loop_detecting_scope_walker_test.go index 2fd5a2d1..efb3d767 100644 --- a/pkg/filesystem/path/loop_detecting_scope_walker_test.go +++ b/pkg/filesystem/path/loop_detecting_scope_walker_test.go @@ -20,7 +20,7 @@ func TestLoopDetectingScopeWalker(t *testing.T) { // finite number of expansions before failing. scopeWalker := mock.NewMockScopeWalker(ctrl) componentWalker := mock.NewMockComponentWalker(ctrl) - scopeWalker.EXPECT().OnScope(false).Return(componentWalker, nil).Times(41) + scopeWalker.EXPECT().OnRelative().Return(componentWalker, nil).Times(41) componentWalker.EXPECT().OnTerminal(path.MustNewComponent("foo")). Return(&path.GotSymlink{Parent: scopeWalker, Target: "foo"}, nil). Times(41) @@ -35,12 +35,12 @@ func TestLoopDetectingScopeWalker(t *testing.T) { // Simple case where a symbolic link is not self-referential. scopeWalker1 := mock.NewMockScopeWalker(ctrl) componentWalker1 := mock.NewMockComponentWalker(ctrl) - scopeWalker1.EXPECT().OnScope(true).Return(componentWalker1, nil) + scopeWalker1.EXPECT().OnAbsolute().Return(componentWalker1, nil) scopeWalker2 := mock.NewMockScopeWalker(ctrl) componentWalker1.EXPECT().OnTerminal(path.MustNewComponent("tmp")). Return(&path.GotSymlink{Parent: scopeWalker2, Target: "private/tmp"}, nil) componentWalker2 := mock.NewMockComponentWalker(ctrl) - scopeWalker2.EXPECT().OnScope(false).Return(componentWalker2, nil) + scopeWalker2.EXPECT().OnRelative().Return(componentWalker2, nil) componentWalker3 := mock.NewMockComponentWalker(ctrl) componentWalker2.EXPECT().OnDirectory(path.MustNewComponent("private")). Return(path.GotDirectory{Child: componentWalker3, IsReversible: true}, nil) diff --git a/pkg/filesystem/path/relative_scope_walker.go b/pkg/filesystem/path/relative_scope_walker.go index 59af8a02..36295cbf 100644 --- a/pkg/filesystem/path/relative_scope_walker.go +++ b/pkg/filesystem/path/relative_scope_walker.go @@ -17,9 +17,10 @@ func NewRelativeScopeWalker(componentWalker ComponentWalker) ScopeWalker { } } -func (pw *relativeScopeWalker) OnScope(absolute bool) (ComponentWalker, error) { - if absolute { - return nil, status.Error(codes.InvalidArgument, "Path is absolute, while a relative path was expected") - } +func (pw *relativeScopeWalker) OnAbsolute() (ComponentWalker, error) { + return nil, status.Error(codes.InvalidArgument, "Path is absolute, while a relative path was expected") +} + +func (pw *relativeScopeWalker) OnRelative() (ComponentWalker, error) { return pw.componentWalker, nil } diff --git a/pkg/filesystem/path/resolve.go b/pkg/filesystem/path/resolve.go index 80781574..a83ccd70 100644 --- a/pkg/filesystem/path/resolve.go +++ b/pkg/filesystem/path/resolve.go @@ -42,7 +42,13 @@ func (rs *resolverState) push(scopeWalker ScopeWalker, path string) error { // Push the path without any leading slashes onto the stack, so // that its components may be processed. Apply them against the // right directory. - componentWalker, err := scopeWalker.OnScope(absolute) + var componentWalker ComponentWalker + var err error + if absolute { + componentWalker, err = scopeWalker.OnAbsolute() + } else { + componentWalker, err = scopeWalker.OnRelative() + } if err != nil { return err } diff --git a/pkg/filesystem/path/resolve_test.go b/pkg/filesystem/path/resolve_test.go index 23ca21fe..70aac1ba 100644 --- a/pkg/filesystem/path/resolve_test.go +++ b/pkg/filesystem/path/resolve_test.go @@ -27,7 +27,7 @@ func TestResolve(t *testing.T) { t.Run("Empty", func(t *testing.T) { scopeWalker := mock.NewMockScopeWalker(ctrl) componentWalker := mock.NewMockComponentWalker(ctrl) - scopeWalker.EXPECT().OnScope(false).Return(componentWalker, nil) + scopeWalker.EXPECT().OnRelative().Return(componentWalker, nil) require.NoError(t, path.Resolve("", scopeWalker)) }) @@ -35,7 +35,7 @@ func TestResolve(t *testing.T) { t.Run("Dot", func(t *testing.T) { scopeWalker := mock.NewMockScopeWalker(ctrl) componentWalker := mock.NewMockComponentWalker(ctrl) - scopeWalker.EXPECT().OnScope(false).Return(componentWalker, nil) + scopeWalker.EXPECT().OnRelative().Return(componentWalker, nil) require.NoError(t, path.Resolve(".", scopeWalker)) }) @@ -43,7 +43,7 @@ func TestResolve(t *testing.T) { t.Run("SingleFileRelative", func(t *testing.T) { scopeWalker := mock.NewMockScopeWalker(ctrl) componentWalker := mock.NewMockComponentWalker(ctrl) - scopeWalker.EXPECT().OnScope(false).Return(componentWalker, nil) + scopeWalker.EXPECT().OnRelative().Return(componentWalker, nil) componentWalker.EXPECT().OnTerminal(path.MustNewComponent("hello")) require.NoError(t, path.Resolve("hello", scopeWalker)) @@ -52,7 +52,7 @@ func TestResolve(t *testing.T) { t.Run("SingleFileAbsolute", func(t *testing.T) { scopeWalker := mock.NewMockScopeWalker(ctrl) componentWalker := mock.NewMockComponentWalker(ctrl) - scopeWalker.EXPECT().OnScope(true).Return(componentWalker, nil) + scopeWalker.EXPECT().OnAbsolute().Return(componentWalker, nil) componentWalker.EXPECT().OnTerminal(path.MustNewComponent("hello")) require.NoError(t, path.Resolve("/hello", scopeWalker)) @@ -61,7 +61,7 @@ func TestResolve(t *testing.T) { t.Run("SingleDirectoryWithSlash", func(t *testing.T) { scopeWalker := mock.NewMockScopeWalker(ctrl) componentWalker1 := mock.NewMockComponentWalker(ctrl) - scopeWalker.EXPECT().OnScope(false).Return(componentWalker1, nil) + scopeWalker.EXPECT().OnRelative().Return(componentWalker1, nil) componentWalker2 := mock.NewMockComponentWalker(ctrl) componentWalker1.EXPECT().OnDirectory(path.MustNewComponent("hello")). Return(path.GotDirectory{Child: componentWalker2}, nil) @@ -72,7 +72,7 @@ func TestResolve(t *testing.T) { t.Run("SingleDirectoryWithSlashDot", func(t *testing.T) { scopeWalker := mock.NewMockScopeWalker(ctrl) componentWalker1 := mock.NewMockComponentWalker(ctrl) - scopeWalker.EXPECT().OnScope(false).Return(componentWalker1, nil) + scopeWalker.EXPECT().OnRelative().Return(componentWalker1, nil) componentWalker2 := mock.NewMockComponentWalker(ctrl) componentWalker1.EXPECT().OnDirectory(path.MustNewComponent("hello")). Return(path.GotDirectory{Child: componentWalker2}, nil) @@ -83,7 +83,7 @@ func TestResolve(t *testing.T) { t.Run("MultipleComponents", func(t *testing.T) { scopeWalker := mock.NewMockScopeWalker(ctrl) componentWalker1 := mock.NewMockComponentWalker(ctrl) - scopeWalker.EXPECT().OnScope(false).Return(componentWalker1, nil) + scopeWalker.EXPECT().OnRelative().Return(componentWalker1, nil) componentWalker2 := mock.NewMockComponentWalker(ctrl) componentWalker1.EXPECT().OnDirectory(path.MustNewComponent("a")). Return(path.GotDirectory{Child: componentWalker2}, nil) @@ -105,12 +105,12 @@ func TestResolve(t *testing.T) { t.Run("SymlinkWithoutSlash", func(t *testing.T) { scopeWalker1 := mock.NewMockScopeWalker(ctrl) componentWalker1 := mock.NewMockComponentWalker(ctrl) - scopeWalker1.EXPECT().OnScope(false).Return(componentWalker1, nil) + scopeWalker1.EXPECT().OnRelative().Return(componentWalker1, nil) scopeWalker2 := mock.NewMockScopeWalker(ctrl) componentWalker1.EXPECT().OnTerminal(path.MustNewComponent("a")). Return(&path.GotSymlink{Parent: scopeWalker2, Target: "b"}, nil) componentWalker2 := mock.NewMockComponentWalker(ctrl) - scopeWalker2.EXPECT().OnScope(false).Return(componentWalker2, nil) + scopeWalker2.EXPECT().OnRelative().Return(componentWalker2, nil) componentWalker2.EXPECT().OnTerminal(path.MustNewComponent("b")) require.NoError(t, path.Resolve("a", scopeWalker1)) @@ -119,12 +119,12 @@ func TestResolve(t *testing.T) { t.Run("SymlinkWithSlashInSymlink", func(t *testing.T) { scopeWalker1 := mock.NewMockScopeWalker(ctrl) componentWalker1 := mock.NewMockComponentWalker(ctrl) - scopeWalker1.EXPECT().OnScope(false).Return(componentWalker1, nil) + scopeWalker1.EXPECT().OnRelative().Return(componentWalker1, nil) scopeWalker2 := mock.NewMockScopeWalker(ctrl) componentWalker1.EXPECT().OnTerminal(path.MustNewComponent("a")). Return(&path.GotSymlink{Parent: scopeWalker2, Target: "b/"}, nil) componentWalker2 := mock.NewMockComponentWalker(ctrl) - scopeWalker2.EXPECT().OnScope(false).Return(componentWalker2, nil) + scopeWalker2.EXPECT().OnRelative().Return(componentWalker2, nil) componentWalker3 := mock.NewMockComponentWalker(ctrl) componentWalker2.EXPECT().OnDirectory(path.MustNewComponent("b")). Return(path.GotDirectory{Child: componentWalker3}, nil) @@ -135,12 +135,12 @@ func TestResolve(t *testing.T) { t.Run("SymlinkWithSlashInPath", func(t *testing.T) { scopeWalker1 := mock.NewMockScopeWalker(ctrl) componentWalker1 := mock.NewMockComponentWalker(ctrl) - scopeWalker1.EXPECT().OnScope(false).Return(componentWalker1, nil) + scopeWalker1.EXPECT().OnRelative().Return(componentWalker1, nil) scopeWalker2 := mock.NewMockScopeWalker(ctrl) componentWalker1.EXPECT().OnDirectory(path.MustNewComponent("a")). Return(path.GotSymlink{Parent: scopeWalker2, Target: "b"}, nil) componentWalker2 := mock.NewMockComponentWalker(ctrl) - scopeWalker2.EXPECT().OnScope(false).Return(componentWalker2, nil) + scopeWalker2.EXPECT().OnRelative().Return(componentWalker2, nil) componentWalker3 := mock.NewMockComponentWalker(ctrl) componentWalker2.EXPECT().OnDirectory(path.MustNewComponent("b")). Return(path.GotDirectory{Child: componentWalker3}, nil) @@ -151,22 +151,22 @@ func TestResolve(t *testing.T) { t.Run("SymlinkInSymlinkInSymlink", func(t *testing.T) { scopeWalker1 := mock.NewMockScopeWalker(ctrl) componentWalker1 := mock.NewMockComponentWalker(ctrl) - scopeWalker1.EXPECT().OnScope(false).Return(componentWalker1, nil) + scopeWalker1.EXPECT().OnRelative().Return(componentWalker1, nil) scopeWalker2 := mock.NewMockScopeWalker(ctrl) componentWalker1.EXPECT().OnTerminal(path.MustNewComponent("a")). Return(&path.GotSymlink{Parent: scopeWalker2, Target: "b/z"}, nil) componentWalker2 := mock.NewMockComponentWalker(ctrl) - scopeWalker2.EXPECT().OnScope(false).Return(componentWalker2, nil) + scopeWalker2.EXPECT().OnRelative().Return(componentWalker2, nil) scopeWalker3 := mock.NewMockScopeWalker(ctrl) componentWalker2.EXPECT().OnDirectory(path.MustNewComponent("b")). Return(path.GotSymlink{Parent: scopeWalker3, Target: "c/y"}, nil) componentWalker3 := mock.NewMockComponentWalker(ctrl) - scopeWalker3.EXPECT().OnScope(false).Return(componentWalker3, nil) + scopeWalker3.EXPECT().OnRelative().Return(componentWalker3, nil) scopeWalker4 := mock.NewMockScopeWalker(ctrl) componentWalker3.EXPECT().OnDirectory(path.MustNewComponent("c")). Return(path.GotSymlink{Parent: scopeWalker4, Target: "x"}, nil) componentWalker4 := mock.NewMockComponentWalker(ctrl) - scopeWalker4.EXPECT().OnScope(false).Return(componentWalker4, nil) + scopeWalker4.EXPECT().OnRelative().Return(componentWalker4, nil) componentWalker5 := mock.NewMockComponentWalker(ctrl) componentWalker4.EXPECT().OnDirectory(path.MustNewComponent("x")). Return(path.GotDirectory{Child: componentWalker5}, nil) diff --git a/pkg/filesystem/path/scope_walker.go b/pkg/filesystem/path/scope_walker.go index 622605e4..58eb441f 100644 --- a/pkg/filesystem/path/scope_walker.go +++ b/pkg/filesystem/path/scope_walker.go @@ -4,22 +4,25 @@ package path // implementation can use it to capture the path that is resolved. // ScopeWalker is called into once for every path that is processed. type ScopeWalker interface { - // OnScope is called right before processing the first component - // in the path (if any). The absolute argument indicates whether - // the provided path is absolute (i.e., starting with one or - // more slashes). + // One of these functions is called right before processing the + // first component in the path (if any). Based on the + // characteristics of the path. Absolute paths are handled through + // OnAbsolute(), and relative paths require OnRelative(). // - // This function can be used by the implementation to determine + // These functions can be used by the implementation to determine // whether path resolution needs to be relative to the current // directory (e.g., working directory or parent directory of the // previous symlink encountered) or the root directory. // - // For every instance of ScopeWalker, OnScope() may be called at - // most once. Resolve() will always call into OnScope() for - // every ScopeWalker presented, though decorators such as - // VirtualRootScopeWalkerFactory may only call it when the path - // is known to be valid. Absence of calls to OnScope() are used - // to indicate that the provided path does not resolve to a - // location inside the file system. - OnScope(absolute bool) (ComponentWalker, error) + // For every instance of ScopeWalker, one of OnAbsolute() or + // OnRelative() may be called at most once. Resolve() will always + // call into one of the interface functions for every ScopeWalker + // presented, though decorators such as + // VirtualRootScopeWalkerFactory may only call it when the path is + // known to be valid. Absence of calls to OnAbsolute() or + // OnRelative() are used to indicate that the provided path does not + // resolve to a location inside the file + // system. + OnAbsolute() (ComponentWalker, error) + OnRelative() (ComponentWalker, error) } diff --git a/pkg/filesystem/path/virtual_root_scope_walker_factory.go b/pkg/filesystem/path/virtual_root_scope_walker_factory.go index 253a438b..b573f28a 100644 --- a/pkg/filesystem/path/virtual_root_scope_walker_factory.go +++ b/pkg/filesystem/path/virtual_root_scope_walker_factory.go @@ -87,11 +87,11 @@ func (cw *virtualRootNodeCreator) OnUp() (ComponentWalker, error) { // // The underlying implementation of ScopeWalker has the ability to // detect whether the resolved path lies inside or outside of the nested -// directory hierarchy by monitoring whether ScopeWalker.OnScope() has -// been called. If this function is called on the wrapped ScopeWalker, -// but not called on the underlying instance (either initially or after -// returning a GotSymlink response), the resolved path lies outside the -// nested root directory. +// directory hierarchy by monitoring whether a ScopeWalker interface +// method has been called. If this function is called on the wrapped +// ScopeWalker, but not called on the underlying instance (either +// initially or after returning a GotSymlink response), the resolved +// path lies outside the nested root directory. type VirtualRootScopeWalkerFactory struct { rootNode namelessVirtualRootNode } @@ -184,7 +184,7 @@ func (w *virtualRootScopeWalker) getComponentWalker(n *namelessVirtualRootNode) } // We've reached the underlying root directory. - root, err := w.base.OnScope(true) + root, err := w.base.OnAbsolute() if err != nil { return nil, err } @@ -194,15 +194,15 @@ func (w *virtualRootScopeWalker) getComponentWalker(n *namelessVirtualRootNode) }, nil } -func (w *virtualRootScopeWalker) OnScope(absolute bool) (ComponentWalker, error) { - if absolute { - return w.getComponentWalker(w.rootNode) - } +func (w *virtualRootScopeWalker) OnAbsolute() (ComponentWalker, error) { + return w.getComponentWalker(w.rootNode) +} +func (w *virtualRootScopeWalker) OnRelative() (ComponentWalker, error) { // Attempted to resolve a relative path. There is no need to // rewrite any paths. Do wrap the ComponentWalker to ensure // future symlinks respect the virtual root. - base, err := w.base.OnScope(false) + base, err := w.base.OnRelative() if err != nil { return nil, err } diff --git a/pkg/filesystem/path/virtual_root_scope_walker_factory_test.go b/pkg/filesystem/path/virtual_root_scope_walker_factory_test.go index 8e4e0b3b..3d5b0e65 100644 --- a/pkg/filesystem/path/virtual_root_scope_walker_factory_test.go +++ b/pkg/filesystem/path/virtual_root_scope_walker_factory_test.go @@ -67,7 +67,7 @@ func TestVirtualRootScopeWalkerFactoryCreationSuccess(t *testing.T) { // virtual root directory. scopeWalker := mock.NewMockScopeWalker(ctrl) componentWalker := mock.NewMockComponentWalker(ctrl) - scopeWalker.EXPECT().OnScope(false).Return(componentWalker, nil) + scopeWalker.EXPECT().OnRelative().Return(componentWalker, nil) componentWalker.EXPECT().OnTerminal(path.MustNewComponent("hello")) require.NoError(t, path.Resolve("hello", factory.New(scopeWalker))) @@ -77,7 +77,7 @@ func TestVirtualRootScopeWalkerFactoryCreationSuccess(t *testing.T) { // Absolute paths should have their prefix stripped. scopeWalker := mock.NewMockScopeWalker(ctrl) componentWalker := mock.NewMockComponentWalker(ctrl) - scopeWalker.EXPECT().OnScope(true).Return(componentWalker, nil) + scopeWalker.EXPECT().OnAbsolute().Return(componentWalker, nil) componentWalker.EXPECT().OnTerminal(path.MustNewComponent("hello")) require.NoError(t, path.Resolve("/root/hello", factory.New(scopeWalker))) @@ -88,7 +88,7 @@ func TestVirtualRootScopeWalkerFactoryCreationSuccess(t *testing.T) { // up in the virtual root directory. scopeWalker := mock.NewMockScopeWalker(ctrl) componentWalker1 := mock.NewMockComponentWalker(ctrl) - scopeWalker.EXPECT().OnScope(true).Return(componentWalker1, nil) + scopeWalker.EXPECT().OnAbsolute().Return(componentWalker1, nil) componentWalker2 := mock.NewMockComponentWalker(ctrl) componentWalker1.EXPECT().OnDirectory(path.MustNewComponent("target")). Return(path.GotDirectory{ @@ -115,7 +115,7 @@ func TestVirtualRootScopeWalkerFactoryCreationSuccess(t *testing.T) { // completely unaffected by the virtual root directory. scopeWalker1 := mock.NewMockScopeWalker(ctrl) componentWalker1 := mock.NewMockComponentWalker(ctrl) - scopeWalker1.EXPECT().OnScope(false).Return(componentWalker1, nil) + scopeWalker1.EXPECT().OnRelative().Return(componentWalker1, nil) scopeWalker2 := mock.NewMockScopeWalker(ctrl) componentWalker1.EXPECT().OnTerminal(path.MustNewComponent("a")). Return(&path.GotSymlink{ @@ -123,7 +123,7 @@ func TestVirtualRootScopeWalkerFactoryCreationSuccess(t *testing.T) { Target: "b", }, nil) componentWalker2 := mock.NewMockComponentWalker(ctrl) - scopeWalker2.EXPECT().OnScope(false).Return(componentWalker2, nil) + scopeWalker2.EXPECT().OnRelative().Return(componentWalker2, nil) componentWalker2.EXPECT().OnTerminal(path.MustNewComponent("b")) require.NoError(t, path.Resolve("a", factory.New(scopeWalker1))) @@ -134,7 +134,7 @@ func TestVirtualRootScopeWalkerFactoryCreationSuccess(t *testing.T) { // have their prefix stripped. scopeWalker1 := mock.NewMockScopeWalker(ctrl) componentWalker1 := mock.NewMockComponentWalker(ctrl) - scopeWalker1.EXPECT().OnScope(false).Return(componentWalker1, nil) + scopeWalker1.EXPECT().OnRelative().Return(componentWalker1, nil) scopeWalker2 := mock.NewMockScopeWalker(ctrl) componentWalker1.EXPECT().OnTerminal(path.MustNewComponent("a")). Return(&path.GotSymlink{ @@ -142,7 +142,7 @@ func TestVirtualRootScopeWalkerFactoryCreationSuccess(t *testing.T) { Target: "/root/b", }, nil) componentWalker2 := mock.NewMockComponentWalker(ctrl) - scopeWalker2.EXPECT().OnScope(true).Return(componentWalker2, nil) + scopeWalker2.EXPECT().OnAbsolute().Return(componentWalker2, nil) componentWalker2.EXPECT().OnTerminal(path.MustNewComponent("b")) require.NoError(t, path.Resolve("a", factory.New(scopeWalker1))) @@ -154,7 +154,7 @@ func TestVirtualRootScopeWalkerFactoryCreationSuccess(t *testing.T) { // generate any calls against the successive ScopeWalker. scopeWalker1 := mock.NewMockScopeWalker(ctrl) componentWalker := mock.NewMockComponentWalker(ctrl) - scopeWalker1.EXPECT().OnScope(false).Return(componentWalker, nil) + scopeWalker1.EXPECT().OnRelative().Return(componentWalker, nil) scopeWalker2 := mock.NewMockScopeWalker(ctrl) componentWalker.EXPECT().OnTerminal(path.MustNewComponent("a")). Return(&path.GotSymlink{ diff --git a/pkg/filesystem/path/void_scope_walker.go b/pkg/filesystem/path/void_scope_walker.go index 1cea8ce7..9216fce1 100644 --- a/pkg/filesystem/path/void_scope_walker.go +++ b/pkg/filesystem/path/void_scope_walker.go @@ -2,7 +2,11 @@ package path type voidScopeWalker struct{} -func (w voidScopeWalker) OnScope(absolute bool) (ComponentWalker, error) { +func (w voidScopeWalker) OnAbsolute() (ComponentWalker, error) { + return VoidComponentWalker, nil +} + +func (w voidScopeWalker) OnRelative() (ComponentWalker, error) { return VoidComponentWalker, nil } From c16113d916c90d4c8c246373ffc945e1e5e1899f Mon Sep 17 00:00:00 2001 From: Nils Wireklint Date: Fri, 9 Feb 2024 12:55:52 +0100 Subject: [PATCH 2/3] Refactor to resolve with path.Parser This prepares for platform specific paths. --- pkg/filesystem/path/BUILD.bazel | 2 + .../path/absolute_scope_walker_test.go | 4 +- pkg/filesystem/path/builder_test.go | 12 +- pkg/filesystem/path/component_walker.go | 2 +- .../path/loop_detecting_scope_walker_test.go | 4 +- pkg/filesystem/path/parser.go | 11 ++ .../path/relative_scope_walker_test.go | 4 +- pkg/filesystem/path/resolve.go | 147 +++++------------- pkg/filesystem/path/resolve_test.go | 28 ++-- pkg/filesystem/path/unix_parser.go | 128 +++++++++++++++ .../path/virtual_root_scope_walker_factory.go | 27 +++- .../virtual_root_scope_walker_factory_test.go | 16 +- 12 files changed, 234 insertions(+), 151 deletions(-) create mode 100644 pkg/filesystem/path/parser.go create mode 100644 pkg/filesystem/path/unix_parser.go diff --git a/pkg/filesystem/path/BUILD.bazel b/pkg/filesystem/path/BUILD.bazel index f6088519..a4df71c6 100644 --- a/pkg/filesystem/path/BUILD.bazel +++ b/pkg/filesystem/path/BUILD.bazel @@ -9,10 +9,12 @@ go_library( "component_walker.go", "components_list.go", "loop_detecting_scope_walker.go", + "parser.go", "relative_scope_walker.go", "resolve.go", "scope_walker.go", "trace.go", + "unix_parser.go", "virtual_root_scope_walker_factory.go", "void_component_walker.go", "void_scope_walker.go", diff --git a/pkg/filesystem/path/absolute_scope_walker_test.go b/pkg/filesystem/path/absolute_scope_walker_test.go index 93ca512a..d1583774 100644 --- a/pkg/filesystem/path/absolute_scope_walker_test.go +++ b/pkg/filesystem/path/absolute_scope_walker_test.go @@ -19,7 +19,7 @@ func TestAbsoluteScopeWalker(t *testing.T) { componentWalker := mock.NewMockComponentWalker(ctrl) componentWalker.EXPECT().OnTerminal(path.MustNewComponent("hello")) - require.NoError(t, path.Resolve("/hello", path.NewAbsoluteScopeWalker(componentWalker))) + require.NoError(t, path.Resolve(path.MustNewUNIXParser("/hello"), path.NewAbsoluteScopeWalker(componentWalker))) }) t.Run("Relative", func(t *testing.T) { @@ -28,6 +28,6 @@ func TestAbsoluteScopeWalker(t *testing.T) { require.Equal( t, status.Error(codes.InvalidArgument, "Path is relative, while an absolute path was expected"), - path.Resolve("hello", path.NewAbsoluteScopeWalker(componentWalker))) + path.Resolve(path.MustNewUNIXParser("hello"), path.NewAbsoluteScopeWalker(componentWalker))) }) } diff --git a/pkg/filesystem/path/builder_test.go b/pkg/filesystem/path/builder_test.go index 402c6efc..478a3478 100644 --- a/pkg/filesystem/path/builder_test.go +++ b/pkg/filesystem/path/builder_test.go @@ -32,7 +32,7 @@ func TestBuilder(t *testing.T) { } { t.Run(p, func(t *testing.T) { builder, scopeWalker := path.EmptyBuilder.Join(path.VoidScopeWalker) - require.NoError(t, path.Resolve(p, scopeWalker)) + require.NoError(t, path.Resolve(path.MustNewUNIXParser(p), scopeWalker)) require.Equal(t, p, builder.String()) }) } @@ -57,7 +57,7 @@ func TestBuilder(t *testing.T) { } { t.Run(from, func(t *testing.T) { builder, scopeWalker := path.EmptyBuilder.Join(path.VoidScopeWalker) - require.NoError(t, path.Resolve(from, scopeWalker)) + require.NoError(t, path.Resolve(path.MustNewUNIXParser(from), scopeWalker)) require.Equal(t, to, builder.String()) }) } @@ -75,7 +75,7 @@ func TestBuilder(t *testing.T) { } { t.Run(from, func(t *testing.T) { builder, scopeWalker := path.RootBuilder.Join(path.VoidScopeWalker) - require.NoError(t, path.Resolve(from, scopeWalker)) + require.NoError(t, path.Resolve(path.MustNewUNIXParser(from), scopeWalker)) require.Equal(t, to, builder.String()) }) } @@ -97,7 +97,7 @@ func TestBuilder(t *testing.T) { componentWalker2.EXPECT().OnUp().Return(componentWalker3, nil) builder, s := path.EmptyBuilder.Join(scopeWalker) - require.NoError(t, path.Resolve("hello/..", s)) + require.NoError(t, path.Resolve(path.MustNewUNIXParser("hello/.."), s)) require.Equal(t, ".", builder.String()) }) @@ -116,7 +116,7 @@ func TestBuilder(t *testing.T) { componentWalker3.EXPECT().OnUp().Return(componentWalker4, nil) builder, s := path.EmptyBuilder.Join(scopeWalker) - require.NoError(t, path.Resolve("../hello/..", s)) + require.NoError(t, path.Resolve(path.MustNewUNIXParser("../hello/.."), s)) require.Equal(t, "..", builder.String()) }) @@ -138,7 +138,7 @@ func TestBuilder(t *testing.T) { componentWalker3.EXPECT().OnUp().Return(componentWalker4, nil) builder, s := path.EmptyBuilder.Join(scopeWalker) - require.NoError(t, path.Resolve("/hello/world/..", s)) + require.NoError(t, path.Resolve(path.MustNewUNIXParser("/hello/world/.."), s)) require.Equal(t, "/hello/", builder.String()) }) } diff --git a/pkg/filesystem/path/component_walker.go b/pkg/filesystem/path/component_walker.go index 2652c429..2a09ee08 100644 --- a/pkg/filesystem/path/component_walker.go +++ b/pkg/filesystem/path/component_walker.go @@ -56,7 +56,7 @@ type ComponentWalker interface { // If the pathname component refers to a symbolic link, this // function will return a GotSymlink containing a ScopeWalker, which // can be used to perform expansion of the symbolic link. The - // Resolve() function will call into OnAbsolute() or OnRealtive() to + // Resolve() function will call into OnAbsolute() or OnRelative() to // signal whether resolution should continue at the root directory // or at the directory that contained the symbolic link. OnDirectory(name Component) (GotDirectoryOrSymlink, error) diff --git a/pkg/filesystem/path/loop_detecting_scope_walker_test.go b/pkg/filesystem/path/loop_detecting_scope_walker_test.go index efb3d767..9354cd28 100644 --- a/pkg/filesystem/path/loop_detecting_scope_walker_test.go +++ b/pkg/filesystem/path/loop_detecting_scope_walker_test.go @@ -28,7 +28,7 @@ func TestLoopDetectingScopeWalker(t *testing.T) { require.Equal( t, status.Error(codes.InvalidArgument, "Maximum number of symbolic link redirections reached"), - path.Resolve("foo", path.NewLoopDetectingScopeWalker(scopeWalker))) + path.Resolve(path.MustNewUNIXParser("foo"), path.NewLoopDetectingScopeWalker(scopeWalker))) }) t.Run("Success", func(t *testing.T) { @@ -49,6 +49,6 @@ func TestLoopDetectingScopeWalker(t *testing.T) { require.NoError( t, - path.Resolve("/tmp", path.NewLoopDetectingScopeWalker(scopeWalker1))) + path.Resolve(path.MustNewUNIXParser("/tmp"), path.NewLoopDetectingScopeWalker(scopeWalker1))) }) } diff --git a/pkg/filesystem/path/parser.go b/pkg/filesystem/path/parser.go new file mode 100644 index 00000000..cdb5ed50 --- /dev/null +++ b/pkg/filesystem/path/parser.go @@ -0,0 +1,11 @@ +package path + +// Parser is used by Resolve to parse paths in the resolution. +type Parser interface { + ParseScope(scopeWalker ScopeWalker) (next ComponentWalker, remainder RelativeParser, err error) +} + +// RelativeParser is used by Resolve to parse relative paths in the resolution. +type RelativeParser interface { + ParseFirstComponent(componentWalker ComponentWalker, mustBeDirectory bool) (next GotDirectoryOrSymlink, remainder RelativeParser, err error) +} diff --git a/pkg/filesystem/path/relative_scope_walker_test.go b/pkg/filesystem/path/relative_scope_walker_test.go index d7f768e0..9c8831df 100644 --- a/pkg/filesystem/path/relative_scope_walker_test.go +++ b/pkg/filesystem/path/relative_scope_walker_test.go @@ -19,7 +19,7 @@ func TestRelativeScopeWalker(t *testing.T) { componentWalker := mock.NewMockComponentWalker(ctrl) componentWalker.EXPECT().OnTerminal(path.MustNewComponent("hello")) - require.NoError(t, path.Resolve("hello", path.NewRelativeScopeWalker(componentWalker))) + require.NoError(t, path.Resolve(path.MustNewUNIXParser("hello"), path.NewRelativeScopeWalker(componentWalker))) }) t.Run("Absolute", func(t *testing.T) { @@ -28,6 +28,6 @@ func TestRelativeScopeWalker(t *testing.T) { require.Equal( t, status.Error(codes.InvalidArgument, "Path is absolute, while a relative path was expected"), - path.Resolve("/hello", path.NewRelativeScopeWalker(componentWalker))) + path.Resolve(path.MustNewUNIXParser("/hello"), path.NewRelativeScopeWalker(componentWalker))) }) } diff --git a/pkg/filesystem/path/resolve.go b/pkg/filesystem/path/resolve.go index a83ccd70..1ce52101 100644 --- a/pkg/filesystem/path/resolve.go +++ b/pkg/filesystem/path/resolve.go @@ -1,134 +1,62 @@ package path -import ( - "strings" - - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" -) - -func stripOneOrMoreSlashes(p string) string { - for { - p = p[1:] - if p == "" || p[0] != '/' { - return p - } - } -} - type resolverState struct { - stack []string + stack []RelativeParser componentWalker ComponentWalker } -// Push a new path string onto the stack of paths that need to be +// Push a new path onto the stack of paths that need to be // processed. This happens once when the resolution process starts, and // will happen for every symlink encountered. -func (rs *resolverState) push(scopeWalker ScopeWalker, path string) error { - // Unix-style paths are generally passed to system calls that - // accept C strings. There is no way these can accept null - // bytes. - if strings.ContainsRune(path, '\x00') { - return status.Error(codes.InvalidArgument, "Path contains a null byte") - } - - // Determine whether the path is absolute. - absolute := false - if path != "" && path[0] == '/' { - path = stripOneOrMoreSlashes(path) - absolute = true - } - +func (rs *resolverState) push(scopeWalker ScopeWalker, parser Parser) error { // Push the path without any leading slashes onto the stack, so - // that its components may be processed. Apply them against the + // that its components may be processed. Apply them against the // right directory. - var componentWalker ComponentWalker - var err error - if absolute { - componentWalker, err = scopeWalker.OnAbsolute() - } else { - componentWalker, err = scopeWalker.OnRelative() - } + componentWalker, remainder, err := parser.ParseScope(scopeWalker) if err != nil { return err } - rs.stack = append(rs.stack, path) + rs.stack = append(rs.stack, remainder) rs.componentWalker = componentWalker return nil } // Pop a single filename from the stack of paths that need to be // processed. This filename is the first one that should be processed. -func (rs *resolverState) pop() string { - p := &rs.stack[len(rs.stack)-1] - slash := strings.IndexByte(*p, '/') - if slash == -1 { - // Path no longer contains a slash. Consume it entirely. - name := *p +func (rs *resolverState) pop() (GotDirectoryOrSymlink, error) { + p := rs.stack[len(rs.stack)-1] + node, remainder, err := p.ParseFirstComponent(rs.componentWalker, len(rs.stack) > 1) + if err != nil { + return nil, err + } + if remainder == nil { rs.stack = rs.stack[:len(rs.stack)-1] - return name + } else { + rs.stack[len(rs.stack)-1] = remainder } - - // Consume the next component and as many slashes as possible. - name := (*p)[:slash] - *p = stripOneOrMoreSlashes((*p)[slash:]) - return name + return node, err } func (rs *resolverState) resolve() error { for len(rs.stack) > 0 { - switch name := rs.pop(); name { - case "", ".": - // An explicit "." entry, or an empty component. - // Empty components can occur if paths end with - // one or more slashes. Treat "foo/" identical - // to "foo/." - case "..": - // Traverse to the parent directory. - componentWalker, err := rs.componentWalker.OnUp() + r, err := rs.pop() + if err != nil { + return err + } + switch rv := r.(type) { + case GotDirectory: + rs.componentWalker = rv.Child + case GotSymlink: + target, err := NewUNIXParser(rv.Target) if err != nil { return err } - rs.componentWalker = componentWalker - default: - if len(rs.stack) > 0 { - // A filename that was followed by a - // slash, or we are symlink expanding - // one or more paths that are followed - // by a slash. This component must yield - // a directory or symlink. - r, err := rs.componentWalker.OnDirectory(Component{ - name: name, - }) - if err != nil { - return err - } - switch rv := r.(type) { - case GotDirectory: - rs.componentWalker = rv.Child - case GotSymlink: - if err := rs.push(rv.Parent, rv.Target); err != nil { - return err - } - default: - panic("Missing result") - } - } else { - // This component may be any kind of file. - r, err := rs.componentWalker.OnTerminal(Component{ - name: name, - }) - if err != nil || r == nil { - // Path resolution ended with - // any file other than a symlink. - return err - } - // Observed a symlink at the end of a - // path. We should continue to run. - if err := rs.push(r.Parent, r.Target); err != nil { - return err - } + if err := rs.push(rv.Parent, target); err != nil { + return err } + default: + panic("Missing result") + case nil: // Do nothing } } @@ -136,18 +64,19 @@ func (rs *resolverState) resolve() error { return nil } -// Resolve a Unix-style pathname string, similar to how the namei() -// function would work in the kernel. For every productive component in -// the pathname, a call against a ScopeWalker or ComponentWalker object -// is made. This object is responsible for registering the path -// traversal and returning symbolic link contents. +// Resolve a pathname string, similar to how the namei() function would +// work in the kernel. For every productive component in the pathname, a +// call against a ScopeWalker or ComponentWalker object is made. This +// object is responsible for registering the path traversal and +// returning symbolic link contents. Unix-style paths can be created +// with NewUNIXParser. // // This function only implements the core algorithm for path resolution. // Features like symlink loop detection, chrooting, etc. should all be // implemented as decorators for ScopeWalker and ComponentWalker. -func Resolve(path string, scopeWalker ScopeWalker) error { +func Resolve(parser Parser, scopeWalker ScopeWalker) error { state := resolverState{} - if err := state.push(scopeWalker, path); err != nil { + if err := state.push(scopeWalker, parser); err != nil { return err } return state.resolve() diff --git a/pkg/filesystem/path/resolve_test.go b/pkg/filesystem/path/resolve_test.go index 70aac1ba..1ca57522 100644 --- a/pkg/filesystem/path/resolve_test.go +++ b/pkg/filesystem/path/resolve_test.go @@ -16,12 +16,12 @@ func TestResolve(t *testing.T) { ctrl := gomock.NewController(t) t.Run("NullByte", func(t *testing.T) { - scopeWalker := mock.NewMockScopeWalker(ctrl) - + _, err := path.NewUNIXParser("hello\x00world") require.Equal( t, status.Error(codes.InvalidArgument, "Path contains a null byte"), - path.Resolve("hello\x00world", scopeWalker)) + err, + ) }) t.Run("Empty", func(t *testing.T) { @@ -29,7 +29,7 @@ func TestResolve(t *testing.T) { componentWalker := mock.NewMockComponentWalker(ctrl) scopeWalker.EXPECT().OnRelative().Return(componentWalker, nil) - require.NoError(t, path.Resolve("", scopeWalker)) + require.NoError(t, path.Resolve(path.MustNewUNIXParser(""), scopeWalker)) }) t.Run("Dot", func(t *testing.T) { @@ -37,7 +37,7 @@ func TestResolve(t *testing.T) { componentWalker := mock.NewMockComponentWalker(ctrl) scopeWalker.EXPECT().OnRelative().Return(componentWalker, nil) - require.NoError(t, path.Resolve(".", scopeWalker)) + require.NoError(t, path.Resolve(path.MustNewUNIXParser("."), scopeWalker)) }) t.Run("SingleFileRelative", func(t *testing.T) { @@ -46,7 +46,7 @@ func TestResolve(t *testing.T) { scopeWalker.EXPECT().OnRelative().Return(componentWalker, nil) componentWalker.EXPECT().OnTerminal(path.MustNewComponent("hello")) - require.NoError(t, path.Resolve("hello", scopeWalker)) + require.NoError(t, path.Resolve(path.MustNewUNIXParser("hello"), scopeWalker)) }) t.Run("SingleFileAbsolute", func(t *testing.T) { @@ -55,7 +55,7 @@ func TestResolve(t *testing.T) { scopeWalker.EXPECT().OnAbsolute().Return(componentWalker, nil) componentWalker.EXPECT().OnTerminal(path.MustNewComponent("hello")) - require.NoError(t, path.Resolve("/hello", scopeWalker)) + require.NoError(t, path.Resolve(path.MustNewUNIXParser("/hello"), scopeWalker)) }) t.Run("SingleDirectoryWithSlash", func(t *testing.T) { @@ -66,7 +66,7 @@ func TestResolve(t *testing.T) { componentWalker1.EXPECT().OnDirectory(path.MustNewComponent("hello")). Return(path.GotDirectory{Child: componentWalker2}, nil) - require.NoError(t, path.Resolve("hello/", scopeWalker)) + require.NoError(t, path.Resolve(path.MustNewUNIXParser("hello/"), scopeWalker)) }) t.Run("SingleDirectoryWithSlashDot", func(t *testing.T) { @@ -77,7 +77,7 @@ func TestResolve(t *testing.T) { componentWalker1.EXPECT().OnDirectory(path.MustNewComponent("hello")). Return(path.GotDirectory{Child: componentWalker2}, nil) - require.NoError(t, path.Resolve("hello/.", scopeWalker)) + require.NoError(t, path.Resolve(path.MustNewUNIXParser("hello/."), scopeWalker)) }) t.Run("MultipleComponents", func(t *testing.T) { @@ -99,7 +99,7 @@ func TestResolve(t *testing.T) { componentWalker5.EXPECT().OnUp().Return(componentWalker6, nil) componentWalker6.EXPECT().OnTerminal(path.MustNewComponent("d")) - require.NoError(t, path.Resolve("./a////../b/c/../d", scopeWalker)) + require.NoError(t, path.Resolve(path.MustNewUNIXParser("./a////../b/c/../d"), scopeWalker)) }) t.Run("SymlinkWithoutSlash", func(t *testing.T) { @@ -113,7 +113,7 @@ func TestResolve(t *testing.T) { scopeWalker2.EXPECT().OnRelative().Return(componentWalker2, nil) componentWalker2.EXPECT().OnTerminal(path.MustNewComponent("b")) - require.NoError(t, path.Resolve("a", scopeWalker1)) + require.NoError(t, path.Resolve(path.MustNewUNIXParser("a"), scopeWalker1)) }) t.Run("SymlinkWithSlashInSymlink", func(t *testing.T) { @@ -129,7 +129,7 @@ func TestResolve(t *testing.T) { componentWalker2.EXPECT().OnDirectory(path.MustNewComponent("b")). Return(path.GotDirectory{Child: componentWalker3}, nil) - require.NoError(t, path.Resolve("a", scopeWalker1)) + require.NoError(t, path.Resolve(path.MustNewUNIXParser("a"), scopeWalker1)) }) t.Run("SymlinkWithSlashInPath", func(t *testing.T) { @@ -145,7 +145,7 @@ func TestResolve(t *testing.T) { componentWalker2.EXPECT().OnDirectory(path.MustNewComponent("b")). Return(path.GotDirectory{Child: componentWalker3}, nil) - require.NoError(t, path.Resolve("a/", scopeWalker1)) + require.NoError(t, path.Resolve(path.MustNewUNIXParser("a/"), scopeWalker1)) }) t.Run("SymlinkInSymlinkInSymlink", func(t *testing.T) { @@ -175,6 +175,6 @@ func TestResolve(t *testing.T) { Return(path.GotDirectory{Child: componentWalker6}, nil) componentWalker6.EXPECT().OnTerminal(path.MustNewComponent("z")) - require.NoError(t, path.Resolve("a", scopeWalker1)) + require.NoError(t, path.Resolve(path.MustNewUNIXParser("a"), scopeWalker1)) }) } diff --git a/pkg/filesystem/path/unix_parser.go b/pkg/filesystem/path/unix_parser.go new file mode 100644 index 00000000..4634eb31 --- /dev/null +++ b/pkg/filesystem/path/unix_parser.go @@ -0,0 +1,128 @@ +package path + +import ( + "strings" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func stripOneOrMoreSlashes(p string) string { + for { + p = p[1:] + if p == "" || p[0] != '/' { + return p + } + } +} + +type unixParser struct { + path string +} + +// NewUNIXParser creates a Parser for Unix paths that can be used in Resolve. +func NewUNIXParser(path string) (Parser, error) { + // Unix-style paths are generally passed to system calls that + // accept C strings. There is no way these can accept null + // bytes. + if strings.ContainsRune(path, '\x00') { + return nil, status.Error(codes.InvalidArgument, "Path contains a null byte") + } + + return &unixParser{path}, nil +} + +// MustNewUNIXParser is identical to NewUNIXParser, except that it panics +// upon failure. +func MustNewUNIXParser(path string) Parser { + parser, err := NewUNIXParser(path) + if err != nil { + panic(err) + } + return parser +} + +func (p unixParser) ParseScope(scopeWalker ScopeWalker) (next ComponentWalker, remainder RelativeParser, err error) { + if p.path != "" && p.path[0] == '/' { + next, err = scopeWalker.OnAbsolute() + if err != nil { + return nil, nil, err + } + + return next, unixRelativeParser{stripOneOrMoreSlashes(p.path)}, nil + } + + next, err = scopeWalker.OnRelative() + if err != nil { + return nil, nil, err + } + + return next, unixRelativeParser{p.path}, nil +} + +type unixRelativeParser struct { + path string +} + +func (rp unixRelativeParser) ParseFirstComponent(componentWalker ComponentWalker, mustBeDirectory bool) (next GotDirectoryOrSymlink, remainder RelativeParser, err error) { + var name string + terminal := false + if slash := strings.IndexByte(rp.path, '/'); slash == -1 { + // Path no longer contains a slash. Consume it entirely. + terminal = true + name = rp.path + remainder = nil + } else { + name = rp.path[:slash] + rp.path = stripOneOrMoreSlashes(rp.path[slash:]) + remainder = unixRelativeParser{rp.path} + } + + switch name { + case "", ".": + // An explicit "." entry, or an empty component. + // Empty components can occur if paths end with + // one or more slashes. Treat "foo/" as identical + // to "foo/." + return GotDirectory{Child: componentWalker}, remainder, nil + case "..": + // Traverse to the parent directory. + parent, err := componentWalker.OnUp() + if err != nil { + return nil, nil, err + } + return GotDirectory{Child: parent}, remainder, nil + } + + // A filename that was followed by a + // slash, or we are symlink expanding + // one or more paths that are followed + // by a slash. This component must yield + // a directory or symlink. + if mustBeDirectory || !terminal { + r, err := componentWalker.OnDirectory(Component{ + name: name, + }) + if err != nil { + return nil, nil, err + } + next = r + return next, remainder, nil + } + + r, err := componentWalker.OnTerminal(Component{ + name: name, + }) + if err != nil || r == nil { + // Path resolution ended with + // any file other than a symlink. + return nil, nil, err + } + + // Observed a symlink at the end of a + // path. We should continue to run. + return GotSymlink{ + Parent: r.Parent, + Target: r.Target, + }, remainder, nil +} diff --git a/pkg/filesystem/path/virtual_root_scope_walker_factory.go b/pkg/filesystem/path/virtual_root_scope_walker_factory.go index b573f28a..250b7944 100644 --- a/pkg/filesystem/path/virtual_root_scope_walker_factory.go +++ b/pkg/filesystem/path/virtual_root_scope_walker_factory.go @@ -126,30 +126,43 @@ func NewVirtualRootScopeWalkerFactory(rootPath string, aliases map[string]string rootCreator := virtualRootNodeCreator{namelessNode: &wf.rootNode} rootPathBuilder, rootPathWalker := EmptyBuilder.Join( NewAbsoluteScopeWalker(&rootCreator)) - if err := Resolve(rootPath, rootPathWalker); err != nil { + + path, err := NewUNIXParser(rootPath) + if err != nil { + return nil, err + } + if err := Resolve(path, rootPathWalker); err != nil { return nil, util.StatusWrapf(err, "Failed to resolve root path %#v", rootPath) } rootCreator.namelessNode.isRoot = true - for path, target := range aliases { + for alias, target := range aliases { + aliasPath, err := NewUNIXParser(alias) + if err != nil { + return nil, err + } + targetPath, err := NewUNIXParser(target) + if err != nil { + return nil, err + } // Resolve the location at which we want to create a fictive // symlink that points into the virtual root directory. aliasCreator := virtualRootNodeCreator{namelessNode: &wf.rootNode} - if err := Resolve(path, NewAbsoluteScopeWalker(&aliasCreator)); err != nil { - return nil, util.StatusWrapf(err, "Failed to resolve alias path %#v", path) + if err := Resolve(aliasPath, NewAbsoluteScopeWalker(&aliasCreator)); err != nil { + return nil, util.StatusWrapf(err, "Failed to resolve alias path %#v", alias) } if aliasCreator.namedNode == nil { - return nil, status.Errorf(codes.InvalidArgument, "Failed to resolve alias path %#v: Last component is not a valid filename", path) + return nil, status.Errorf(codes.InvalidArgument, "Failed to resolve alias path %#v: Last component is not a valid filename", alias) } if aliasCreator.namelessNode.up != nil || len(aliasCreator.namelessNode.down) > 0 { - return nil, status.Errorf(codes.InvalidArgument, "Failed to resolve alias path %#v: Path resides above an already registered path", path) + return nil, status.Errorf(codes.InvalidArgument, "Failed to resolve alias path %#v: Path resides above an already registered path", alias) } // Convert the relative target path to an absolute path // underneath the virtual root. targetPathBuilder, targetPathWalker := rootPathBuilder.Join( NewRelativeScopeWalker(VoidComponentWalker)) - if err := Resolve(target, targetPathWalker); err != nil { + if err := Resolve(targetPath, targetPathWalker); err != nil { return nil, util.StatusWrapf(err, "Failed to resolve alias target %#v", target) } aliasCreator.namedNode.target = targetPathBuilder.String() diff --git a/pkg/filesystem/path/virtual_root_scope_walker_factory_test.go b/pkg/filesystem/path/virtual_root_scope_walker_factory_test.go index 3d5b0e65..610274f8 100644 --- a/pkg/filesystem/path/virtual_root_scope_walker_factory_test.go +++ b/pkg/filesystem/path/virtual_root_scope_walker_factory_test.go @@ -70,7 +70,7 @@ func TestVirtualRootScopeWalkerFactoryCreationSuccess(t *testing.T) { scopeWalker.EXPECT().OnRelative().Return(componentWalker, nil) componentWalker.EXPECT().OnTerminal(path.MustNewComponent("hello")) - require.NoError(t, path.Resolve("hello", factory.New(scopeWalker))) + require.NoError(t, path.Resolve(path.MustNewUNIXParser("hello"), factory.New(scopeWalker))) }) t.Run("Absolute", func(t *testing.T) { @@ -80,7 +80,7 @@ func TestVirtualRootScopeWalkerFactoryCreationSuccess(t *testing.T) { scopeWalker.EXPECT().OnAbsolute().Return(componentWalker, nil) componentWalker.EXPECT().OnTerminal(path.MustNewComponent("hello")) - require.NoError(t, path.Resolve("/root/hello", factory.New(scopeWalker))) + require.NoError(t, path.Resolve(path.MustNewUNIXParser("/root/hello"), factory.New(scopeWalker))) }) t.Run("AbsoluteViaAlias", func(t *testing.T) { @@ -97,7 +97,7 @@ func TestVirtualRootScopeWalkerFactoryCreationSuccess(t *testing.T) { }, nil) componentWalker2.EXPECT().OnTerminal(path.MustNewComponent("hello")) - require.NoError(t, path.Resolve("/alias/hello", factory.New(scopeWalker))) + require.NoError(t, path.Resolve(path.MustNewUNIXParser("/alias/hello"), factory.New(scopeWalker))) }) t.Run("Outside", func(t *testing.T) { @@ -106,8 +106,8 @@ func TestVirtualRootScopeWalkerFactoryCreationSuccess(t *testing.T) { // the ScopeWalker. scopeWalker := mock.NewMockScopeWalker(ctrl) - require.NoError(t, path.Resolve("/", factory.New(scopeWalker))) - require.NoError(t, path.Resolve("/hello", factory.New(scopeWalker))) + require.NoError(t, path.Resolve(path.MustNewUNIXParser("/"), factory.New(scopeWalker))) + require.NoError(t, path.Resolve(path.MustNewUNIXParser("/hello"), factory.New(scopeWalker))) }) t.Run("SymlinkRelative", func(t *testing.T) { @@ -126,7 +126,7 @@ func TestVirtualRootScopeWalkerFactoryCreationSuccess(t *testing.T) { scopeWalker2.EXPECT().OnRelative().Return(componentWalker2, nil) componentWalker2.EXPECT().OnTerminal(path.MustNewComponent("b")) - require.NoError(t, path.Resolve("a", factory.New(scopeWalker1))) + require.NoError(t, path.Resolve(path.MustNewUNIXParser("a"), factory.New(scopeWalker1))) }) t.Run("SymlinkAbsolute", func(t *testing.T) { @@ -145,7 +145,7 @@ func TestVirtualRootScopeWalkerFactoryCreationSuccess(t *testing.T) { scopeWalker2.EXPECT().OnAbsolute().Return(componentWalker2, nil) componentWalker2.EXPECT().OnTerminal(path.MustNewComponent("b")) - require.NoError(t, path.Resolve("a", factory.New(scopeWalker1))) + require.NoError(t, path.Resolve(path.MustNewUNIXParser("a"), factory.New(scopeWalker1))) }) t.Run("SymlinkAbsolute", func(t *testing.T) { @@ -162,6 +162,6 @@ func TestVirtualRootScopeWalkerFactoryCreationSuccess(t *testing.T) { Target: "/hello", }, nil) - require.NoError(t, path.Resolve("a", factory.New(scopeWalker1))) + require.NoError(t, path.Resolve(path.MustNewUNIXParser("a"), factory.New(scopeWalker1))) }) } From bab2e10d747a6a1b36ddf23f861d49972763784a Mon Sep 17 00:00:00 2001 From: Nils Wireklint Date: Tue, 20 Feb 2024 14:23:06 +0100 Subject: [PATCH 3/3] Propagate path.Parser in GotSymlink --- pkg/filesystem/path/component_walker.go | 2 +- .../path/loop_detecting_scope_walker_test.go | 4 ++-- pkg/filesystem/path/parser.go | 9 ++++++++- pkg/filesystem/path/resolve.go | 13 +++++++------ pkg/filesystem/path/resolve_test.go | 12 ++++++------ .../path/virtual_root_scope_walker_factory.go | 18 +++++++++++------- .../virtual_root_scope_walker_factory_test.go | 6 +++--- 7 files changed, 38 insertions(+), 26 deletions(-) diff --git a/pkg/filesystem/path/component_walker.go b/pkg/filesystem/path/component_walker.go index 2a09ee08..c7b5651c 100644 --- a/pkg/filesystem/path/component_walker.go +++ b/pkg/filesystem/path/component_walker.go @@ -23,7 +23,7 @@ type GotSymlink struct { Parent ScopeWalker // The contents of the symbolic link. - Target string + Target Parser } // GotDirectoryOrSymlink is a union type of GotDirectory and GotSymlink. diff --git a/pkg/filesystem/path/loop_detecting_scope_walker_test.go b/pkg/filesystem/path/loop_detecting_scope_walker_test.go index 9354cd28..bd044b17 100644 --- a/pkg/filesystem/path/loop_detecting_scope_walker_test.go +++ b/pkg/filesystem/path/loop_detecting_scope_walker_test.go @@ -22,7 +22,7 @@ func TestLoopDetectingScopeWalker(t *testing.T) { componentWalker := mock.NewMockComponentWalker(ctrl) scopeWalker.EXPECT().OnRelative().Return(componentWalker, nil).Times(41) componentWalker.EXPECT().OnTerminal(path.MustNewComponent("foo")). - Return(&path.GotSymlink{Parent: scopeWalker, Target: "foo"}, nil). + Return(&path.GotSymlink{Parent: scopeWalker, Target: path.MustNewUNIXParser("foo")}, nil). Times(41) require.Equal( @@ -38,7 +38,7 @@ func TestLoopDetectingScopeWalker(t *testing.T) { scopeWalker1.EXPECT().OnAbsolute().Return(componentWalker1, nil) scopeWalker2 := mock.NewMockScopeWalker(ctrl) componentWalker1.EXPECT().OnTerminal(path.MustNewComponent("tmp")). - Return(&path.GotSymlink{Parent: scopeWalker2, Target: "private/tmp"}, nil) + Return(&path.GotSymlink{Parent: scopeWalker2, Target: path.MustNewUNIXParser("private/tmp")}, nil) componentWalker2 := mock.NewMockComponentWalker(ctrl) scopeWalker2.EXPECT().OnRelative().Return(componentWalker2, nil) componentWalker3 := mock.NewMockComponentWalker(ctrl) diff --git a/pkg/filesystem/path/parser.go b/pkg/filesystem/path/parser.go index cdb5ed50..a92e25fe 100644 --- a/pkg/filesystem/path/parser.go +++ b/pkg/filesystem/path/parser.go @@ -1,11 +1,18 @@ package path -// Parser is used by Resolve to parse paths in the resolution. + + +// Parser is used by Resolve to parse paths in the resolution. Implementations +// of ParseScope() should return a new copy of Parser and leave the current +// instance unmodified. It is permitted to call ParseScope() multiple times. type Parser interface { ParseScope(scopeWalker ScopeWalker) (next ComponentWalker, remainder RelativeParser, err error) } // RelativeParser is used by Resolve to parse relative paths in the resolution. +// Implementations of ParseFirstComponent() should return a new copy of Parser +// and leave the current instance unmodified. It is permitted to call +// ParseFirstComponent() multiple times. type RelativeParser interface { ParseFirstComponent(componentWalker ComponentWalker, mustBeDirectory bool) (next GotDirectoryOrSymlink, remainder RelativeParser, err error) } diff --git a/pkg/filesystem/path/resolve.go b/pkg/filesystem/path/resolve.go index 1ce52101..fef50de3 100644 --- a/pkg/filesystem/path/resolve.go +++ b/pkg/filesystem/path/resolve.go @@ -47,16 +47,17 @@ func (rs *resolverState) resolve() error { case GotDirectory: rs.componentWalker = rv.Child case GotSymlink: - target, err := NewUNIXParser(rv.Target) - if err != nil { - return err - } - if err := rs.push(rv.Parent, target); err != nil { + if err := rs.push(rv.Parent, rv.Target); err != nil { return err } default: panic("Missing result") - case nil: // Do nothing + case nil: + // Parsed a terminal component. This should only be permitted at the + // very end of the path resolution process. + if len(rs.stack) > 0 { + panic("Parser.ParseFirstComponent() did not respect mustBeDirectory") + } } } diff --git a/pkg/filesystem/path/resolve_test.go b/pkg/filesystem/path/resolve_test.go index 1ca57522..c028b123 100644 --- a/pkg/filesystem/path/resolve_test.go +++ b/pkg/filesystem/path/resolve_test.go @@ -108,7 +108,7 @@ func TestResolve(t *testing.T) { scopeWalker1.EXPECT().OnRelative().Return(componentWalker1, nil) scopeWalker2 := mock.NewMockScopeWalker(ctrl) componentWalker1.EXPECT().OnTerminal(path.MustNewComponent("a")). - Return(&path.GotSymlink{Parent: scopeWalker2, Target: "b"}, nil) + Return(&path.GotSymlink{Parent: scopeWalker2, Target: path.MustNewUNIXParser("b")}, nil) componentWalker2 := mock.NewMockComponentWalker(ctrl) scopeWalker2.EXPECT().OnRelative().Return(componentWalker2, nil) componentWalker2.EXPECT().OnTerminal(path.MustNewComponent("b")) @@ -122,7 +122,7 @@ func TestResolve(t *testing.T) { scopeWalker1.EXPECT().OnRelative().Return(componentWalker1, nil) scopeWalker2 := mock.NewMockScopeWalker(ctrl) componentWalker1.EXPECT().OnTerminal(path.MustNewComponent("a")). - Return(&path.GotSymlink{Parent: scopeWalker2, Target: "b/"}, nil) + Return(&path.GotSymlink{Parent: scopeWalker2, Target: path.MustNewUNIXParser("b/")}, nil) componentWalker2 := mock.NewMockComponentWalker(ctrl) scopeWalker2.EXPECT().OnRelative().Return(componentWalker2, nil) componentWalker3 := mock.NewMockComponentWalker(ctrl) @@ -138,7 +138,7 @@ func TestResolve(t *testing.T) { scopeWalker1.EXPECT().OnRelative().Return(componentWalker1, nil) scopeWalker2 := mock.NewMockScopeWalker(ctrl) componentWalker1.EXPECT().OnDirectory(path.MustNewComponent("a")). - Return(path.GotSymlink{Parent: scopeWalker2, Target: "b"}, nil) + Return(path.GotSymlink{Parent: scopeWalker2, Target: path.MustNewUNIXParser("b")}, nil) componentWalker2 := mock.NewMockComponentWalker(ctrl) scopeWalker2.EXPECT().OnRelative().Return(componentWalker2, nil) componentWalker3 := mock.NewMockComponentWalker(ctrl) @@ -154,17 +154,17 @@ func TestResolve(t *testing.T) { scopeWalker1.EXPECT().OnRelative().Return(componentWalker1, nil) scopeWalker2 := mock.NewMockScopeWalker(ctrl) componentWalker1.EXPECT().OnTerminal(path.MustNewComponent("a")). - Return(&path.GotSymlink{Parent: scopeWalker2, Target: "b/z"}, nil) + Return(&path.GotSymlink{Parent: scopeWalker2, Target: path.MustNewUNIXParser("b/z")}, nil) componentWalker2 := mock.NewMockComponentWalker(ctrl) scopeWalker2.EXPECT().OnRelative().Return(componentWalker2, nil) scopeWalker3 := mock.NewMockScopeWalker(ctrl) componentWalker2.EXPECT().OnDirectory(path.MustNewComponent("b")). - Return(path.GotSymlink{Parent: scopeWalker3, Target: "c/y"}, nil) + Return(path.GotSymlink{Parent: scopeWalker3, Target: path.MustNewUNIXParser("c/y")}, nil) componentWalker3 := mock.NewMockComponentWalker(ctrl) scopeWalker3.EXPECT().OnRelative().Return(componentWalker3, nil) scopeWalker4 := mock.NewMockScopeWalker(ctrl) componentWalker3.EXPECT().OnDirectory(path.MustNewComponent("c")). - Return(path.GotSymlink{Parent: scopeWalker4, Target: "x"}, nil) + Return(path.GotSymlink{Parent: scopeWalker4, Target: path.MustNewUNIXParser("x")}, nil) componentWalker4 := mock.NewMockComponentWalker(ctrl) scopeWalker4.EXPECT().OnRelative().Return(componentWalker4, nil) componentWalker5 := mock.NewMockComponentWalker(ctrl) diff --git a/pkg/filesystem/path/virtual_root_scope_walker_factory.go b/pkg/filesystem/path/virtual_root_scope_walker_factory.go index 250b7944..2077dc2b 100644 --- a/pkg/filesystem/path/virtual_root_scope_walker_factory.go +++ b/pkg/filesystem/path/virtual_root_scope_walker_factory.go @@ -22,7 +22,7 @@ type namelessVirtualRootNode struct { // that point into the root directory. type namedVirtualRootNode struct { nameless namelessVirtualRootNode - target string + target Parser } // virtualRootNodeCreator is an implementation of ComponentWalker that @@ -37,7 +37,7 @@ type virtualRootNodeCreator struct { func (cw *virtualRootNodeCreator) OnDirectory(name Component) (GotDirectoryOrSymlink, error) { n, ok := cw.namelessNode.down[name] if ok { - if n.nameless.isRoot || n.target != "" { + if n.nameless.isRoot || n.target != nil { return nil, status.Error(codes.InvalidArgument, "Path resides at or below an already registered path") } } else { @@ -129,7 +129,7 @@ func NewVirtualRootScopeWalkerFactory(rootPath string, aliases map[string]string path, err := NewUNIXParser(rootPath) if err != nil { - return nil, err + return nil, util.StatusWrapf(err, "Failed to parse root path %#v", rootPath) } if err := Resolve(path, rootPathWalker); err != nil { return nil, util.StatusWrapf(err, "Failed to resolve root path %#v", rootPath) @@ -139,11 +139,11 @@ func NewVirtualRootScopeWalkerFactory(rootPath string, aliases map[string]string for alias, target := range aliases { aliasPath, err := NewUNIXParser(alias) if err != nil { - return nil, err + return nil, util.StatusWrapf(err, "Failed to parse alias path %#v", rootPath) } targetPath, err := NewUNIXParser(target) if err != nil { - return nil, err + return nil, util.StatusWrapf(err, "Failed to parse target path %#v", targetPath) } // Resolve the location at which we want to create a fictive // symlink that points into the virtual root directory. @@ -165,7 +165,11 @@ func NewVirtualRootScopeWalkerFactory(rootPath string, aliases map[string]string if err := Resolve(targetPath, targetPathWalker); err != nil { return nil, util.StatusWrapf(err, "Failed to resolve alias target %#v", target) } - aliasCreator.namedNode.target = targetPathBuilder.String() + parser, err := NewUNIXParser(targetPathBuilder.String()) + if err != nil { + return nil, err + } + aliasCreator.namedNode.target = parser } return wf, nil } @@ -242,7 +246,7 @@ func (cw *pendingVirtualRootComponentWalker) OnDirectory(name Component) (GotDir // the full path. return VoidComponentWalker.OnDirectory(name) } - if n.target != "" { + if n.target != nil { // Found an alias to the underlying root directory. return GotSymlink{ Parent: cw.walker, diff --git a/pkg/filesystem/path/virtual_root_scope_walker_factory_test.go b/pkg/filesystem/path/virtual_root_scope_walker_factory_test.go index 610274f8..d8c5faae 100644 --- a/pkg/filesystem/path/virtual_root_scope_walker_factory_test.go +++ b/pkg/filesystem/path/virtual_root_scope_walker_factory_test.go @@ -120,7 +120,7 @@ func TestVirtualRootScopeWalkerFactoryCreationSuccess(t *testing.T) { componentWalker1.EXPECT().OnTerminal(path.MustNewComponent("a")). Return(&path.GotSymlink{ Parent: scopeWalker2, - Target: "b", + Target: path.MustNewUNIXParser("b"), }, nil) componentWalker2 := mock.NewMockComponentWalker(ctrl) scopeWalker2.EXPECT().OnRelative().Return(componentWalker2, nil) @@ -139,7 +139,7 @@ func TestVirtualRootScopeWalkerFactoryCreationSuccess(t *testing.T) { componentWalker1.EXPECT().OnTerminal(path.MustNewComponent("a")). Return(&path.GotSymlink{ Parent: scopeWalker2, - Target: "/root/b", + Target: path.MustNewUNIXParser("/root/b"), }, nil) componentWalker2 := mock.NewMockComponentWalker(ctrl) scopeWalker2.EXPECT().OnAbsolute().Return(componentWalker2, nil) @@ -159,7 +159,7 @@ func TestVirtualRootScopeWalkerFactoryCreationSuccess(t *testing.T) { componentWalker.EXPECT().OnTerminal(path.MustNewComponent("a")). Return(&path.GotSymlink{ Parent: scopeWalker2, - Target: "/hello", + Target: path.MustNewUNIXParser("/hello"), }, nil) require.NoError(t, path.Resolve(path.MustNewUNIXParser("a"), factory.New(scopeWalker1)))