]> Cypherpunks repositories - gostls13.git/commitdiff
os: depend on Readlink only when necessary
authorEgon Elbre <egonelbre@gmail.com>
Tue, 20 Apr 2021 14:38:54 +0000 (17:38 +0300)
committerTobias Klauser <tobias.klauser@gmail.com>
Thu, 22 Apr 2021 21:03:32 +0000 (21:03 +0000)
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>

src/os/executable_procfs.go
src/os/executable_test.go

index 9c64a0d4747f692d74824974e97903b2783b7b04..76ba0e6d085e5bac291c47374cd971c49d42e1ce 100644 (file)
@@ -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
 }
index f25ee0c95a1cdf11202b25557a72216c054abedc..f682105fa64a2aa59d92a0cd362a1e39248f019a 100644 (file)
@@ -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)
+       }
+}
+`