Skip to content

Commit cfe5d79

Browse files
egonelbretklauser
authored andcommitted
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 <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]> Trust: Tobias Klauser <[email protected]>
1 parent ecfce58 commit cfe5d79

File tree

2 files changed

+78
-8
lines changed

2 files changed

+78
-8
lines changed

src/os/executable_procfs.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,7 @@ import (
1212
"runtime"
1313
)
1414

15-
// We query the executable path at init time to avoid the problem of
16-
// readlink returns a path appended with " (deleted)" when the original
17-
// binary gets deleted.
18-
var executablePath, executablePathErr = func() (string, error) {
15+
func executable() (string, error) {
1916
var procfn string
2017
switch runtime.GOOS {
2118
default:
@@ -25,9 +22,17 @@ var executablePath, executablePathErr = func() (string, error) {
2522
case "netbsd":
2623
procfn = "/proc/curproc/exe"
2724
}
28-
return Readlink(procfn)
29-
}()
25+
path, err := Readlink(procfn)
3026

31-
func executable() (string, error) {
32-
return executablePath, executablePathErr
27+
// When the executable has been deleted then Readlink returns a
28+
// path appended with " (deleted)".
29+
return stringsTrimSuffix(path, " (deleted)"), err
30+
}
31+
32+
// stringsTrimSuffix is the same as strings.TrimSuffix.
33+
func stringsTrimSuffix(s, suffix string) string {
34+
if len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix {
35+
return s[:len(s)-len(suffix)]
36+
}
37+
return s
3338
}

src/os/executable_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,68 @@ func init() {
8686
os.Exit(0)
8787
}
8888
}
89+
90+
func TestExecutableDeleted(t *testing.T) {
91+
testenv.MustHaveExec(t)
92+
switch runtime.GOOS {
93+
case "windows":
94+
t.Skip("windows does not support deleting running binary")
95+
case "openbsd", "freebsd":
96+
t.Skipf("%v does not support reading deleted binary name", runtime.GOOS)
97+
}
98+
99+
dir := t.TempDir()
100+
101+
src := filepath.Join(dir, "testdel.go")
102+
exe := filepath.Join(dir, "testdel.exe")
103+
104+
err := os.WriteFile(src, []byte(testExecutableDeletion), 0666)
105+
if err != nil {
106+
t.Fatal(err)
107+
}
108+
109+
out, err := osexec.Command(testenv.GoToolPath(t), "build", "-o", exe, src).CombinedOutput()
110+
t.Logf("build output:\n%s", out)
111+
if err != nil {
112+
t.Fatal(err)
113+
}
114+
115+
out, err = osexec.Command(exe).CombinedOutput()
116+
t.Logf("exec output:\n%s", out)
117+
if err != nil {
118+
t.Fatal(err)
119+
}
120+
}
121+
122+
const testExecutableDeletion = `package main
123+
124+
import (
125+
"fmt"
126+
"os"
127+
)
128+
129+
func main() {
130+
before, err := os.Executable()
131+
if err != nil {
132+
fmt.Fprintf(os.Stderr, "failed to read executable name before deletion: %v\n", err)
133+
os.Exit(1)
134+
}
135+
136+
err = os.Remove(before)
137+
if err != nil {
138+
fmt.Fprintf(os.Stderr, "failed to remove executable: %v\n", err)
139+
os.Exit(1)
140+
}
141+
142+
after, err := os.Executable()
143+
if err != nil {
144+
fmt.Fprintf(os.Stderr, "failed to read executable name after deletion: %v\n", err)
145+
os.Exit(1)
146+
}
147+
148+
if before != after {
149+
fmt.Fprintf(os.Stderr, "before and after do not match: %v != %v\n", before, after)
150+
os.Exit(1)
151+
}
152+
}
153+
`

0 commit comments

Comments
 (0)