diff --git a/lookup_linux.go b/lookup_linux.go index 16acb03..103a743 100644 --- a/lookup_linux.go +++ b/lookup_linux.go @@ -65,6 +65,11 @@ func (s *symlinkStack) popPart(part string) error { // real path provided by the user, and this is a no-op. return errEmptyStack } + if part == "." { + // "." components are no-ops -- we drop them when doing SwapLink. + return nil + } + tailEntry := (*s)[len(*s)-1] // Double-check that we are popping the component we expect. @@ -110,14 +115,7 @@ func (s *symlinkStack) push(dir *os.File, remainingPath, linkTarget string) erro // Split the link target and clean up any "" parts. linkTargetParts := slices.DeleteFunc( strings.Split(linkTarget, "/"), - func(part string) bool { return part == "" }) - - // Don't add a no-op link to the stack. You can't create a no-op link - // symlink, but if the symlink is /, partialLookupInRoot has already jumped to the - // root and so there's nothing more to do. - if len(linkTargetParts) == 0 { - return nil - } + func(part string) bool { return part == "" || part == "." }) // Copy the directory so the caller doesn't close our copy. dirCopy, err := dupFile(dir) @@ -244,9 +242,11 @@ func lookupInRoot(root *os.File, unsafePath string, partial bool) (Handle *os.Fi } else { part, remainingPath = remainingPath[:i], remainingPath[i+1:] } - // Skip any "//" components. + // If we hit an empty component, we need to treat it as though it is + // "." so that trailing "/" and "//" components on a non-directory + // correctly return the right error code. if part == "" { - continue + part = "." } // Apply the component lexically to the path we are building. @@ -365,6 +365,25 @@ func lookupInRoot(root *os.File, unsafePath string, partial bool) (Handle *os.Fi return currentDir, oldRemainingPath, err } } + + // If the unsafePath had a trailing slash, we need to make sure we try to + // do a relative "." open so that we will correctly return an error when + // the final component is a non-directory (to match openat2). In the + // context of openat2, a trailing slash and a trailing "/." are completely + // equivalent. + if strings.HasSuffix(unsafePath, "/") { + nextDir, err := openatFile(currentDir, ".", unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) + if err != nil { + if !partial { + _ = currentDir.Close() + currentDir = nil + } + return currentDir, "", err + } + _ = currentDir.Close() + currentDir = nextDir + } + // All of the components existed! return currentDir, "", nil } diff --git a/lookup_linux_test.go b/lookup_linux_test.go index 76e37ef..eac547c 100644 --- a/lookup_linux_test.go +++ b/lookup_linux_test.go @@ -153,55 +153,86 @@ func testPartialLookup(t *testing.T, partialLookupFn partialLookupFunc) { // Complete lookups. "complete-dir1": {"a", lookupResult{handlePath: "/a", remainingPath: "", fileType: unix.S_IFDIR}}, "complete-dir2": {"b/c/d/e/f", lookupResult{handlePath: "/b/c/d/e/f", remainingPath: "", fileType: unix.S_IFDIR}}, + "complete-dir3": {"b///././c////.//d/./././///e////.//./f//././././", lookupResult{handlePath: "/b/c/d/e/f", remainingPath: "", fileType: unix.S_IFDIR}}, "complete-fifo": {"b/fifo", lookupResult{handlePath: "/b/fifo", remainingPath: "", fileType: unix.S_IFIFO}}, "complete-sock": {"b/sock", lookupResult{handlePath: "/b/sock", remainingPath: "", fileType: unix.S_IFSOCK}}, // Partial lookups. "partial-dir-basic": {"a/b/c/d/e/f/g/h", lookupResult{handlePath: "/a", remainingPath: "b/c/d/e/f/g/h", fileType: unix.S_IFDIR, err: unix.ENOENT}}, "partial-dir-dotdot": {"a/foo/../bar/baz", lookupResult{handlePath: "/a", remainingPath: "foo/../bar/baz", fileType: unix.S_IFDIR, err: unix.ENOENT}}, // Complete lookups of non-lexical symlinks. - "nonlexical-basic-complete": {"target", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, + "nonlexical-basic-complete1": {"target", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, + "nonlexical-basic-complete2": {"target/", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, + "nonlexical-basic-complete3": {"target//", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, "nonlexical-basic-partial": {"target/foo", lookupResult{handlePath: "/target", remainingPath: "foo", fileType: unix.S_IFDIR, err: unix.ENOENT}}, "nonlexical-basic-partial-dotdot": {"target/../target/foo/bar/../baz", lookupResult{handlePath: "/target", remainingPath: "foo/bar/../baz", fileType: unix.S_IFDIR, err: unix.ENOENT}}, - "nonlexical-level1-abs-complete": {"link1/target_abs", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, + "nonlexical-level1-abs-complete1": {"link1/target_abs", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, + "nonlexical-level1-abs-complete2": {"link1/target_abs/", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, + "nonlexical-level1-abs-complete3": {"link1/target_abs//", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, "nonlexical-level1-abs-partial": {"link1/target_abs/foo", lookupResult{handlePath: "/target", remainingPath: "foo", fileType: unix.S_IFDIR, err: unix.ENOENT}}, "nonlexical-level1-abs-partial-dotdot": {"link1/target_abs/../target/foo/bar/../baz", lookupResult{handlePath: "/target", remainingPath: "foo/bar/../baz", fileType: unix.S_IFDIR, err: unix.ENOENT}}, - "nonlexical-level1-rel-complete": {"link1/target_rel", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, + "nonlexical-level1-rel-complete1": {"link1/target_rel", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, + "nonlexical-level1-rel-complete2": {"link1/target_rel/", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, + "nonlexical-level1-rel-complete3": {"link1/target_rel//", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, "nonlexical-level1-rel-partial": {"link1/target_rel/foo", lookupResult{handlePath: "/target", remainingPath: "foo", fileType: unix.S_IFDIR, err: unix.ENOENT}}, "nonlexical-level1-rel-partial-dotdot": {"link1/target_rel/../target/foo/bar/../baz", lookupResult{handlePath: "/target", remainingPath: "foo/bar/../baz", fileType: unix.S_IFDIR, err: unix.ENOENT}}, - "nonlexical-level2-abs-abs-complete": {"link2/link1_abs/target_abs", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, + "nonlexical-level2-abs-abs-complete1": {"link2/link1_abs/target_abs", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, + "nonlexical-level2-abs-abs-complete2": {"link2/link1_abs/target_abs/", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, + "nonlexical-level2-abs-abs-complete3": {"link2/link1_abs/target_abs//", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, "nonlexical-level2-abs-abs-partial": {"link2/link1_abs/target_abs/foo", lookupResult{handlePath: "/target", remainingPath: "foo", fileType: unix.S_IFDIR, err: unix.ENOENT}}, "nonlexical-level2-abs-abs-partial-dotdot": {"link2/link1_abs/target_abs/../target/foo/bar/../baz", lookupResult{handlePath: "/target", remainingPath: "foo/bar/../baz", fileType: unix.S_IFDIR, err: unix.ENOENT}}, - "nonlexical-level2-abs-rel-complete": {"link2/link1_abs/target_rel", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, + "nonlexical-level2-abs-rel-complete1": {"link2/link1_abs/target_rel", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, + "nonlexical-level2-abs-rel-complete2": {"link2/link1_abs/target_rel/", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, + "nonlexical-level2-abs-rel-complete3": {"link2/link1_abs/target_rel//", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, "nonlexical-level2-abs-rel-partial": {"link2/link1_abs/target_rel/foo", lookupResult{handlePath: "/target", remainingPath: "foo", fileType: unix.S_IFDIR, err: unix.ENOENT}}, "nonlexical-level2-abs-rel-partial-dotdot": {"link2/link1_abs/target_rel/../target/foo/bar/../baz", lookupResult{handlePath: "/target", remainingPath: "foo/bar/../baz", fileType: unix.S_IFDIR, err: unix.ENOENT}}, - "nonlexical-level2-abs-open-complete": {"link2/link1_abs/../target", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, + "nonlexical-level2-abs-open-complete1": {"link2/link1_abs/../target", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, + "nonlexical-level2-abs-open-complete2": {"link2/link1_abs/../target/", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, + "nonlexical-level2-abs-open-complete3": {"link2/link1_abs/../target//", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, "nonlexical-level2-abs-open-partial": {"link2/link1_abs/../target/foo", lookupResult{handlePath: "/target", remainingPath: "foo", fileType: unix.S_IFDIR, err: unix.ENOENT}}, "nonlexical-level2-abs-open-partial-dotdot": {"link2/link1_abs/../target/../target/foo/bar/../baz", lookupResult{handlePath: "/target", remainingPath: "foo/bar/../baz", fileType: unix.S_IFDIR, err: unix.ENOENT}}, - "nonlexical-level2-rel-abs-complete": {"link2/link1_rel/target_abs", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, + "nonlexical-level2-rel-abs-complete1": {"link2/link1_rel/target_abs", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, + "nonlexical-level2-rel-abs-complete2": {"link2/link1_rel/target_abs/", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, + "nonlexical-level2-rel-abs-complete3": {"link2/link1_rel/target_abs//", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, "nonlexical-level2-rel-abs-partial": {"link2/link1_rel/target_abs/foo", lookupResult{handlePath: "/target", remainingPath: "foo", fileType: unix.S_IFDIR, err: unix.ENOENT}}, "nonlexical-level2-rel-abs-partial-dotdot": {"link2/link1_rel/target_abs/../target/foo/bar/../baz", lookupResult{handlePath: "/target", remainingPath: "foo/bar/../baz", fileType: unix.S_IFDIR, err: unix.ENOENT}}, - "nonlexical-level2-rel-rel-complete": {"link2/link1_rel/target_rel", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, + "nonlexical-level2-rel-rel-complete1": {"link2/link1_rel/target_rel", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, + "nonlexical-level2-rel-rel-complete2": {"link2/link1_rel/target_rel/", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, + "nonlexical-level2-rel-rel-complete3": {"link2/link1_rel/target_rel//", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, "nonlexical-level2-rel-rel-partial": {"link2/link1_rel/target_rel/foo", lookupResult{handlePath: "/target", remainingPath: "foo", fileType: unix.S_IFDIR, err: unix.ENOENT}}, "nonlexical-level2-rel-rel-partial-dotdot": {"link2/link1_rel/target_rel/../target/foo/bar/../baz", lookupResult{handlePath: "/target", remainingPath: "foo/bar/../baz", fileType: unix.S_IFDIR, err: unix.ENOENT}}, - "nonlexical-level2-rel-open-complete": {"link2/link1_rel/../target", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, + "nonlexical-level2-rel-open-complete1": {"link2/link1_rel/../target", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, + "nonlexical-level2-rel-open-complete2": {"link2/link1_rel/../target/", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, + "nonlexical-level2-rel-open-complete3": {"link2/link1_rel/../target//", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, "nonlexical-level2-rel-open-partial": {"link2/link1_rel/../target/foo", lookupResult{handlePath: "/target", remainingPath: "foo", fileType: unix.S_IFDIR, err: unix.ENOENT}}, "nonlexical-level2-rel-open-partial-dotdot": {"link2/link1_rel/../target/../target/foo/bar/../baz", lookupResult{handlePath: "/target", remainingPath: "foo/bar/../baz", fileType: unix.S_IFDIR, err: unix.ENOENT}}, - "nonlexical-level3-abs-complete": {"link3/target_abs", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, + "nonlexical-level3-abs-complete1": {"link3/target_abs", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, + "nonlexical-level3-abs-complete2": {"link3/target_abs/", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, + "nonlexical-level3-abs-complete3": {"link3/target_abs//", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, "nonlexical-level3-abs-partial": {"link3/target_abs/foo", lookupResult{handlePath: "/target", remainingPath: "foo", fileType: unix.S_IFDIR, err: unix.ENOENT}}, "nonlexical-level3-abs-partial-dotdot": {"link3/target_abs/../target/foo/bar/../baz", lookupResult{handlePath: "/target", remainingPath: "foo/bar/../baz", fileType: unix.S_IFDIR, err: unix.ENOENT}}, - "nonlexical-level3-rel-complete": {"link3/target_rel", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, + "nonlexical-level3-rel-complete1": {"link3/target_rel", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, + "nonlexical-level3-rel-complete2": {"link3/target_rel/", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, + "nonlexical-level3-rel-complete3": {"link3/target_rel//", lookupResult{handlePath: "/target", remainingPath: "", fileType: unix.S_IFDIR}}, "nonlexical-level3-rel-partial": {"link3/target_rel/foo", lookupResult{handlePath: "/target", remainingPath: "foo", fileType: unix.S_IFDIR, err: unix.ENOENT}}, "nonlexical-level3-rel-partial-dotdot": {"link3/target_rel/../target/foo/bar/../baz", lookupResult{handlePath: "/target", remainingPath: "foo/bar/../baz", fileType: unix.S_IFDIR, err: unix.ENOENT}}, // Partial lookups due to hitting a non-directory. + "partial-nondir-slash1": {"b/c/file/", lookupResult{handlePath: "/b/c/file", remainingPath: "", fileType: unix.S_IFREG, err: unix.ENOTDIR}}, + "partial-nondir-slash2": {"b/c/file//", lookupResult{handlePath: "/b/c/file", remainingPath: "/", fileType: unix.S_IFREG, err: unix.ENOTDIR}}, "partial-nondir-dot": {"b/c/file/.", lookupResult{handlePath: "/b/c/file", remainingPath: ".", fileType: unix.S_IFREG, err: unix.ENOTDIR}}, "partial-nondir-dotdot1": {"b/c/file/..", lookupResult{handlePath: "/b/c/file", remainingPath: "..", fileType: unix.S_IFREG, err: unix.ENOTDIR}}, "partial-nondir-dotdot2": {"b/c/file/../foo/bar", lookupResult{handlePath: "/b/c/file", remainingPath: "../foo/bar", fileType: unix.S_IFREG, err: unix.ENOTDIR}}, + "partial-nondir-symlink-slash1": {"b-file/", lookupResult{handlePath: "/b/c/file", remainingPath: "", fileType: unix.S_IFREG, err: unix.ENOTDIR}}, + "partial-nondir-symlink-slash2": {"b-file//", lookupResult{handlePath: "/b/c/file", remainingPath: "/", fileType: unix.S_IFREG, err: unix.ENOTDIR}}, "partial-nondir-symlink-dot": {"b-file/.", lookupResult{handlePath: "/b/c/file", remainingPath: ".", fileType: unix.S_IFREG, err: unix.ENOTDIR}}, "partial-nondir-symlink-dotdot1": {"b-file/..", lookupResult{handlePath: "/b/c/file", remainingPath: "..", fileType: unix.S_IFREG, err: unix.ENOTDIR}}, "partial-nondir-symlink-dotdot2": {"b-file/../foo/bar", lookupResult{handlePath: "/b/c/file", remainingPath: "../foo/bar", fileType: unix.S_IFREG, err: unix.ENOTDIR}}, + "partial-fifo-slash1": {"b/fifo/", lookupResult{handlePath: "/b/fifo", remainingPath: "", fileType: unix.S_IFIFO, err: unix.ENOTDIR}}, + "partial-fifo-slash2": {"b/fifo//", lookupResult{handlePath: "/b/fifo", remainingPath: "/", fileType: unix.S_IFIFO, err: unix.ENOTDIR}}, "partial-fifo-dot": {"b/fifo/.", lookupResult{handlePath: "/b/fifo", remainingPath: ".", fileType: unix.S_IFIFO, err: unix.ENOTDIR}}, "partial-fifo-dotdot1": {"b/fifo/..", lookupResult{handlePath: "/b/fifo", remainingPath: "..", fileType: unix.S_IFIFO, err: unix.ENOTDIR}}, "partial-fifo-dotdot2": {"b/fifo/../foo/bar", lookupResult{handlePath: "/b/fifo", remainingPath: "../foo/bar", fileType: unix.S_IFIFO, err: unix.ENOTDIR}}, + "partial-sock-slash1": {"b/sock/", lookupResult{handlePath: "/b/sock", remainingPath: "", fileType: unix.S_IFSOCK, err: unix.ENOTDIR}}, + "partial-sock-slash2": {"b/sock//", lookupResult{handlePath: "/b/sock", remainingPath: "/", fileType: unix.S_IFSOCK, err: unix.ENOTDIR}}, "partial-sock-dot": {"b/sock/.", lookupResult{handlePath: "/b/sock", remainingPath: ".", fileType: unix.S_IFSOCK, err: unix.ENOTDIR}}, "partial-sock-dotdot1": {"b/sock/..", lookupResult{handlePath: "/b/sock", remainingPath: "..", fileType: unix.S_IFSOCK, err: unix.ENOTDIR}}, "partial-sock-dotdot2": {"b/sock/../foo/bar", lookupResult{handlePath: "/b/sock", remainingPath: "../foo/bar", fileType: unix.S_IFSOCK, err: unix.ENOTDIR}}, @@ -747,7 +778,7 @@ func TestSymlinkStackTailChain(t *testing.T) { defer ss.Close() // Basic expected contents. - testStackContents(t, "initial state", ss, + initialState := []expectedStackEntry{ // Top entry is not a tail-chain. expectedStackEntry{"A", []string{"subdir1"}}, // The first tail-chain should have no unwalked links. @@ -764,31 +795,22 @@ func TestSymlinkStackTailChain(t *testing.T) { expectedStackEntry{"J", nil}, // Final entry in the second tail-chain. expectedStackEntry{"K", []string{"taillink2", ".."}}, - ) + } + + testStackContents(t, "initial state", ss, initialState...) + + // Trying to pop "." does nothing. + for i := 0; i < 20; i++ { + require.NoError(t, ss.PopPart("."), `popping "." should never fail`) + // NOTE: Same contents as above. + testStackContents(t, "noop pop .", ss, initialState...) + } // Popping any of the early tail chain entries must fail. - for _, badPart := range []string{"subdir1", "subdir2", "..", "."} { + for _, badPart := range []string{"subdir1", "subdir2", ".."} { require.ErrorIsf(t, ss.PopPart(badPart), errBrokenSymlinkStack, "bad pop %q", badPart) - // NOTE: Same contents as above. - testStackContents(t, "bad pop "+badPart, ss, - // Top entry is not a tail-chain. - expectedStackEntry{"A", []string{"subdir1"}}, - // The first tail-chain should have no unwalked links. - expectedStackEntry{"B", nil}, - expectedStackEntry{"C", nil}, - expectedStackEntry{"D", nil}, - // Final entry in the first tail-chain. - expectedStackEntry{"E", []string{"subdir2"}}, - // The second tail-chain should have no unwalked links. - expectedStackEntry{"F", nil}, - expectedStackEntry{"G", nil}, - expectedStackEntry{"H", nil}, - expectedStackEntry{"I", nil}, - expectedStackEntry{"J", nil}, - // Final entry in the second tail-chain. - expectedStackEntry{"K", []string{"taillink2", ".."}}, - ) + testStackContents(t, "bad pop "+badPart, ss, initialState...) } // Dropping the second-last entry should keep the tail-chain. diff --git a/open_linux_test.go b/open_linux_test.go index 6cd2e36..0621ae7 100644 --- a/open_linux_test.go +++ b/open_linux_test.go @@ -211,6 +211,7 @@ func testOpenInRoot(t *testing.T, openInRootFn openInRootFunc) { // Complete lookups. "complete-dir1": {"a", openResult{handlePath: "/a", fileType: unix.S_IFDIR}}, "complete-dir2": {"b/c/d/e/f", openResult{handlePath: "/b/c/d/e/f", fileType: unix.S_IFDIR}}, + "complete-dir3": {"b///././c////.//d/./././///e////.//./f//././././", openResult{handlePath: "/b/c/d/e/f", fileType: unix.S_IFDIR}}, "complete-file": {"b/c/file", openResult{handlePath: "/b/c/file", fileType: unix.S_IFREG}}, "complete-file-link": {"b-file", openResult{handlePath: "/b/c/file", fileType: unix.S_IFREG}}, "complete-fifo": {"b/fifo", openResult{handlePath: "/b/fifo", fileType: unix.S_IFIFO}}, @@ -219,49 +220,79 @@ func testOpenInRoot(t *testing.T, openInRootFn openInRootFunc) { "partial-dir-basic": {"a/b/c/d/e/f/g/h", openResult{err: unix.ENOENT}}, "partial-dir-dotdot": {"a/foo/../bar/baz", openResult{err: unix.ENOENT}}, // Complete lookups of non-lexical symlinks. - "nonlexical-basic-complete": {"target", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, + "nonlexical-basic-complete1": {"target", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, + "nonlexical-basic-complete2": {"target/", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, + "nonlexical-basic-complete3": {"target//", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, "nonlexical-basic-partial": {"target/foo", openResult{err: unix.ENOENT}}, "nonlexical-basic-partial-dotdot": {"target/../target/foo/bar/../baz", openResult{err: unix.ENOENT}}, - "nonlexical-level1-abs-complete": {"link1/target_abs", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, + "nonlexical-level1-abs-complete1": {"link1/target_abs", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, + "nonlexical-level1-abs-complete2": {"link1/target_abs/", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, + "nonlexical-level1-abs-complete3": {"link1/target_abs//", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, "nonlexical-level1-abs-partial": {"link1/target_abs/foo", openResult{err: unix.ENOENT}}, "nonlexical-level1-abs-partial-dotdot": {"link1/target_abs/../target/foo/bar/../baz", openResult{err: unix.ENOENT}}, - "nonlexical-level1-rel-complete": {"link1/target_rel", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, + "nonlexical-level1-rel-complete1": {"link1/target_rel", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, + "nonlexical-level1-rel-complete2": {"link1/target_rel/", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, + "nonlexical-level1-rel-complete3": {"link1/target_rel//", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, "nonlexical-level1-rel-partial": {"link1/target_rel/foo", openResult{err: unix.ENOENT}}, "nonlexical-level1-rel-partial-dotdot": {"link1/target_rel/../target/foo/bar/../baz", openResult{err: unix.ENOENT}}, - "nonlexical-level2-abs-abs-complete": {"link2/link1_abs/target_abs", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, + "nonlexical-level2-abs-abs-complete1": {"link2/link1_abs/target_abs", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, + "nonlexical-level2-abs-abs-complete2": {"link2/link1_abs/target_abs/", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, + "nonlexical-level2-abs-abs-complete3": {"link2/link1_abs/target_abs//", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, "nonlexical-level2-abs-abs-partial": {"link2/link1_abs/target_abs/foo", openResult{err: unix.ENOENT}}, "nonlexical-level2-abs-abs-partial-dotdot": {"link2/link1_abs/target_abs/../target/foo/bar/../baz", openResult{err: unix.ENOENT}}, - "nonlexical-level2-abs-rel-complete": {"link2/link1_abs/target_rel", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, + "nonlexical-level2-abs-rel-complete1": {"link2/link1_abs/target_rel", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, + "nonlexical-level2-abs-rel-complete2": {"link2/link1_abs/target_rel/", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, + "nonlexical-level2-abs-rel-complete3": {"link2/link1_abs/target_rel//", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, "nonlexical-level2-abs-rel-partial": {"link2/link1_abs/target_rel/foo", openResult{err: unix.ENOENT}}, "nonlexical-level2-abs-rel-partial-dotdot": {"link2/link1_abs/target_rel/../target/foo/bar/../baz", openResult{err: unix.ENOENT}}, - "nonlexical-level2-abs-open-complete": {"link2/link1_abs/../target", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, + "nonlexical-level2-abs-open-complete1": {"link2/link1_abs/../target", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, + "nonlexical-level2-abs-open-complete2": {"link2/link1_abs/../target/", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, + "nonlexical-level2-abs-open-complete3": {"link2/link1_abs/../target//", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, "nonlexical-level2-abs-open-partial": {"link2/link1_abs/../target/foo", openResult{err: unix.ENOENT}}, "nonlexical-level2-abs-open-partial-dotdot": {"link2/link1_abs/../target/../target/foo/bar/../baz", openResult{err: unix.ENOENT}}, - "nonlexical-level2-rel-abs-complete": {"link2/link1_rel/target_abs", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, + "nonlexical-level2-rel-abs-complete1": {"link2/link1_rel/target_abs", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, + "nonlexical-level2-rel-abs-complete2": {"link2/link1_rel/target_abs/", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, + "nonlexical-level2-rel-abs-complete3": {"link2/link1_rel/target_abs//", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, "nonlexical-level2-rel-abs-partial": {"link2/link1_rel/target_abs/foo", openResult{err: unix.ENOENT}}, "nonlexical-level2-rel-abs-partial-dotdot": {"link2/link1_rel/target_abs/../target/foo/bar/../baz", openResult{err: unix.ENOENT}}, - "nonlexical-level2-rel-rel-complete": {"link2/link1_rel/target_rel", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, + "nonlexical-level2-rel-rel-complete1": {"link2/link1_rel/target_rel", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, + "nonlexical-level2-rel-rel-complete2": {"link2/link1_rel/target_rel/", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, + "nonlexical-level2-rel-rel-complete3": {"link2/link1_rel/target_rel//", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, "nonlexical-level2-rel-rel-partial": {"link2/link1_rel/target_rel/foo", openResult{err: unix.ENOENT}}, "nonlexical-level2-rel-rel-partial-dotdot": {"link2/link1_rel/target_rel/../target/foo/bar/../baz", openResult{err: unix.ENOENT}}, - "nonlexical-level2-rel-open-complete": {"link2/link1_rel/../target", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, + "nonlexical-level2-rel-open-complete1": {"link2/link1_rel/../target", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, + "nonlexical-level2-rel-open-complete2": {"link2/link1_rel/../target/", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, + "nonlexical-level2-rel-open-complete3": {"link2/link1_rel/../target//", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, "nonlexical-level2-rel-open-partial": {"link2/link1_rel/../target/foo", openResult{err: unix.ENOENT}}, "nonlexical-level2-rel-open-partial-dotdot": {"link2/link1_rel/../target/../target/foo/bar/../baz", openResult{err: unix.ENOENT}}, - "nonlexical-level3-abs-complete": {"link3/target_abs", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, + "nonlexical-level3-abs-complete1": {"link3/target_abs", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, + "nonlexical-level3-abs-complete2": {"link3/target_abs/", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, + "nonlexical-level3-abs-complete3": {"link3/target_abs//", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, "nonlexical-level3-abs-partial": {"link3/target_abs/foo", openResult{err: unix.ENOENT}}, "nonlexical-level3-abs-partial-dotdot": {"link3/target_abs/../target/foo/bar/../baz", openResult{err: unix.ENOENT}}, - "nonlexical-level3-rel-complete": {"link3/target_rel", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, + "nonlexical-level3-rel-complete1": {"link3/target_rel", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, + "nonlexical-level3-rel-complete2": {"link3/target_rel/", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, + "nonlexical-level3-rel-complete3": {"link3/target_rel//", openResult{handlePath: "/target", fileType: unix.S_IFDIR}}, "nonlexical-level3-rel-partial": {"link3/target_rel/foo", openResult{err: unix.ENOENT}}, "nonlexical-level3-rel-partial-dotdot": {"link3/target_rel/../target/foo/bar/../baz", openResult{err: unix.ENOENT}}, // Partial lookups due to hitting a non-directory. + "partial-nondir-slash1": {"b/c/file/", openResult{err: unix.ENOTDIR}}, + "partial-nondir-slash2": {"b/c/file//", openResult{err: unix.ENOTDIR}}, "partial-nondir-dot": {"b/c/file/.", openResult{err: unix.ENOTDIR}}, "partial-nondir-dotdot1": {"b/c/file/..", openResult{err: unix.ENOTDIR}}, "partial-nondir-dotdot2": {"b/c/file/../foo/bar", openResult{err: unix.ENOTDIR}}, + "partial-nondir-symlink-slash1": {"b-file/", openResult{err: unix.ENOTDIR}}, + "partial-nondir-symlink-slash2": {"b-file//", openResult{err: unix.ENOTDIR}}, "partial-nondir-symlink-dot": {"b-file/.", openResult{err: unix.ENOTDIR}}, "partial-nondir-symlink-dotdot1": {"b-file/..", openResult{err: unix.ENOTDIR}}, "partial-nondir-symlink-dotdot2": {"b-file/../foo/bar", openResult{err: unix.ENOTDIR}}, + "partial-fifo-slash1": {"b/fifo/", openResult{err: unix.ENOTDIR}}, + "partial-fifo-slash2": {"b/fifo//", openResult{err: unix.ENOTDIR}}, "partial-fifo-dot": {"b/fifo/.", openResult{err: unix.ENOTDIR}}, "partial-fifo-dotdot1": {"b/fifo/..", openResult{err: unix.ENOTDIR}}, "partial-fifo-dotdot2": {"b/fifo/../foo/bar", openResult{err: unix.ENOTDIR}}, + "partial-sock-slash1": {"b/sock/", openResult{err: unix.ENOTDIR}}, + "partial-sock-slash2": {"b/sock//", openResult{err: unix.ENOTDIR}}, "partial-sock-dot": {"b/sock/.", openResult{err: unix.ENOTDIR}}, "partial-sock-dotdot1": {"b/sock/..", openResult{err: unix.ENOTDIR}}, "partial-sock-dotdot2": {"b/sock/../foo/bar", openResult{err: unix.ENOTDIR}},