| // Copyright 2024 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| //go:build unix |
| |
| package os_test |
| |
| import ( |
| "errors" |
| . "os" |
| "path/filepath" |
| "runtime" |
| "strings" |
| "syscall" |
| "testing" |
| ) |
| |
| func TestGetwdDeep(t *testing.T) { |
| testGetwdDeep(t, false) |
| } |
| |
| func TestGetwdDeepWithPWDSet(t *testing.T) { |
| testGetwdDeep(t, true) |
| } |
| |
| // testGetwdDeep checks that os.Getwd is able to return paths |
| // longer than syscall.PathMax (with or without PWD set). |
| func testGetwdDeep(t *testing.T, setPWD bool) { |
| tempDir := t.TempDir() |
| |
| dir := tempDir |
| t.Chdir(dir) |
| |
| if setPWD { |
| t.Setenv("PWD", dir) |
| } else { |
| // When testing os.Getwd, setting PWD to empty string |
| // is the same as unsetting it, but the latter would |
| // be more complicated since we don't have t.Unsetenv. |
| t.Setenv("PWD", "") |
| } |
| |
| name := strings.Repeat("a", 200) |
| for { |
| if err := Mkdir(name, 0o700); err != nil { |
| t.Fatal(err) |
| } |
| if err := Chdir(name); err != nil { |
| t.Fatal(err) |
| } |
| if setPWD { |
| dir += "/" + name |
| if err := Setenv("PWD", dir); err != nil { |
| t.Fatal(err) |
| } |
| t.Logf(" $PWD len: %d", len(dir)) |
| } |
| |
| wd, err := Getwd() |
| t.Logf("Getwd len: %d", len(wd)) |
| if err != nil { |
| // We can get an EACCES error if we can't read up |
| // to root, which happens on the Android builders. |
| if errors.Is(err, syscall.EACCES) { |
| t.Logf("ignoring EACCES error: %v", err) |
| break |
| } |
| t.Fatal(err) |
| } |
| if setPWD && wd != dir { |
| // It's possible for the stat of PWD to fail |
| // with ENAMETOOLONG, and for getwd to fail for |
| // the same reason, and it's possible for $TMPDIR |
| // to contain a symlink. In that case the fallback |
| // code will not return the same directory. |
| if len(dir) > 1000 { |
| symDir, err := filepath.EvalSymlinks(tempDir) |
| if err == nil && symDir != tempDir { |
| t.Logf("EvalSymlinks(%q) = %q", tempDir, symDir) |
| if strings.Replace(dir, tempDir, symDir, 1) == wd { |
| // Symlink confusion is OK. |
| break |
| } |
| } |
| } |
| |
| t.Fatalf("Getwd: got %q, want same value as $PWD: %q", wd, dir) |
| } |
| // Ideally the success criterion should be len(wd) > syscall.PathMax, |
| // but the latter is not public for some platforms, so use Stat(wd). |
| // When it fails with ENAMETOOLONG, it means: |
| // - wd is longer than PathMax; |
| // - Getwd have used the slow fallback code. |
| // |
| // To avoid an endless loop here in case Stat keeps working, |
| // check if len(wd) is above the largest known PathMax among |
| // all Unix platforms (4096, on Linux). |
| if _, err := Stat(wd); err != nil || len(wd) > 4096 { |
| t.Logf("Done; len(wd)=%d", len(wd)) |
| // Most systems return ENAMETOOLONG. |
| // Dragonfly returns EFAULT. |
| switch { |
| case err == nil: |
| case errors.Is(err, syscall.ENAMETOOLONG): |
| case runtime.GOOS == "dragonfly" && errors.Is(err, syscall.EFAULT): |
| default: |
| t.Fatalf("unexpected Stat error: %v", err) |
| } |
| break |
| } |
| } |
| } |