-
Notifications
You must be signed in to change notification settings - Fork 45
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #179 from thaJeztah/integrate_reexec
integrate pkg/reexec
- Loading branch information
Showing
7 changed files
with
187 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module github.com/moby/sys/reexec | ||
|
||
go 1.18 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
// Package reexec facilitates the busybox style reexec of a binary. | ||
// | ||
// Handlers can be registered with a name and the argv 0 of the exec of | ||
// the binary will be used to find and execute custom init paths. | ||
// | ||
// It is used to work around forking limitations when using Go. | ||
package reexec | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"os/exec" | ||
"path/filepath" | ||
"runtime" | ||
) | ||
|
||
var registeredInitializers = make(map[string]func()) | ||
|
||
// Register adds an initialization func under the specified name. It panics | ||
// if the given name is already registered. | ||
func Register(name string, initializer func()) { | ||
if _, exists := registeredInitializers[name]; exists { | ||
panic(fmt.Sprintf("reexec func already registered under name %q", name)) | ||
} | ||
|
||
registeredInitializers[name] = initializer | ||
} | ||
|
||
// Init is called as the first part of the exec process and returns true if an | ||
// initialization function was called. | ||
func Init() bool { | ||
if initializer, ok := registeredInitializers[os.Args[0]]; ok { | ||
initializer() | ||
return true | ||
} | ||
return false | ||
} | ||
|
||
// Command returns an [*exec.Cmd] with its Path set to the path of the current | ||
// binary using the result of [Self]. | ||
// | ||
// On Linux, the Pdeathsig of [*exec.Cmd.SysProcAttr] is set to SIGTERM. | ||
// This signal is sent to the process when the OS thread that created | ||
// the process dies. | ||
// | ||
// It is the caller's responsibility to ensure that the creating thread is | ||
// not terminated prematurely. See https://go.dev/issue/27505 for more details. | ||
func Command(args ...string) *exec.Cmd { | ||
return command(args...) | ||
} | ||
|
||
// Self returns the path to the current process's binary. | ||
// | ||
// On Linux, it returns "/proc/self/exe", which provides the in-memory version | ||
// of the current binary. This makes it safe to delete or replace the on-disk | ||
// binary (os.Args[0]). | ||
// | ||
// On Other platforms, it attempts to look up the absolute path for os.Args[0], | ||
// or otherwise returns os.Args[0] as-is. For example if current binary is | ||
// "my-binary" at "/usr/bin/" (or "my-binary.exe" at "C:\" on Windows), | ||
// then it returns "/usr/bin/my-binary" and "C:\my-binary.exe" respectively. | ||
func Self() string { | ||
if runtime.GOOS == "linux" { | ||
return "/proc/self/exe" | ||
} | ||
return naiveSelf() | ||
} | ||
|
||
func naiveSelf() string { | ||
name := os.Args[0] | ||
if filepath.Base(name) == name { | ||
if lp, err := exec.LookPath(name); err == nil { | ||
return lp | ||
} | ||
} | ||
// handle conversion of relative paths to absolute | ||
if absName, err := filepath.Abs(name); err == nil { | ||
return absName | ||
} | ||
// if we couldn't get absolute name, return original | ||
// (NOTE: Go only errors on Abs() if os.Getwd fails) | ||
return name | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package reexec | ||
|
||
import ( | ||
"os/exec" | ||
"syscall" | ||
) | ||
|
||
func command(args ...string) *exec.Cmd { | ||
return &exec.Cmd{ | ||
Path: Self(), | ||
Args: args, | ||
SysProcAttr: &syscall.SysProcAttr{ | ||
Pdeathsig: syscall.SIGTERM, | ||
}, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
//go:build !linux | ||
|
||
package reexec | ||
|
||
import ( | ||
"os/exec" | ||
) | ||
|
||
func command(args ...string) *exec.Cmd { | ||
return &exec.Cmd{ | ||
Path: Self(), | ||
Args: args, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package reexec | ||
|
||
import ( | ||
"os" | ||
"os/exec" | ||
"testing" | ||
) | ||
|
||
const testReExec = "test-reexec" | ||
|
||
func init() { | ||
Register(testReExec, func() { | ||
panic("Return Error") | ||
}) | ||
Init() | ||
} | ||
|
||
func TestRegister(t *testing.T) { | ||
defer func() { | ||
if r := recover(); r != nil { | ||
const expected = `reexec func already registered under name "test-reexec"` | ||
if r != expected { | ||
t.Errorf("got %q, want %q", r, expected) | ||
} | ||
} | ||
}() | ||
Register(testReExec, func() {}) | ||
} | ||
|
||
func TestCommand(t *testing.T) { | ||
cmd := Command(testReExec) | ||
w, err := cmd.StdinPipe() | ||
if err != nil { | ||
t.Fatalf("Error on pipe creation: %v", err) | ||
} | ||
defer w.Close() | ||
|
||
err = cmd.Start() | ||
if err != nil { | ||
t.Fatalf("Error on re-exec cmd: %v", err) | ||
} | ||
err = cmd.Wait() | ||
const expected = "exit status 2" | ||
if err == nil || err.Error() != expected { | ||
t.Fatalf("got %v, want %v", err, expected) | ||
} | ||
} | ||
|
||
func TestNaiveSelf(t *testing.T) { | ||
if os.Getenv("TEST_CHECK") == "1" { | ||
os.Exit(2) | ||
} | ||
cmd := exec.Command(naiveSelf(), "-test.run=TestNaiveSelf") | ||
cmd.Env = append(os.Environ(), "TEST_CHECK=1") | ||
err := cmd.Start() | ||
if err != nil { | ||
t.Fatalf("Unable to start command: %v", err) | ||
} | ||
err = cmd.Wait() | ||
const expected = "exit status 2" | ||
if err == nil || err.Error() != expected { | ||
t.Fatalf("got %v, want %v", err, expected) | ||
} | ||
|
||
os.Args[0] = "mkdir" | ||
if naiveSelf() == os.Args[0] { | ||
t.Fatalf("Expected naiveSelf to resolve the location of mkdir") | ||
} | ||
} |