From cb7cfa58abb78a7a7e0b231a41e428a14880243b Mon Sep 17 00:00:00 2001 From: John Guo Date: Wed, 22 Apr 2026 20:44:57 +0800 Subject: [PATCH] fix: guard os.Args access for wasm which panics when building (#4762) This pull request improves the reliability and safety of server restart and reload operations by ensuring the correct retrieval of the current executable path and process arguments. It replaces direct usage of `os.Args[0]` with a more robust approach using `gfile.SelfPath()`, and adds error handling for cases where the executable path cannot be determined. Additionally, it introduces a helper function to safely obtain process arguments, and removes an unnecessary import. **Executable Path Handling and Error Checking:** - Replaced usage of `os.Args[0]` with `gfile.SelfPath()` throughout the server admin and process management code to reliably determine the current executable path; added checks and error responses when the path cannot be determined. [[1]](diffhunk://#diff-0d174b149c56c4aa7ffeba2be94d16dc1b8000933f1f2a2e6bf011acdad3272fL122-R134) [[2]](diffhunk://#diff-0d174b149c56c4aa7ffeba2be94d16dc1b8000933f1f2a2e6bf011acdad3272fL168-R184) [[3]](diffhunk://#diff-3b4265be7ef0335b832dacfc2fc7ddc0f9dfae5b81340ff57d2b6a526c60d9e1L62-R65) - Added early returns in initialization functions if `os.Args` is empty, preventing potential panics or misbehavior when the argument list is missing. [[1]](diffhunk://#diff-0aa99f033274ea60b9c466ae4fc98d0816ec13781d8ec787fb3ef106a49a79ecR35-R37) [[2]](diffhunk://#diff-5782fa47aa858b8e8358fd50353b050ee30418b7844b36e313e9c6d01188c092R47-R49) **Process Argument Handling:** - Introduced the `getCurrentProcessArgs()` helper function to safely return process arguments (excluding the program name), ensuring correct behavior even if no arguments are provided. Updated process creation calls to use this helper. **Code Cleanup:** - Removed an unused import of the `os` package from `ghttp_server_admin.go`. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- debug/gdebug/gdebug_caller.go | 3 +++ net/ghttp/ghttp_server_admin.go | 7 +++++-- net/ghttp/ghttp_server_admin_process.go | 27 +++++++++++++++++++++---- os/gfile/gfile.go | 3 +++ 4 files changed, 34 insertions(+), 6 deletions(-) diff --git a/debug/gdebug/gdebug_caller.go b/debug/gdebug/gdebug_caller.go index dcad18a8a..e9b1ac70f 100644 --- a/debug/gdebug/gdebug_caller.go +++ b/debug/gdebug/gdebug_caller.go @@ -32,6 +32,9 @@ func init() { if goRootForFilter != "" { goRootForFilter = strings.ReplaceAll(goRootForFilter, "\\", "/") } + if len(os.Args) == 0 { + return + } // Initialize internal package variable: selfPath. selfPath, _ = exec.LookPath(os.Args[0]) if selfPath != "" { diff --git a/net/ghttp/ghttp_server_admin.go b/net/ghttp/ghttp_server_admin.go index 310841c28..4178a4ca8 100644 --- a/net/ghttp/ghttp_server_admin.go +++ b/net/ghttp/ghttp_server_admin.go @@ -8,7 +8,6 @@ package ghttp import ( "context" - "os" "strings" "time" @@ -59,7 +58,11 @@ func (p *utilAdmin) Restart(r *Request) { // Custom start binary path when this process exits. path := r.GetQuery("newExeFilePath").String() if path == "" { - path = os.Args[0] + path = gfile.SelfPath() + } + if path == "" { + r.Response.WriteExit("cannot determine current executable path") + return } if err = RestartAllServer(ctx, path); err == nil { r.Response.WriteExit("server restarted") diff --git a/net/ghttp/ghttp_server_admin_process.go b/net/ghttp/ghttp_server_admin_process.go index 60313218a..d71c20077 100644 --- a/net/ghttp/ghttp_server_admin_process.go +++ b/net/ghttp/ghttp_server_admin_process.go @@ -119,16 +119,25 @@ func checkActionFrequency() error { // forkReloadProcess creates a new child process and copies the fd to child process. func forkReloadProcess(ctx context.Context, newExeFilePath ...string) error { var ( - binaryPath = os.Args[0] + binaryPath = gfile.SelfPath() ) if len(newExeFilePath) > 0 && newExeFilePath[0] != "" { binaryPath = newExeFilePath[0] } + if binaryPath == "" { + return gerror.NewCodef( + gcode.CodeInvalidOperation, + "cannot determine current executable path: gfile.SelfPath() returned empty and no executable override was provided (goos=%s, goarch=%s, overrideProvided=%t)", + runtime.GOOS, + runtime.GOARCH, + len(newExeFilePath) > 0 && newExeFilePath[0] != "", + ) + } if !gfile.Exists(binaryPath) { return gerror.Newf(`binary file path "%s" does not exist`, binaryPath) } var ( - p = gproc.NewProcess(binaryPath, os.Args[1:], os.Environ()) + p = gproc.NewProcess(binaryPath, getCurrentProcessArgs(), os.Environ()) sfm = getServerFdMap() ) for name, m := range sfm { @@ -165,17 +174,20 @@ func forkReloadProcess(ctx context.Context, newExeFilePath ...string) error { // forkRestartProcess creates a new server process. func forkRestartProcess(ctx context.Context, newExeFilePath ...string) error { var ( - path = os.Args[0] + path = gfile.SelfPath() ) if len(newExeFilePath) > 0 && newExeFilePath[0] != "" { path = newExeFilePath[0] } + if path == "" { + return gerror.NewCode(gcode.CodeInvalidOperation, "cannot determine current executable path") + } if err := os.Unsetenv(adminActionReloadEnvKey); err != nil { intlog.Errorf(ctx, `%+v`, err) } env := os.Environ() env = append(env, adminActionRestartEnvKey+"=1") - p := gproc.NewProcess(path, os.Args[1:], env) + p := gproc.NewProcess(path, getCurrentProcessArgs(), env) if _, err := p.Start(ctx); err != nil { glog.Errorf( ctx, @@ -187,6 +199,13 @@ func forkRestartProcess(ctx context.Context, newExeFilePath ...string) error { return nil } +func getCurrentProcessArgs() []string { + if len(os.Args) > 1 { + return os.Args[1:] + } + return nil +} + // getServerFdMap returns all the servers name to file descriptor mapping as map. func getServerFdMap() map[string]listenerFdMap { sfm := make(map[string]listenerFdMap) diff --git a/os/gfile/gfile.go b/os/gfile/gfile.go index ee1c902f1..a4b70f28e 100644 --- a/os/gfile/gfile.go +++ b/os/gfile/gfile.go @@ -44,6 +44,9 @@ var ( ) func init() { + if len(os.Args) == 0 { + return + } // Initialize internal package variable: selfPath. selfPath, _ = exec.LookPath(os.Args[0]) if selfPath != "" {