diff options
author | Egon Elbre <egonelbre@gmail.com> | 2021-04-20 17:38:54 +0300 |
---|---|---|
committer | Tobias Klauser <tobias.klauser@gmail.com> | 2021-04-22 21:03:32 +0000 |
commit | cfe5d79c5c2c9888a0e56e089dca99e405a225b9 (patch) | |
tree | 98ecca28b1161a03395b5f737bf3e0230fab7a00 /src/os | |
parent | ecfce58965da6017e02f5fc5c03eda52fc41c8d6 (diff) | |
download | go-cfe5d79c5c2c9888a0e56e089dca99e405a225b9.tar.gz go-cfe5d79c5c2c9888a0e56e089dca99e405a225b9.zip |
os: depend on Readlink only when necessary
Currently Readlink gets linked into the binary even when Executable is
not needed.
This reduces a simple "os.Stdout.Write([]byte("hello"))" by ~10KiB.
Previously the executable path was read during init time, because
deleting the executable would make "Readlink" return "(deleted)" suffix.
There's probably a slight chance that the init time reading would return
it anyways.
Updates #6853
Change-Id: Ic76190c5b64d9320ceb489cd6a553108614653d1
Reviewed-on: https://go-review.googlesource.com/c/go/+/311790
Run-TryBot: Tobias Klauser <tobias.klauser@gmail.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Trust: Tobias Klauser <tobias.klauser@gmail.com>
Diffstat (limited to 'src/os')
-rw-r--r-- | src/os/executable_procfs.go | 21 | ||||
-rw-r--r-- | src/os/executable_test.go | 65 |
2 files changed, 78 insertions, 8 deletions
diff --git a/src/os/executable_procfs.go b/src/os/executable_procfs.go index 9c64a0d474..76ba0e6d08 100644 --- a/src/os/executable_procfs.go +++ b/src/os/executable_procfs.go @@ -12,10 +12,7 @@ import ( "runtime" ) -// We query the executable path at init time to avoid the problem of -// readlink returns a path appended with " (deleted)" when the original -// binary gets deleted. -var executablePath, executablePathErr = func() (string, error) { +func executable() (string, error) { var procfn string switch runtime.GOOS { default: @@ -25,9 +22,17 @@ var executablePath, executablePathErr = func() (string, error) { case "netbsd": procfn = "/proc/curproc/exe" } - return Readlink(procfn) -}() + path, err := Readlink(procfn) -func executable() (string, error) { - return executablePath, executablePathErr + // When the executable has been deleted then Readlink returns a + // path appended with " (deleted)". + return stringsTrimSuffix(path, " (deleted)"), err +} + +// stringsTrimSuffix is the same as strings.TrimSuffix. +func stringsTrimSuffix(s, suffix string) string { + if len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix { + return s[:len(s)-len(suffix)] + } + return s } diff --git a/src/os/executable_test.go b/src/os/executable_test.go index f25ee0c95a..f682105fa6 100644 --- a/src/os/executable_test.go +++ b/src/os/executable_test.go @@ -86,3 +86,68 @@ func init() { os.Exit(0) } } + +func TestExecutableDeleted(t *testing.T) { + testenv.MustHaveExec(t) + switch runtime.GOOS { + case "windows": + t.Skip("windows does not support deleting running binary") + case "openbsd", "freebsd": + t.Skipf("%v does not support reading deleted binary name", runtime.GOOS) + } + + dir := t.TempDir() + + src := filepath.Join(dir, "testdel.go") + exe := filepath.Join(dir, "testdel.exe") + + err := os.WriteFile(src, []byte(testExecutableDeletion), 0666) + if err != nil { + t.Fatal(err) + } + + out, err := osexec.Command(testenv.GoToolPath(t), "build", "-o", exe, src).CombinedOutput() + t.Logf("build output:\n%s", out) + if err != nil { + t.Fatal(err) + } + + out, err = osexec.Command(exe).CombinedOutput() + t.Logf("exec output:\n%s", out) + if err != nil { + t.Fatal(err) + } +} + +const testExecutableDeletion = `package main + +import ( + "fmt" + "os" +) + +func main() { + before, err := os.Executable() + if err != nil { + fmt.Fprintf(os.Stderr, "failed to read executable name before deletion: %v\n", err) + os.Exit(1) + } + + err = os.Remove(before) + if err != nil { + fmt.Fprintf(os.Stderr, "failed to remove executable: %v\n", err) + os.Exit(1) + } + + after, err := os.Executable() + if err != nil { + fmt.Fprintf(os.Stderr, "failed to read executable name after deletion: %v\n", err) + os.Exit(1) + } + + if before != after { + fmt.Fprintf(os.Stderr, "before and after do not match: %v != %v\n", before, after) + os.Exit(1) + } +} +` |