1- // Package reexec facilitates the busybox style reexec of a binary.
1+ // Package reexec implements a BusyBox-style “multi-call binary” re-execution
2+ // pattern for Go programs.
23//
3- // Handlers can be registered with a name and the argv 0 of the exec of
4- // the binary will be used to find and execute custom init paths.
4+ // Callers register named entrypoints through [Register]. When the current
5+ // process starts, [Init] compares filepath.Base(os.Args[0]) against the set of
6+ // registered entrypoints. If it matches, Init runs that entrypoint and returns
7+ // true.
58//
6- // It is used to work around forking limitations when using Go.
9+ // A matched entrypoint is analogous to main for that invocation mode: callers
10+ // typically call Init at the start of main and return immediately when it
11+ // reports a match.
12+ //
13+ // Example uses:
14+ //
15+ // - For multi-call binaries: multiple symlinks/hardlinks point at one binary,
16+ // and argv[0] (see [execve(2)]) selects the entrypoint.
17+ // - For programmatic re-exec: a parent launches the current binary (via
18+ // [Command] or [CommandContext]) with argv[0] set to a registered entrypoint
19+ // name to run a specific mode.
20+ //
21+ // The programmatic re-exec pattern is commonly used as a safe alternative to
22+ // fork-without-exec in Go, since the runtime is not fork-safe once multiple
23+ // threads exist (see [os.StartProcess] and https://go.dev/issue/27505).
24+ //
25+ // Example (multi-call binary):
26+ //
27+ // package main
28+ //
29+ // import (
30+ // "fmt"
31+ //
32+ // "github.com/moby/sys/reexec"
33+ // )
34+ //
35+ // func init() {
36+ // reexec.Register("example-foo", func() {
37+ // fmt.Println("Hello from entrypoint example-foo")
38+ // })
39+ // reexec.Register("example-bar", entrypointBar)
40+ // }
41+ //
42+ // func main() {
43+ // if reexec.Init() {
44+ // // Matched a reexec entrypoint; stop normal main execution.
45+ // return
46+ // }
47+ // fmt.Println("Hello main")
48+ // }
49+ //
50+ // func entrypointBar() {
51+ // fmt.Println("Hello from entrypoint example-bar")
52+ // }
53+ //
54+ // To try it:
55+ //
56+ // go build -o example .
57+ // ln -s example example-foo
58+ // ln -s example example-bar
59+ //
60+ // ./example
61+ // # Hello main
62+ //
63+ // ./example-foo
64+ // # Hello from entrypoint example-foo
65+ //
66+ // ./example-bar
67+ // # Hello from entrypoint example-bar
68+ //
69+ // [execve(2)]: https://man7.org/linux/man-pages/man2/execve.2.html
770package reexec
871
972import (
@@ -17,38 +80,53 @@ import (
1780
1881var registeredInitializers = make (map [string ]func ())
1982
20- // Register adds an initialization func under the specified name. It panics
21- // if the given name is already registered.
22- func Register (name string , initializer func ()) {
83+ // Register associates name with an entrypoint function to be executed when
84+ // the current binary is invoked with argv[0] equal to name.
85+ //
86+ // Register is not safe for concurrent use; entrypoints must be registered
87+ // during program initialization, before calling [Init].
88+ // It panics if name contains a path component or is already registered.
89+ func Register (name string , entrypoint func ()) {
2390 if filepath .Base (name ) != name {
2491 panic (fmt .Sprintf ("reexec func does not expect a path component: %q" , name ))
2592 }
2693 if _ , exists := registeredInitializers [name ]; exists {
2794 panic (fmt .Sprintf ("reexec func already registered under name %q" , name ))
2895 }
2996
30- registeredInitializers [name ] = initializer
97+ registeredInitializers [name ] = entrypoint
3198}
3299
33- // Init is called as the first part of the exec process and returns true if an
34- // initialization function was called.
100+ // Init checks whether the current process was invoked under a registered name
101+ // (based on filepath.Base(os.Args[0])).
102+ //
103+ // If a matching entrypoint is found, it is executed and Init returns true. In
104+ // that case, the caller should stop normal main execution. If no match is found,
105+ // Init returns false and normal execution should continue.
35106func Init () bool {
36- if initializer , ok := registeredInitializers [filepath .Base (os .Args [0 ])]; ok {
37- initializer ()
107+ if entrypoint , ok := registeredInitializers [filepath .Base (os .Args [0 ])]; ok {
108+ entrypoint ()
38109 return true
39110 }
40111 return false
41112}
42113
43- // Command returns an [*exec.Cmd] with its Path set to the path of the current
44- // binary using the result of [Self].
114+ // Command returns an [*exec.Cmd] configured to re-execute the current binary,
115+ // using the path returned by [Self].
116+ //
117+ // The first element of args becomes argv[0] of the new process and is used by
118+ // [Init] to select a registered entrypoint.
119+ //
120+ // On Linux, the Pdeathsig of [*exec.Cmd.SysProcAttr] is set to SIGTERM. This
121+ // signal is sent to the child process when the OS thread that created it dies,
122+ // which helps ensure the child does not outlive its parent unexpectedly. See
123+ // [PR_SET_PDEATHSIG(2const)] and [go.dev/issue/27505] for details.
45124//
46- // On Linux, the Pdeathsig of [*exec.Cmd.SysProcAttr] is set to SIGTERM.
47- // This signal is sent to the process when the OS thread that created
48- // the process dies.
125+ // It is the caller's responsibility to ensure that the creating thread is not
126+ // terminated prematurely.
49127//
50- // It is the caller's responsibility to ensure that the creating thread is
51- // not terminated prematurely. See https://go.dev/issue/27505 for more details.
128+ // [PR_SET_PDEATHSIG(2const)]: https://man7.org/linux/man-pages/man2/PR_SET_PDEATHSIG.2const.html
129+ // [go.dev/issue/27505]: https://go.dev/issue/27505
52130func Command (args ... string ) * exec.Cmd {
53131 return command (args ... )
54132}
@@ -67,16 +145,14 @@ func CommandContext(ctx context.Context, args ...string) *exec.Cmd {
67145 return commandContext (ctx , args ... )
68146}
69147
70- // Self returns the path to the current process's binary .
148+ // Self returns the executable path of the current process.
71149//
72- // On Linux, it returns "/proc/self/exe", which provides the in-memory version
73- // of the current binary. This makes it safe to delete or replace the on-disk
74- // binary (os.Args[0]) .
150+ // On Linux, it returns "/proc/self/exe", which references the in-memory image
151+ // of the running binary. This allows the on-disk binary (os.Args[0]) to be
152+ // replaced or deleted without affecting re-execution .
75153//
76- // On Other platforms, it attempts to look up the absolute path for os.Args[0],
77- // or otherwise returns os.Args[0] as-is. For example if current binary is
78- // "my-binary" at "/usr/bin/" (or "my-binary.exe" at "C:\" on Windows),
79- // then it returns "/usr/bin/my-binary" and "C:\my-binary.exe" respectively.
154+ // On other platforms, it attempts to resolve os.Args[0] to an absolute path.
155+ // If resolution fails, it returns os.Args[0] unchanged.
80156func Self () string {
81157 if runtime .GOOS == "linux" {
82158 return "/proc/self/exe"
0 commit comments