2014-06-19 09:08:03 +04:00
// Copyright 2014 The Gogs Authors. All rights reserved.
2019-10-12 03:13:27 +03:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2014-06-19 09:08:03 +04:00
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package process
import (
"bytes"
2017-11-13 17:51:45 +03:00
"context"
2014-06-19 09:08:03 +04:00
"fmt"
2019-10-12 03:13:27 +03:00
"io"
2014-06-19 09:08:03 +04:00
"os/exec"
2019-11-30 17:40:22 +03:00
"sort"
2017-01-17 08:58:58 +03:00
"sync"
2014-06-19 09:08:03 +04:00
"time"
)
2017-01-17 08:58:58 +03:00
// TODO: This packages still uses a singleton for the Manager.
// Once there's a decent web framework and dependencies are passed around like they should,
// then we delete the singleton.
2014-07-07 01:32:36 +04:00
var (
2021-04-09 10:40:34 +03:00
manager * Manager
managerInit sync . Once
2019-11-30 17:40:22 +03:00
// DefaultContext is the default context to run processing commands in
DefaultContext = context . Background ( )
2014-07-07 01:32:36 +04:00
)
2019-11-30 17:40:22 +03:00
// Process represents a working process inheriting from Gitea.
2014-06-19 09:08:03 +04:00
type Process struct {
2017-01-17 08:58:58 +03:00
PID int64 // Process ID, not system one.
2014-06-19 09:08:03 +04:00
Description string
Start time . Time
2019-11-30 17:40:22 +03:00
Cancel context . CancelFunc
2014-06-19 09:08:03 +04:00
}
2017-01-17 08:58:58 +03:00
// Manager knows about all processes and counts PIDs.
type Manager struct {
mutex sync . Mutex
2014-06-19 09:08:03 +04:00
2017-01-17 08:58:58 +03:00
counter int64
2019-11-30 17:40:22 +03:00
processes map [ int64 ] * Process
2017-01-17 08:58:58 +03:00
}
// GetManager returns a Manager and initializes one as singleton if there's none yet
func GetManager ( ) * Manager {
2021-02-04 00:36:38 +03:00
managerInit . Do ( func ( ) {
2017-01-17 08:58:58 +03:00
manager = & Manager {
2019-11-30 17:40:22 +03:00
processes : make ( map [ int64 ] * Process ) ,
2017-01-17 08:58:58 +03:00
}
2021-02-04 00:36:38 +03:00
} )
2017-01-17 08:58:58 +03:00
return manager
}
// Add a process to the ProcessManager and returns its PID.
2019-11-30 17:40:22 +03:00
func ( pm * Manager ) Add ( description string , cancel context . CancelFunc ) int64 {
2017-01-17 08:58:58 +03:00
pm . mutex . Lock ( )
pid := pm . counter + 1
2019-11-30 17:40:22 +03:00
pm . processes [ pid ] = & Process {
2017-01-17 08:58:58 +03:00
PID : pid ,
Description : description ,
2014-06-19 09:08:03 +04:00
Start : time . Now ( ) ,
2019-11-30 17:40:22 +03:00
Cancel : cancel ,
2017-01-17 08:58:58 +03:00
}
pm . counter = pid
pm . mutex . Unlock ( )
2014-06-19 09:08:03 +04:00
return pid
}
2017-01-17 08:58:58 +03:00
// Remove a process from the ProcessManager.
func ( pm * Manager ) Remove ( pid int64 ) {
pm . mutex . Lock ( )
2019-11-30 17:40:22 +03:00
delete ( pm . processes , pid )
2017-01-17 08:58:58 +03:00
pm . mutex . Unlock ( )
}
2019-11-30 17:40:22 +03:00
// Cancel a process in the ProcessManager.
func ( pm * Manager ) Cancel ( pid int64 ) {
pm . mutex . Lock ( )
process , ok := pm . processes [ pid ]
pm . mutex . Unlock ( )
if ok {
process . Cancel ( )
}
}
// Processes gets the processes in a thread safe manner
func ( pm * Manager ) Processes ( ) [ ] * Process {
pm . mutex . Lock ( )
processes := make ( [ ] * Process , 0 , len ( pm . processes ) )
for _ , process := range pm . processes {
processes = append ( processes , process )
}
pm . mutex . Unlock ( )
sort . Sort ( processList ( processes ) )
return processes
}
2017-01-17 08:58:58 +03:00
// Exec a command and use the default timeout.
func ( pm * Manager ) Exec ( desc , cmdName string , args ... string ) ( string , string , error ) {
return pm . ExecDir ( - 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 ( timeout , "" , desc , cmdName , args ... )
}
// ExecDir a command and use the default timeout.
func ( pm * Manager ) ExecDir ( timeout time . Duration , dir , desc , cmdName string , args ... string ) ( string , string , error ) {
return pm . ExecDirEnv ( timeout , dir , desc , nil , cmdName , args ... )
}
2016-11-27 05:10:08 +03:00
// ExecDirEnv runs a command in given path and environment variables, and waits for its completion
2016-11-15 10:06:31 +03:00
// 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)
2017-01-17 08:58:58 +03:00
func ( pm * Manager ) ExecDirEnv ( timeout time . Duration , dir , desc string , env [ ] string , cmdName string , args ... string ) ( string , string , error ) {
2019-10-12 03:13:27 +03:00
return pm . ExecDirEnvStdIn ( 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 -1 is given).
// Returns its complete stdout and stderr
// outputs and an error, if any (including timeout)
func ( pm * Manager ) ExecDirEnvStdIn ( timeout time . Duration , dir , desc string , env [ ] string , stdIn io . Reader , cmdName string , args ... string ) ( string , string , error ) {
2014-07-07 01:32:36 +04:00
if timeout == - 1 {
2017-01-17 08:58:58 +03:00
timeout = 60 * time . Second
2014-07-07 01:32:36 +04:00
}
2017-01-17 08:58:58 +03:00
stdOut := new ( bytes . Buffer )
stdErr := new ( bytes . Buffer )
2014-06-19 09:08:03 +04:00
2019-11-30 17:40:22 +03:00
ctx , cancel := context . WithTimeout ( DefaultContext , timeout )
2017-11-13 17:51:45 +03:00
defer cancel ( )
cmd := exec . CommandContext ( ctx , cmdName , args ... )
2014-06-19 09:08:03 +04:00
cmd . Dir = dir
2016-11-27 05:10:08 +03:00
cmd . Env = env
2017-01-17 08:58:58 +03:00
cmd . Stdout = stdOut
cmd . Stderr = stdErr
2019-10-12 03:13:27 +03:00
if stdIn != nil {
cmd . Stdin = stdIn
}
2014-07-07 01:32:36 +04:00
if err := cmd . Start ( ) ; err != nil {
2017-01-17 08:58:58 +03:00
return "" , "" , err
2014-07-07 01:32:36 +04:00
}
2014-06-19 09:08:03 +04:00
2019-11-30 17:40:22 +03:00
pid := pm . Add ( desc , cancel )
2017-11-13 17:51:45 +03:00
err := cmd . Wait ( )
2017-01-17 08:58:58 +03:00
pm . Remove ( pid )
2014-06-19 09:08:03 +04:00
2017-01-17 08:58:58 +03:00
if err != nil {
2020-06-14 00:47:31 +03:00
err = & Error {
PID : pid ,
Description : desc ,
Err : err ,
CtxErr : ctx . Err ( ) ,
Stdout : stdOut . String ( ) ,
Stderr : stdErr . String ( ) ,
}
2014-06-19 09:08:03 +04:00
}
2017-01-17 08:58:58 +03:00
2017-11-13 17:51:45 +03:00
return stdOut . String ( ) , stdErr . String ( ) , err
2014-06-19 09:08:03 +04:00
}
2019-11-30 17:40:22 +03:00
type processList [ ] * Process
func ( l processList ) Len ( ) int {
return len ( l )
}
func ( l processList ) Less ( i , j int ) bool {
return l [ i ] . PID < l [ j ] . PID
}
2017-01-17 08:58:58 +03:00
2019-11-30 17:40:22 +03:00
func ( l processList ) Swap ( i , j int ) {
l [ i ] , l [ j ] = l [ j ] , l [ i ]
2014-06-19 09:08:03 +04:00
}
2020-06-14 00:47:31 +03:00
// Error is a wrapped error describing the error results of Process Execution
type Error struct {
PID int64
Description string
Err error
CtxErr error
Stdout string
Stderr string
}
func ( err * Error ) Error ( ) string {
return fmt . Sprintf ( "exec(%d:%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
}