diff --git a/internal/app.go b/internal/app.go index fbc1718..6aad8b9 100644 --- a/internal/app.go +++ b/internal/app.go @@ -20,6 +20,7 @@ import ( const onChangedForceExit = 123 var NotModifiedError = errors.New("Config unchanged on server") +var HandlersDir = "/usr/share/fioconfig/handlers/" type CryptoHandler interface { Decrypt(value string) ([]byte, error) @@ -169,16 +170,15 @@ func (a *App) runOnChanged(fname string, fullpath string, onChanged []string) { } if len(onChanged) > 0 { binary := filepath.Clean(onChanged[0]) - if a.unsafeHandlers || strings.HasPrefix(binary, "/usr/share/fioconfig/handlers/") { + if a.unsafeHandlers || strings.HasPrefix(binary, HandlersDir) { slog.Info("Running on-change command", "file", fname, "args", onChanged) cmd := exec.Command(onChanged[0], onChanged[1:]...) cmd.Env = append(os.Environ(), "CONFIG_FILE="+fullpath) cmd.Env = append(cmd.Env, "STORAGE_DIR="+a.StorageDir) cmd.Env = append(cmd.Env, "SOTA_DIR="+strings.Join(a.sota.SearchPaths(), ",")) cmd.Env = append(cmd.Env, "FIOCONFIG_BIN="+path) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - if err := cmd.Run(); err != nil { + + if err := ExecIndented(cmd, "| "); err != nil { slog.Error("Unable to run command", "command", onChanged, "error", err) if exitError, ok := err.(*exec.ExitError); ok { if exitError.ExitCode() == onChangedForceExit { diff --git a/internal/exec.go b/internal/exec.go new file mode 100644 index 0000000..487c6ae --- /dev/null +++ b/internal/exec.go @@ -0,0 +1,48 @@ +package internal + +import ( + "bufio" + "fmt" + "io" + "log/slog" + "os" + "os/exec" + "sync" +) + +func ExecIndented(cmd *exec.Cmd, indentChars string) error { + stdout, err := cmd.StdoutPipe() + if err != nil { + return fmt.Errorf("failed to get stdout: %w", err) + } + + stderr, err := cmd.StderrPipe() + if err != nil { + return fmt.Errorf("failed to get stderr: %w", err) + } + + if err = cmd.Start(); err != nil { + return fmt.Errorf("failed to start command: %w", err) + } + + var wg sync.WaitGroup + wg.Add(2) + + go prefixAndCopy(indentChars, stdout, os.Stdout, &wg) + go prefixAndCopy(indentChars, stderr, os.Stderr, &wg) + + wg.Wait() + return cmd.Wait() +} + +// prefixAndCopy reads from r line by line and writes to w with "| " prefix. +func prefixAndCopy(prefix string, r io.Reader, w io.Writer, wg *sync.WaitGroup) { + defer wg.Done() + scanner := bufio.NewScanner(r) + for scanner.Scan() { + fmt.Fprint(w, prefix, scanner.Text(), "\n") + } + if err := scanner.Err(); err != nil { + slog.Error("Error reading command output", "error", err) + } +}