From eea03676d36fa9bf2aa8830eac28ce0eb35d6f4c Mon Sep 17 00:00:00 2001 From: Royce Remer Date: Sat, 29 Aug 2026 12:54:07 -0700 Subject: [PATCH] fix(process): reap entire process group on cmd.Cancel (#39143) Signed-off-by: Royce Remer Co-authored-by: wxiaoguang --- .golangci.yml | 5 ++ cmd/serv.go | 8 +-- modules/git/gitcmd/command.go | 21 +++---- modules/git/gpg.go | 4 +- modules/graceful/manager.go | 4 -- modules/markup/external/external.go | 4 +- modules/process/command.go | 54 ++++++++++++++++++ modules/process/command_unix.go | 32 +++++++++++ modules/process/command_unix_test.go | 67 +++++++++++++++++++++++ modules/process/command_windows.go | 19 +++++++ modules/process/error.go | 25 --------- modules/process/manager.go | 3 - modules/process/manager_exec.go | 79 --------------------------- modules/process/manager_test.go | 23 -------- modules/process/manager_unix.go | 17 ------ modules/process/manager_windows.go | 15 ----- modules/ssh/ssh.go | 4 +- services/asymkey/commit.go | 4 +- services/asymkey/sign.go | 3 +- services/mailer/sender/sendmail.go | 4 +- tests/integration/gpg_ssh_git_test.go | 6 +- 21 files changed, 198 insertions(+), 203 deletions(-) create mode 100644 modules/process/command.go create mode 100644 modules/process/command_unix.go create mode 100644 modules/process/command_unix_test.go create mode 100644 modules/process/command_windows.go delete mode 100644 modules/process/error.go delete mode 100644 modules/process/manager_exec.go delete mode 100644 modules/process/manager_unix.go delete mode 100644 modules/process/manager_windows.go diff --git a/.golangci.yml b/.golangci.yml index 22ea9b9d2b6..1a674eb3dec 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -62,6 +62,10 @@ linters: desc: "migrations must not depend on the models package. HINT: MIGRATION-STRUCT-FROZEN" - pkg: gitea.dev/modules/structs desc: "migrations must not depend on modules/structs. HINT: MIGRATION-STRUCT-FROZEN" + forbidigo: + forbid: + - pattern: '^(fmt\.Print(|f|ln)|print|println)$' # default + - pattern: '^exec\.CommandContext$' # use our wrapper for graceful termination modernize: disable: - embedlit @@ -140,6 +144,7 @@ linters: - linters: - dupl - errcheck + - forbidigo - staticcheck - unparam path: _test\.go diff --git a/cmd/serv.go b/cmd/serv.go index 00118eea483..583d534d2bc 100644 --- a/cmd/serv.go +++ b/cmd/serv.go @@ -9,7 +9,6 @@ import ( "fmt" "net/url" "os" - "os/exec" "path/filepath" "strconv" "strings" @@ -294,7 +293,7 @@ func runServ(ctx context.Context, c *cli.Command) error { return nil } - var command *exec.Cmd + var command *process.Cmd gitBinPath := filepath.Dir(gitcmd.GitExecutable) // e.g. /usr/bin gitBinVerb := filepath.Join(gitBinPath, verb) // e.g. /usr/bin/git-upload-pack if _, err := os.Stat(gitBinVerb); err != nil { @@ -303,15 +302,14 @@ func runServ(ctx context.Context, c *cli.Command) error { verbFields := strings.SplitN(verb, "-", 2) if len(verbFields) == 2 { // use git binary with the sub-command part: "C:\...\bin\git.exe", "upload-pack", ... - command = exec.CommandContext(ctx, gitcmd.GitExecutable, verbFields[1], results.RepoStoragePath) + command = process.CommandContext(ctx, gitcmd.GitExecutable, verbFields[1], results.RepoStoragePath) } } if command == nil { // by default, use the verb (it has been checked above by allowedCommands) - command = exec.CommandContext(ctx, gitBinVerb, results.RepoStoragePath) + command = process.CommandContext(ctx, gitBinVerb, results.RepoStoragePath) } - process.SetSysProcAttribute(command) command.Dir = setting.RepoRootPath command.Stdout = os.Stdout command.Stdin = os.Stdin diff --git a/modules/git/gitcmd/command.go b/modules/git/gitcmd/command.go index c55b04cd318..4a4eaedd230 100644 --- a/modules/git/gitcmd/command.go +++ b/modules/git/gitcmd/command.go @@ -11,7 +11,6 @@ import ( "fmt" "io" "os" - "os/exec" "path/filepath" "strings" "time" @@ -47,7 +46,7 @@ type Command struct { // otherwise some git commands might overwrite git dir internal files by a repo file. gitDir string - cmd *exec.Cmd + cmd *process.Cmd cmdCtx context.Context cmdCancel process.CancelCauseFunc @@ -432,30 +431,24 @@ func (c *Command) Start(ctx context.Context) (retErr error) { c.cmdStartTime = time.Now() - c.cmd = exec.CommandContext(c.cmdCtx, c.prog, append(c.configArgs, c.args...)...) + c.cmd = process.CommandContext(c.cmdCtx, c.prog, append(c.configArgs, c.args...)...) if c.cmdEnv == nil { c.cmd.Env = os.Environ() } else { c.cmd.Env = c.cmdEnv } - process.SetSysProcAttribute(c.cmd) c.cmd.Env = append(c.cmd.Env, CommonGitCmdEnvs()...) c.cmd.Dir = c.gitDir c.cmd.Stdout = c.cmdStdout c.cmd.Stdin = c.cmdStdin c.cmd.Stderr = c.cmdStderr - c.cmd.Cancel = func() error { - // Golang's default cmd.Cancel only calls Process.Kill(), but here we need to close the parent pipes together: - // * for some commands like "git --batch-xxx", Windows git might have 2 processes (a wrapper and a real git process) - // * on Windows, if parent process is killed (context canceled), the children process won't be killed, and the pipe handles are still open. - // * if we don't close the parent pipes here, the children process won't exit. - // - // There is no such problem on POSIX, while it won't make things worse by closing the parent pipes also on POSIX. - err := c.cmd.Process.Kill() + c.cmd.WithOnCancelGracefully(func() error { + // Need to close the pipes to notify all sub processes to exit. + // Especially on Windows: there is no process group, and we didn't implement process job object (like process group). c.closePipeFiles(c.parentPipeFiles) - return err - } + return nil + }) return c.cmd.Start() } diff --git a/modules/git/gpg.go b/modules/git/gpg.go index 480c1db430a..26b55d22e1b 100644 --- a/modules/git/gpg.go +++ b/modules/git/gpg.go @@ -27,7 +27,7 @@ type CommitSignSettings struct { cachedPublicKeyContent atomic.Pointer[string] } -func (css *CommitSignSettings) PublicKeyContent() (string, error) { +func (css *CommitSignSettings) PublicKeyContent(ctx context.Context) (string, error) { cached := css.cachedPublicKeyContent.Load() if cached != nil { return *cached, nil @@ -43,7 +43,7 @@ func (css *CommitSignSettings) PublicKeyContent() (string, error) { return s, nil } - content, stderr, err := process.GetManager().Exec("gpg -a --export", "gpg", "-a", "--export", css.KeyID) + content, stderr, err := process.CommandContext(ctx, "gpg", "-a", "--export", css.KeyID).OutputString() if err != nil { return "", fmt.Errorf("unable to get default signing key: %s, %s, %w", css.KeyID, stderr, err) } diff --git a/modules/graceful/manager.go b/modules/graceful/manager.go index 24d4c3f61e3..e2595a45924 100644 --- a/modules/graceful/manager.go +++ b/modules/graceful/manager.go @@ -11,7 +11,6 @@ import ( "gitea.dev/modules/gtprof" "gitea.dev/modules/log" - "gitea.dev/modules/process" "gitea.dev/modules/setting" ) @@ -62,9 +61,6 @@ func InitManager(ctx context.Context) { func initManager(ctx context.Context) { initOnce.Do(func() { manager = newGracefulManager(ctx) - - // Set the process default context to the HammerContext - process.DefaultContext = manager.HammerContext() }) } diff --git a/modules/markup/external/external.go b/modules/markup/external/external.go index 0ff010c6ad5..65efbdef300 100644 --- a/modules/markup/external/external.go +++ b/modules/markup/external/external.go @@ -9,7 +9,6 @@ import ( "fmt" "io" "os" - "os/exec" "strings" "gitea.dev/modules/markup" @@ -147,7 +146,7 @@ func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io. processCtx, _, finished := process.GetManager().AddContext(ctx, fmt.Sprintf("Render [%s] for %s", cmdProg, baseLinkSrc)) defer finished() - cmd := exec.CommandContext(processCtx, cmdProg, cmdArgs...) + cmd := process.CommandContext(processCtx, cmdProg, cmdArgs...) cmd.Env = append( os.Environ(), "GITEA_PREFIX_SRC="+baseLinkSrc, @@ -159,7 +158,6 @@ func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io. var stderr bytes.Buffer cmd.Stdout = output cmd.Stderr = &stderr - process.SetSysProcAttribute(cmd) if err := cmd.Run(); err != nil { return fmt.Errorf("%s render run command %s %v failed: %w\nStderr: %s", p.Name(), cmdProg, shellquote.Join(cmdArgs...), err, stderr.String()) diff --git a/modules/process/command.go b/modules/process/command.go new file mode 100644 index 00000000000..290d6dbc5d2 --- /dev/null +++ b/modules/process/command.go @@ -0,0 +1,54 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package process + +import ( + "bytes" + "context" + "os/exec" +) + +type Cmd struct { + *exec.Cmd + + onCancelUserFunc func() error + termGraceful bool +} + +func (c *Cmd) WithOnCancelGracefully(userFunc func() error) *Cmd { + c.termGraceful, c.onCancelUserFunc = true, userFunc + return c +} + +func (c *Cmd) WithOnCancelForceKill(userFunc func() error) *Cmd { + c.termGraceful, c.onCancelUserFunc = false, userFunc + return c +} + +func (c *Cmd) WithDir(dir string) *Cmd { + c.Cmd.Dir = dir + return c +} + +func (c *Cmd) OutputString() (string, string, error) { + stdout, stderr := &bytes.Buffer{}, &bytes.Buffer{} + c.Cmd.Stdout = stdout + c.Cmd.Stderr = stderr + err := c.Cmd.Run() + return stdout.String(), stderr.String(), err +} + +// CommandContext returns a wrapped exec.Cmd which kills the process group when the context is canceled. +// By default, it uses graceful termination (SIGTERM) on Unix-like systems to make the subprocesses have chances +// to clean up (e.g. remove temporary files or lock files). +func CommandContext(ctx context.Context, name string, arg ...string) *Cmd { + c := &Cmd{Cmd: exec.CommandContext(ctx, name, arg...)} //nolint:forbidigo // wrap it + setSysProcAttribute(c.Cmd) + c.Cmd.Cancel = c.onCancel + + // Unlike exec.CommandContext, we use graceful termination by default to avoid corrupting data or leaving lock files behind. + // If some processes don't respond to SIGTERM, can switch to WithOnCancelForceKill (SIGKILL) to force kill them. + c.termGraceful = true + return c +} diff --git a/modules/process/command_unix.go b/modules/process/command_unix.go new file mode 100644 index 00000000000..a2f437e5da0 --- /dev/null +++ b/modules/process/command_unix.go @@ -0,0 +1,32 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +//go:build unix + +package process + +import ( + "os/exec" + "syscall" + + "gitea.dev/modules/util" +) + +func setSysProcAttribute(cmd *exec.Cmd) { + // When Gitea runs SubProcessA -> SubProcessB and SubProcessA gets killed by context cancel, + // use process group to make sure the sub processes can be killed and reaped instead of leaving defunct(zombie) processes. + cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} +} + +func (c *Cmd) onCancel() error { + if c.onCancelUserFunc != nil { + if err := c.onCancelUserFunc(); err != nil { + return err + } + } + sig := util.Iif(c.termGraceful, syscall.SIGTERM, syscall.SIGKILL) + // kill the whole process group + // ATTENTION: do not access PID after Wait or in other goroutine, it will just cause PID reuse data-race. + // There is no easy solution to implement "first SIGTERM then SIGKILL" in a safe way, only one signal can be sent to the process group. + return syscall.Kill(-c.Process.Pid, sig) +} diff --git a/modules/process/command_unix_test.go b/modules/process/command_unix_test.go new file mode 100644 index 00000000000..0f81e9b2ccd --- /dev/null +++ b/modules/process/command_unix_test.go @@ -0,0 +1,67 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +//go:build unix + +package process + +import ( + "bufio" + "context" + "os" + "strconv" + "strings" + "syscall" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCommandContextCancelKillProcessGroup(t *testing.T) { + ctx, cancel := context.WithCancel(t.Context()) + defer cancel() + + // When executing external commands, there can be multiple subprocesses involved. + // e.g.: Gitea -> "git upload-pack" -> "git pack-objects". + // If the context is canceled (HTTP client disconnects), all subprocesses must terminate. + + // Spawn a shell that itself spawns a long-lived background process and + // prints its PID — mimicking git upload-pack spawning git pack-objects. + + r, w, err := os.Pipe() + require.NoError(t, err) + + cmd := CommandContext(ctx, "sh", "-c", "sleep 600 & echo $!; wait") + cmd.Stdout = w + require.NoError(t, cmd.Start()) + _ = w.Close() // parent keeps only the read end + + t.Cleanup(func() { + // make sure our test doesn't leave a zombie process even if test fails + _ = syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL) + _ = cmd.Wait() + }) + + // Block until the shell prints the grandchild PID. + scanner := bufio.NewScanner(r) + require.True(t, scanner.Scan(), "expected grandchild PID on stdout") + grandchildPID, err := strconv.Atoi(strings.TrimSpace(scanner.Text())) + require.NoError(t, err) + _ = r.Close() + + // Sanity: grandchild must be alive before we cancel. + grandchild, err := os.FindProcess(grandchildPID) + require.NoError(t, err) + require.NoError(t, grandchild.Signal(syscall.Signal(0)), "grandchild should be alive before cancel") + + // Cancel the context + cancel() + _ = cmd.Wait() + + // Subprocess should not exist after context cancel (killed by process group) + assert.Eventually(t, func() bool { + return grandchild.Signal(syscall.Signal(0)) != nil + }, 5*time.Second, 10*time.Millisecond) +} diff --git a/modules/process/command_windows.go b/modules/process/command_windows.go new file mode 100644 index 00000000000..6fc58fae4fa --- /dev/null +++ b/modules/process/command_windows.go @@ -0,0 +1,19 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package process + +import "os/exec" + +// There is no graceful way to kill a process on Windows at the moment + +func setSysProcAttribute(cmd *exec.Cmd) {} + +func (c *Cmd) onCancel() error { + if c.onCancelUserFunc != nil { + if err := c.onCancelUserFunc(); err != nil { + return err + } + } + return c.Process.Kill() +} diff --git a/modules/process/error.go b/modules/process/error.go deleted file mode 100644 index 8f02f652585..00000000000 --- a/modules/process/error.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package process - -import "fmt" - -// Error is a wrapped error describing the error results of Process Execution -type Error struct { - PID IDType - Description string - Err error - CtxErr error - Stdout string - Stderr string -} - -func (err *Error) Error() string { - return fmt.Sprintf("exec(%s:%s) failed: %v(%v) stdout: %s stderr: %s", err.PID, err.Description, err.Err, err.CtxErr, err.Stdout, err.Stderr) -} - -// Unwrap implements the unwrappable implicit interface for go1.13 Unwrap() -func (err *Error) Unwrap() error { - return err.Err -} diff --git a/modules/process/manager.go b/modules/process/manager.go index 74e7c405062..062c21509e7 100644 --- a/modules/process/manager.go +++ b/modules/process/manager.go @@ -23,9 +23,6 @@ import ( var ( manager *Manager managerInit sync.Once - - // DefaultContext is the default context to run processing commands in - DefaultContext = context.Background() ) type ( diff --git a/modules/process/manager_exec.go b/modules/process/manager_exec.go deleted file mode 100644 index c9831737483..00000000000 --- a/modules/process/manager_exec.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package process - -import ( - "bytes" - "context" - "io" - "os/exec" - "time" -) - -// Exec a command and use the default timeout. -func (pm *Manager) Exec(desc, cmdName string, args ...string) (string, string, error) { - return pm.ExecDir(DefaultContext, -1, "", desc, cmdName, args...) -} - -// ExecTimeout a command and use a specific timeout duration. -func (pm *Manager) ExecTimeout(timeout time.Duration, desc, cmdName string, args ...string) (string, string, error) { - return pm.ExecDir(DefaultContext, timeout, "", desc, cmdName, args...) -} - -// ExecDir a command and use the default timeout. -func (pm *Manager) ExecDir(ctx context.Context, timeout time.Duration, dir, desc, cmdName string, args ...string) (string, string, error) { - return pm.ExecDirEnv(ctx, timeout, dir, desc, nil, cmdName, args...) -} - -// ExecDirEnv runs a command in given path and environment variables, and waits for its completion -// up to the given timeout (or DefaultTimeout if -1 is given). -// Returns its complete stdout and stderr -// outputs and an error, if any (including timeout) -func (pm *Manager) ExecDirEnv(ctx context.Context, timeout time.Duration, dir, desc string, env []string, cmdName string, args ...string) (string, string, error) { - return pm.ExecDirEnvStdIn(ctx, timeout, dir, desc, env, nil, cmdName, args...) -} - -// ExecDirEnvStdIn runs a command in given path and environment variables with provided stdIN, and waits for its completion -// up to the given timeout (or DefaultTimeout if timeout <= 0 is given). -// Returns its complete stdout and stderr -// outputs and an error, if any (including timeout) -func (pm *Manager) ExecDirEnvStdIn(ctx context.Context, timeout time.Duration, dir, desc string, env []string, stdIn io.Reader, cmdName string, args ...string) (string, string, error) { - if timeout <= 0 { - timeout = 60 * time.Second - } - - stdOut := new(bytes.Buffer) - stdErr := new(bytes.Buffer) - - ctx, _, finished := pm.AddContextTimeout(ctx, timeout, desc) - defer finished() - - cmd := exec.CommandContext(ctx, cmdName, args...) - cmd.Dir = dir - cmd.Env = env - cmd.Stdout = stdOut - cmd.Stderr = stdErr - if stdIn != nil { - cmd.Stdin = stdIn - } - SetSysProcAttribute(cmd) - - if err := cmd.Start(); err != nil { - return "", "", err - } - - err := cmd.Wait() - if err != nil { - err = &Error{ - PID: GetPID(ctx), - Description: desc, - Err: err, - CtxErr: ctx.Err(), - Stdout: stdOut.String(), - Stderr: stdErr.String(), - } - } - - return stdOut.String(), stdErr.String(), err -} diff --git a/modules/process/manager_test.go b/modules/process/manager_test.go index 0d637c8acc3..bf2e84bf1c0 100644 --- a/modules/process/manager_test.go +++ b/modules/process/manager_test.go @@ -6,7 +6,6 @@ package process import ( "context" "testing" - "time" "github.com/stretchr/testify/assert" ) @@ -87,25 +86,3 @@ func TestManager_Remove(t *testing.T) { _, exists := pm.processMap[GetPID(p2Ctx)] assert.False(t, exists, "PID %d is in the list but shouldn't", GetPID(p2Ctx)) } - -func TestExecTimeoutNever(t *testing.T) { - // TODO Investigate how to improve the time elapsed per round. - maxLoops := 10 - for i := 1; i < maxLoops; i++ { - _, stderr, err := GetManager().ExecTimeout(5*time.Second, "ExecTimeout", "git", "--version") - if err != nil { - t.Fatalf("git --version: %v(%s)", err, stderr) - } - } -} - -func TestExecTimeoutAlways(t *testing.T) { - maxLoops := 100 - for i := 1; i < maxLoops; i++ { - _, stderr, err := GetManager().ExecTimeout(100*time.Microsecond, "ExecTimeout", "sleep", "5") - // TODO Simplify logging and errors to get precise error type. E.g. checking "if err != context.DeadlineExceeded". - if err == nil { - t.Fatalf("sleep 5 secs: %v(%s)", err, stderr) - } - } -} diff --git a/modules/process/manager_unix.go b/modules/process/manager_unix.go deleted file mode 100644 index c5be906b35e..00000000000 --- a/modules/process/manager_unix.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build !windows - -package process - -import ( - "os/exec" - "syscall" -) - -// SetSysProcAttribute sets the common SysProcAttrs for commands -func SetSysProcAttribute(cmd *exec.Cmd) { - // When Gitea runs SubProcessA -> SubProcessB and SubProcessA gets killed by context timeout, use setpgid to make sure the sub processes can be reaped instead of leaving defunct(zombie) processes. - cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} -} diff --git a/modules/process/manager_windows.go b/modules/process/manager_windows.go deleted file mode 100644 index 44a84f22031..00000000000 --- a/modules/process/manager_windows.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build windows - -package process - -import ( - "os/exec" -) - -// SetSysProcAttribute sets the common SysProcAttrs for commands -func SetSysProcAttribute(cmd *exec.Cmd) { - // Do nothing -} diff --git a/modules/ssh/ssh.go b/modules/ssh/ssh.go index c80b83370b7..5995ad16abf 100644 --- a/modules/ssh/ssh.go +++ b/modules/ssh/ssh.go @@ -75,7 +75,7 @@ func sessionHandler(session *sshSession) int { } } - cmd := exec.CommandContext(ctx, setting.AppPath, args...) + cmd := process.CommandContext(ctx, setting.AppPath, args...) cmd.Env = append( os.Environ(), "SSH_ORIGINAL_COMMAND="+session.rawCmd, @@ -104,8 +104,6 @@ func sessionHandler(session *sshSession) int { } defer stdin.Close() - process.SetSysProcAttribute(cmd) - wg := &sync.WaitGroup{} if err = cmd.Start(); err != nil { diff --git a/services/asymkey/commit.go b/services/asymkey/commit.go index ef1a910155f..0d6a50f0584 100644 --- a/services/asymkey/commit.go +++ b/services/asymkey/commit.go @@ -298,7 +298,7 @@ func verifyCommitSignByGPGSettings(ctx context.Context, gpgSettings *git.CommitS } // Otherwise we have to parse the key - pubKeyContent, err := gpgSettings.PublicKeyContent() + pubKeyContent, err := gpgSettings.PublicKeyContent(ctx) if err != nil { log.Error("gpgSettings.PublicKeyContent: %v", err) return nil @@ -420,7 +420,7 @@ func parseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committerUs // Try the configured instance-wide SSH public key if instanceSettings := getInstanceCommitSignSettings(git.SigningKeyFormatSSH); instanceSettings != nil { - pubKeyContent, err := instanceSettings.PublicKeyContent() + pubKeyContent, err := instanceSettings.PublicKeyContent(ctx) if err != nil { log.Error("commitSignSettings.PublicKeyContent: %v", err) } else { diff --git a/services/asymkey/sign.go b/services/asymkey/sign.go index b7c47e3ab53..e95aa3439a8 100644 --- a/services/asymkey/sign.go +++ b/services/asymkey/sign.go @@ -122,8 +122,7 @@ func PublicSigningKey(ctx context.Context) (content, format string, err error) { return string(content), signingKey.Format, nil } - content, stderr, err := process.GetManager().ExecDir(ctx, -1, setting.Git.HomePath, - "gpg --export -a", "gpg", "--export", "-a", signingKey.KeyID) + content, stderr, err := process.CommandContext(ctx, "gpg", "--export", "-a", signingKey.KeyID).WithDir(setting.Git.HomePath).OutputString() if err != nil { log.Error("Unable to get default signing key: %s, %s, %v", signingKey, stderr, err) return "", signingKey.Format, err diff --git a/services/mailer/sender/sendmail.go b/services/mailer/sender/sendmail.go index 6f7fadc31de..ce438a78ea6 100644 --- a/services/mailer/sender/sendmail.go +++ b/services/mailer/sender/sendmail.go @@ -6,7 +6,6 @@ package sender import ( "fmt" "io" - "os/exec" "strings" "gitea.dev/modules/graceful" @@ -47,12 +46,11 @@ func (s *SendmailSender) Send(from string, to []string, msg io.WriterTo) error { ctx, _, finished := process.GetManager().AddContextTimeout(graceful.GetManager().HammerContext(), setting.MailService.SendmailTimeout, desc) defer finished() - cmd := exec.CommandContext(ctx, setting.MailService.SendmailPath, args...) + cmd := process.CommandContext(ctx, setting.MailService.SendmailPath, args...) pipe, err := cmd.StdinPipe() if err != nil { return err } - process.SetSysProcAttribute(cmd) if err = cmd.Start(); err != nil { _ = pipe.Close() diff --git a/tests/integration/gpg_ssh_git_test.go b/tests/integration/gpg_ssh_git_test.go index 130cfb65341..75203db2def 100644 --- a/tests/integration/gpg_ssh_git_test.go +++ b/tests/integration/gpg_ssh_git_test.go @@ -39,7 +39,7 @@ func TestGPGGit(t *testing.T) { t.Setenv("GNUPGHOME", tmpDir) // Need to create a root key - rootKeyPair, err := importTestingKey() + rootKeyPair, err := importTestingKey(t) require.NoError(t, err, "importTestingKey") defer test.MockVariableValue(&setting.Repository.Signing.SigningKey, rootKeyPair.PrimaryKey.KeyIdShortString())() @@ -403,9 +403,9 @@ func crudActionCreateFile(_ *testing.T, ctx APITestContext, user *user_model.Use }, callback...) } -func importTestingKey() (*openpgp.Entity, error) { +func importTestingKey(t *testing.T) (*openpgp.Entity, error) { keyPath := filepath.Join(setting.GetGiteaTestSourceRoot(), "tests/integration/private-testing.key") - if _, _, err := process.GetManager().Exec("gpg --import "+keyPath, "gpg", "--import", keyPath); err != nil { + if _, _, err := process.CommandContext(t.Context(), "gpg", "--import", keyPath).OutputString(); err != nil { return nil, err } keyringFile, err := os.Open(keyPath)