From f6f85251b378c15c130304e529ceb366e7ff3302 Mon Sep 17 00:00:00 2001 From: Manuel Coenen Date: Mon, 19 Oct 2020 10:38:59 +0200 Subject: [PATCH] Add option to skip channel flush Add option to execute system commands async and return success to DCS immediately Add option to select InterceptionMode (Pre|Post|Executed) --- README.md | 14 +++++++++--- cmd/eom/execonmcode.go | 42 ++++++++++++++++++++-------------- executor.go | 51 +++++++++++++++++++++++++++--------------- settings.go | 12 ++++++++++ 4 files changed, 81 insertions(+), 38 deletions(-) create mode 100644 settings.go diff --git a/README.md b/README.md index 001c18c..95e499e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Description -This is a small extension to [DuetSoftwareFramework](https://github.com/christhamm/DuetSoftwareFramework) +This is a small extension to [DuetSoftwareFramework](https://github.com/Duet3D/DuetSoftwareFramework) to execute arbitrary system commands when a user-defined `M-Code` is encountered. An example usage would be to execute system shutdown on the SBC when a e.g. `M7722` is run. @@ -9,15 +9,23 @@ An example usage would be to execute system shutdown on the SBC when a e.g. `M77 $ execonmcode -help Usage of ./execonmcode: -command value - Command to execute + Command to execute. This can be specified multiple times. -debug Print debug output + -execAsync + Run command to execute async and return success to DCS immediately + -interceptionMode string + Interception mode to use (default "Pre") -mCode value - Code that will initiate execution of the command + Code that will initiate execution of the command. This can be specified multiple times. + -noFlush + Do not flush the code channel before executing the associated command -socketPath string Path to socket (default "/var/run/dsf/dcs.sock") -trace Print underlying requests/responses + -version + Show version and exit ``` Starting from version 3 it is possible to provide an arbitrary number of `-mCode` + `-command` tuples. This way a diff --git a/cmd/eom/execonmcode.go b/cmd/eom/execonmcode.go index 67b86bb..2428c5d 100644 --- a/cmd/eom/execonmcode.go +++ b/cmd/eom/execonmcode.go @@ -4,32 +4,29 @@ import ( "flag" "log" "os" + "strings" "github.com/Duet3D/DSF-APIs/godsfapi/v3/connection" + "github.com/Duet3D/DSF-APIs/godsfapi/v3/connection/initmessages" "github.com/wilriker/execonmcode" ) const ( - version = "5.1" + version = "5.2" ) -type settings struct { - socketPath string - mCodes execonmcode.MCodes - commands execonmcode.Commands - debug bool - trace bool -} - func main() { - s := settings{} + s := execonmcode.Settings{} - flag.StringVar(&s.socketPath, "socketPath", connection.FullSocketPath, "Path to socket") - flag.Var(&s.mCodes, "mCode", "Code that will initiate execution of the command") - flag.Var(&s.commands, "command", "Command to execute") - flag.BoolVar(&s.debug, "debug", false, "Print debug output") - flag.BoolVar(&s.trace, "trace", false, "Print underlying requests/responses") + flag.StringVar(&s.SocketPath, "socketPath", connection.FullSocketPath, "Path to socket") + flag.StringVar(&s.InterceptionMode, "interceptionMode", string(initmessages.InterceptionModePre), "Interception mode to use") + flag.Var(&s.MCodes, "mCode", "Code that will initiate execution of the command. This can be specified multiple times.") + flag.Var(&s.Commands, "command", "Command to execute. This can be specified multiple times.") + flag.BoolVar(&s.NoFlush, "noFlush", false, "Do not flush the code channel before executing the associated command") + flag.BoolVar(&s.ExecAsync, "execAsync", false, "Run command to execute async and return success to DCS immediately") + flag.BoolVar(&s.Debug, "debug", false, "Print debug output") + flag.BoolVar(&s.Trace, "trace", false, "Print underlying requests/responses") version := flag.Bool("version", false, "Show version and exit") flag.Parse() @@ -38,11 +35,22 @@ func main() { os.Exit(0) } - if s.mCodes.Len() != s.commands.Len() { + if s.MCodes.Len() != s.Commands.Len() { log.Fatal("Unequal amount of M-codes and commands given") } - e := execonmcode.NewExecutor(s.socketPath, s.commands, s.mCodes, s.debug, s.trace) + switch strings.ToLower(s.InterceptionMode) { + case strings.ToLower(string(initmessages.InterceptionModePre)): + s.InterceptionMode = string(initmessages.InterceptionModePre) + case strings.ToLower(string(initmessages.InterceptionModePost)): + s.InterceptionMode = string(initmessages.InterceptionModePost) + case strings.ToLower(string(initmessages.InterceptionModeExecuted)): + s.InterceptionMode = string(initmessages.InterceptionModeExecuted) + default: + log.Fatal("Unsupported InterceptionMode", s.InterceptionMode) + } + + e := execonmcode.NewExecutor(s) err := e.Run() if err != nil { log.Fatal(err) diff --git a/executor.go b/executor.go index c77d423..552200d 100644 --- a/executor.go +++ b/executor.go @@ -19,18 +19,21 @@ const ( type Executor struct { socketPath string + mode initmessages.InterceptionMode mCodes map[int64]int commands Commands + execAsync bool + flush bool debug bool trace bool } -func NewExecutor(socketPath string, commands Commands, mCodes MCodes, debug, trace bool) *Executor { +func NewExecutor(s Settings) *Executor { mc := make(map[int64]int) - for i, m := range mCodes { + for i, m := range s.MCodes { mc[m] = i - if debug { - cmd, args, err := commands.Get(i) + if s.Debug { + cmd, args, err := s.Commands.Get(i) if err != nil { log.Println(m, err) } @@ -38,11 +41,14 @@ func NewExecutor(socketPath string, commands Commands, mCodes MCodes, debug, tra } } return &Executor{ - socketPath: socketPath, + socketPath: s.SocketPath, + mode: initmessages.InterceptionMode(s.InterceptionMode), mCodes: mc, - commands: commands, - debug: debug, - trace: trace, + commands: s.Commands, + execAsync: s.ExecAsync, + flush: !s.NoFlush, + debug: s.Debug, + trace: s.Trace, } } @@ -50,7 +56,7 @@ func (e *Executor) Run() error { ic := connection.InterceptConnection{} ic.Debug = e.trace - err := ic.Connect(initmessages.InterceptionModePre, e.socketPath) + err := ic.Connect(e.mode, e.socketPath) if err != nil { return err } @@ -77,11 +83,13 @@ func (e *Executor) Run() error { ic.IgnoreCode() continue } - success, err := ic.Flush(c.Channel) - if !success || err != nil { - log.Println("Could not Flush. Cancelling code") - ic.CancelCode() - continue + if e.flush { + success, err := ic.Flush(c.Channel) + if !success || err != nil { + log.Println("Could not Flush. Cancelling code") + ic.CancelCode() + continue + } } comd, a, err := e.commands.Get(i) if err != nil { @@ -91,11 +99,18 @@ func (e *Executor) Run() error { if e.debug { log.Println("Executing:", cmd) } - output, err := cmd.CombinedOutput() - if err != nil { - err = ic.ResolveCode(messages.Error, fmt.Sprintf("%s: %s", err.Error(), string(output))) - } else { + + // If we should exec async run it as goroutine and return success + if e.execAsync { + go cmd.Run() err = ic.ResolveCode(messages.Success, "") + } else { + output, err := cmd.CombinedOutput() + if err != nil { + err = ic.ResolveCode(messages.Error, fmt.Sprintf("%s: %s", err.Error(), string(output))) + } else { + err = ic.ResolveCode(messages.Success, "") + } } if err != nil { log.Println("Error executing command:", err) diff --git a/settings.go b/settings.go new file mode 100644 index 0000000..7e0dca7 --- /dev/null +++ b/settings.go @@ -0,0 +1,12 @@ +package execonmcode + +type Settings struct { + SocketPath string + InterceptionMode string + MCodes MCodes + Commands Commands + NoFlush bool + ExecAsync bool + Debug bool + Trace bool +}