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,40 +80,55 @@ import (
1780 "github.com/moby/sys/reexec/internal/reexecoverride"
1881)
1982
20- var registeredInitializers = make (map [string ]func ())
83+ var entrypoints = make (map [string ]func ())
2184
22- // Register adds an initialization func under the specified name. It panics
23- // if the given name is already registered.
24- func Register (name string , initializer func ()) {
85+ // Register associates name with an entrypoint function to be executed when
86+ // the current binary is invoked with argv[0] equal to name.
87+ //
88+ // Register is not safe for concurrent use; entrypoints must be registered
89+ // during program initialization, before calling [Init].
90+ // It panics if name contains a path component or is already registered.
91+ func Register (name string , entrypoint func ()) {
2592 if filepath .Base (name ) != name {
2693 panic (fmt .Sprintf ("reexec func does not expect a path component: %q" , name ))
2794 }
28- if _ , exists := registeredInitializers [name ]; exists {
95+ if _ , exists := entrypoints [name ]; exists {
2996 panic (fmt .Sprintf ("reexec func already registered under name %q" , name ))
3097 }
3198
32- registeredInitializers [name ] = initializer
99+ entrypoints [name ] = entrypoint
33100}
34101
35- // Init is called as the first part of the exec process and returns true if an
36- // initialization function was called.
102+ // Init checks whether the current process was invoked under a registered name
103+ // (based on filepath.Base(os.Args[0])).
104+ //
105+ // If a matching entrypoint is found, it is executed and Init returns true. In
106+ // that case, the caller should stop normal main execution. If no match is found,
107+ // Init returns false and normal execution should continue.
37108func Init () bool {
38- if initializer , ok := registeredInitializers [filepath .Base (os .Args [0 ])]; ok {
39- initializer ()
109+ if entrypoint , ok := entrypoints [filepath .Base (os .Args [0 ])]; ok {
110+ entrypoint ()
40111 return true
41112 }
42113 return false
43114}
44115
45- // Command returns an [*exec.Cmd] with its Path set to the path of the current
46- // binary using the result of [Self].
116+ // Command returns an [*exec.Cmd] configured to re-execute the current binary,
117+ // using the path returned by [Self].
118+ //
119+ // The first element of args becomes argv[0] of the new process and is used by
120+ // [Init] to select a registered entrypoint.
121+ //
122+ // On Linux, the Pdeathsig of [*exec.Cmd.SysProcAttr] is set to SIGTERM. This
123+ // signal is sent to the child process when the OS thread that created it dies,
124+ // which helps ensure the child does not outlive its parent unexpectedly. See
125+ // [PR_SET_PDEATHSIG(2const)] and [go.dev/issue/27505] for details.
47126//
48- // On Linux, the Pdeathsig of [*exec.Cmd.SysProcAttr] is set to SIGTERM.
49- // This signal is sent to the process when the OS thread that created
50- // the process dies.
127+ // It is the caller's responsibility to ensure that the creating thread is not
128+ // terminated prematurely.
51129//
52- // It is the caller's responsibility to ensure that the creating thread is
53- // not terminated prematurely. See https://go.dev/issue/27505 for more details.
130+ // [PR_SET_PDEATHSIG(2const)]: https://man7.org/linux/man-pages/man2/PR_SET_PDEATHSIG.2const.html
131+ // [go.dev/issue/27505]: https://go.dev/issue/27505
54132func Command (args ... string ) * exec.Cmd {
55133 return command (args ... )
56134}
@@ -69,16 +147,14 @@ func CommandContext(ctx context.Context, args ...string) *exec.Cmd {
69147 return commandContext (ctx , args ... )
70148}
71149
72- // Self returns the path to the current process's binary .
150+ // Self returns the executable path of the current process.
73151//
74- // On Linux, it returns "/proc/self/exe", which provides the in-memory version
75- // of the current binary. This makes it safe to delete or replace the on-disk
76- // binary (os.Args[0]) .
152+ // On Linux, it returns "/proc/self/exe", which references the in-memory image
153+ // of the running binary. This allows the on-disk binary (os.Args[0]) to be
154+ // replaced or deleted without affecting re-execution .
77155//
78- // On Other platforms, it attempts to look up the absolute path for os.Args[0],
79- // or otherwise returns os.Args[0] as-is. For example if current binary is
80- // "my-binary" at "/usr/bin/" (or "my-binary.exe" at "C:\" on Windows),
81- // then it returns "/usr/bin/my-binary" and "C:\my-binary.exe" respectively.
156+ // On other platforms, it attempts to resolve os.Args[0] to an absolute path.
157+ // If resolution fails, it returns os.Args[0] unchanged.
82158func Self () string {
83159 if argv0 , ok := reexecoverride .Argv0 (); ok {
84160 return naiveSelf (argv0 )
0 commit comments