Skip to content

Commit

Permalink
run command without sh -c
Browse files Browse the repository at this point in the history
  • Loading branch information
Chang Hua Ou committed Jan 16, 2017
1 parent c1721e4 commit f5a6853
Show file tree
Hide file tree
Showing 10 changed files with 283 additions and 58 deletions.
72 changes: 72 additions & 0 deletions command_parser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package main

import (
"fmt"
"unicode"
)

func findChar( s string, offset int, ch byte ) int {
for i := offset; i < len( s ); i++ {
if s[i] == '\\' {
i++
} else if s[i] == ch {
return i
}
}
return -1
}

func skipSpace( s string, offset int ) int {
for i := offset; i < len( s ); i++ {
if !unicode.IsSpace(rune(s[i]) ) {
return i
}
}
return -1
}

func appendArgument( arg string, args []string) []string {
if arg[0] == '"' || arg[0] == '\'' {
return append( args, arg[1:len(arg)-1 ] )
}
return append( args, arg )
}

func parseCommand( command string )( []string, error ) {
args := make([]string, 0)
cmdLen := len( command )
for i := 0; i < cmdLen; {
j := skipSpace( command, i )
if j == -1 {
break
}
for ; j < cmdLen; j++ {
if unicode.IsSpace(rune(command[j])) {
args = appendArgument( command[i:j], args )
i = j + 1
break
} else if command[j] == '\\' {
j ++
} else if command[j] == '"' || command[j] == '\'' {
k := findChar( command, j + 1, command[j] )
if k == -1 {
args = appendArgument( command[i:], args )
i = cmdLen
} else {
args = appendArgument( command[i:k+1], args)
i = k + 2
}
break
}
}
if j >= cmdLen {
args = appendArgument( command[i:], args )
i = cmdLen
}
}
if len( args ) <= 0 {
return nil, fmt.Errorf( "no command from empty string")
}
return args, nil
}

33 changes: 33 additions & 0 deletions command_parser_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package main

import (
"testing"
)


func TestEmptyCommandLine( t* testing.T ) {
args, err := parseCommand( " ")
if err == nil || len( args ) > 0 {
t.Error( "fail to parse the empty command line")
}
}

func TestNormalCommandLine(t *testing.T ) {
args, err := parseCommand( "program arg1 arg2")
if err != nil {
t.Error( "fail to parse normal command line")
}
if args[0] != "program" || args[1] != "arg1" || args[2] != "arg2" {
t.Error( "fail to parse normal command line" )
}
}

func TestCommandLineWithQuotationMarks(t* testing.T ) {
args, err := parseCommand( "program 'this is arg1' args=\"this is arg2\"" )
if err != nil || len( args) != 3 {
t.Error( "fail to parse command line with quotation marks")
}
if args[0] != "program" || args[1] != "this is arg1" || args[2] != "args=\"this is arg2\"" {
t.Error( "fail to parse command line with quotation marks")
}
}
2 changes: 1 addition & 1 deletion config.go
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ func (c *Config) parseProgram( cfg *ini.File ) {
log.WithFields( log.Fields{
"numprocs": numProcs,
"process_name": procNameKey.Value(),
}).Error( "no %(process_num) in process name" )
}).Error( "no process_num in process name" )
}
}
for i := 1; i <= numProcs; i += 1 {
Expand Down
2 changes: 1 addition & 1 deletion config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ func TestGetBytesFromConfig( t *testing.T ) {
func TestGetUnitHttpServer(t *testing.T ) {
config, _ := parse( []byte( "[program:test]\nA=1024\nB=2KB\nC=3MB\nD=4GB\nE=test\n[unix_http_server]\n") )

entry, ok := config.GetUnitHttpServer()
entry, ok := config.GetUnixHttpServer()

if !ok || entry == nil {
t.Error( "Fail to get the unix_http_server")
Expand Down
1 change: 0 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package main

import(
//"flag"
"os"
"github.com/jessevdk/go-flags"
log "github.com/Sirupsen/logrus"
Expand Down
71 changes: 56 additions & 15 deletions process.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@ type Process struct {
startTime time.Time
stopTime time.Time
state ProcessState
//true if process is starting
inStart bool
//true if the process is stopped by user
stopByUser bool
lock sync.RWMutex
}

Expand All @@ -66,47 +69,67 @@ func NewProcess(config *ConfigEntry) *Process {
proc.cmd = nil
proc.state = STOPPED
proc.inStart = false
proc.stopByUser = false

//start the process if autostart is set to true
if( proc.isAutoStart() ) {
proc.Start()
proc.Start( false )
}

return proc
}

func (p *Process) Start() {
func (p *Process) Start( wait bool ) {
log.WithFields( log.Fields {"program": p.GetName() }).Info( "try to start program" )
p.lock.Lock()
if p.inStart {
log.WithFields( log.Fields{ "program": p.GetName() } ).Info( "Don't start program again, program is already started" )
p.lock.Unlock()
return
}

p.inStart = true
p.stopByUser = false
p.lock.Unlock()

var m sync.Mutex
runCond := sync.NewCond( &m )
m.Lock()

go func() {
retryTimes := 0

for {
p.run()
p.run( runCond )
if (p.stopTime.Unix() - p.startTime.Unix()) < int64(p.getStartSeconds()) {
retryTimes ++
} else {
retryTimes = 0
}
if retryTimes >= p.getStartRetries() || !p.isAutoRestart() {
if p.stopByUser {
log.WithFields( log.Fields{ "program":p.GetName()}).Info( "Stopped by user, don't start it again")
break
}
if !p.isAutoRestart() {
log.WithFields( log.Fields{ "program": p.GetName() } ).Info( "Don't start the stopped program because its autorestart flag is false")
break
}
if retryTimes >= p.getStartRetries() {
log.WithFields( log.Fields { "program": p.GetName() } ).Info( "Don't start the stopped program because its retry times ", retryTimes, " is greater than start retries ", p.getStartRetries() )
break
}
}
p.lock.Lock()
p.inStart = false
p.lock.Unlock()
}()
if wait {
runCond.Wait()
}
}

func (p *Process) GetName() string {
return p.config.Name
return p.config.Name[len("program:"):]
}

func (p *Process) GetGroup() string {
Expand Down Expand Up @@ -236,33 +259,49 @@ func (p *Process) getExitCodes() []int {
return result
}

func (p *Process) run() {
p.lock.Lock()
if p.cmd != nil && !p.cmd.ProcessState.Exited() {
p.lock.Unlock()
func (p *Process) run( runCond *sync.Cond ) {
args, err := parseCommand( p.config.GetString( "command", "" ) )

if err != nil {
log.Error( "the command is empty string" )
return
}
p.cmd = exec.Command("/bin/sh", "-c", p.config.GetString("command", ""))
p.lock.Lock()
if p.cmd != nil {
status := p.cmd.ProcessState.Sys().(syscall.WaitStatus)
if status.Continued() {
log.WithFields( log.Fields{ "program":p.GetName() }).Info( "Don't start program because it is running")
p.lock.Unlock()
return
}
}
p.cmd = exec.Command( args[0] )
if len( args ) > 1 {
p.cmd.Args = args
}
p.setEnv()
p.setLog()

p.startTime = time.Now()
p.state = STARTING
err := p.cmd.Start()
err = p.cmd.Start()
if err != nil {
log.WithFields( log.Fields{"program":p.config.GetProgramName()}).Error("fail to start program")
p.state = FATAL
p.stopTime = time.Now()
p.lock.Unlock()
runCond.Signal()
} else {
log.WithFields( log.Fields{"program":p.config.GetProgramName()}).Info("success to start program")
startSecs := p.config.GetInt( "startsecs", 1 )
//Set startsec to 0 to indicate that the program needn't stay
//running for any particular amount of time.
//Set startsec to 0 to indicate that the program needn't stay
//running for any particular amount of time.
if startSecs > 0 {
p.state = RUNNING
}
p.lock.Unlock()
log.WithFields( log.Fields {"program":p.config.GetProgramName()}).Debug( "wait program exit")
runCond.Signal()
p.cmd.Wait()
p.lock.Lock()
p.stopTime = time.Now()
Expand Down Expand Up @@ -302,7 +341,7 @@ func (p *Process) setLog() {
p.cmd.Stdout = NewNullLogger()

if len(logFile) > 0 {
p.cmd.Stdout = NewLogger( logFile,
p.cmd.Stdout = NewLogger( logFile,
int64(p.config.GetBytes( "stdout_logfile_maxbytes", 50*1024*1024 )),
p.config.GetInt( "stdout_logfile_backups", 10 ) )

Expand Down Expand Up @@ -338,10 +377,12 @@ func toSignal(signalName string) os.Signal {
}

//send signal to process to stop it
func (p *Process) Stop() {
func (p *Process) Stop( wait bool ) {
p.lock.Lock()
defer p.lock.Unlock()
p.stopByUser = true
if p.cmd != nil && p.cmd.Process != nil {
log.WithFields( log.Fields{"program":p.GetName()} ).Info( "stop the program")
p.cmd.Process.Signal(toSignal(p.config.GetString("stopsignal", "")))
}
}
Expand Down
19 changes: 15 additions & 4 deletions process_manager.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
log "github.com/Sirupsen/logrus"
"sync"
)
type ProcessManager struct {
Expand All @@ -17,36 +18,46 @@ func newProcessManager() *ProcessManager {
func (pm *ProcessManager) CreateProcess( config* ConfigEntry ) *Process {
pm.lock.Lock()
defer pm.lock.Unlock()
procName := config.Name[len("program:"):]

proc, ok := pm.procs[config.Name]
proc, ok := pm.procs[procName]

if !ok {
proc = NewProcess( config )
pm.procs[config.Name] = proc
pm.procs[procName] = proc
}
log.Info( "create process:", procName )
return proc
}

func (pm *ProcessManager) Add(name string, proc *Process) {
pm.lock.Lock()
defer pm.lock.Unlock()
pm.procs[name] = proc
log.Info( "add process:", name )
}

func (pm *ProcessManager) Remove(name string) *Process {
pm.lock.Lock()
defer pm.lock.Unlock()
proc, _ := pm.procs[name]
delete(pm.procs, name)
log.Info( "remove process:", name )
return proc
}

// return process if found or nil if not found
func (pm *ProcessManager) Find(name string) *Process {
pm.lock.Lock()
defer pm.lock.Unlock()
proc, _ := pm.procs[name]
return proc
proc, ok := pm.procs[name]
if ok {
log.Debug( "succeed to find process:", name)
return proc
} else {
log.Info( "fail to find process:", name )
return nil
}
}

// clear all the processes
Expand Down
2 changes: 1 addition & 1 deletion string_expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func (se *StringExpression) Eval(s string) (string, error) {
} else if s[typ] == 's' {
s = s[0:start] + varValue + s[typ+1:]
} else {
return "", fmt.Errorf("not implement type:%s", s[typ])
return "", fmt.Errorf("not implement type:%v", s[typ])
}
} else {
return "", fmt.Errorf("invalid string expression format")
Expand Down
Loading

0 comments on commit f5a6853

Please sign in to comment.