2023-04-13 21:10:53 +01:00
|
|
|
// Copyright (C) 2023 Storj Labs, Inc.
|
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
|
|
|
package lazyfilewalker
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
|
|
|
|
"go.uber.org/zap"
|
|
|
|
"golang.org/x/sys/execabs"
|
2023-05-05 22:19:09 +01:00
|
|
|
|
|
|
|
"storj.io/storj/storagenode/pieces/lazyfilewalker/execwrapper"
|
2023-04-13 21:10:53 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
// process is a subprocess that can be used to perform filewalker operations.
|
|
|
|
type process struct {
|
|
|
|
log *zap.Logger
|
|
|
|
executable string
|
|
|
|
args []string
|
2023-05-05 22:19:09 +01:00
|
|
|
|
|
|
|
cmd execwrapper.Command
|
2023-04-13 21:10:53 +01:00
|
|
|
}
|
|
|
|
|
2023-05-05 22:19:09 +01:00
|
|
|
// newProcess creates a new process.
|
|
|
|
// The cmd argument can be used to replace the subprocess with a runner for testing, it can be nil.
|
|
|
|
func newProcess(cmd execwrapper.Command, log *zap.Logger, executable string, args []string) *process {
|
2023-04-13 21:10:53 +01:00
|
|
|
return &process{
|
2023-05-05 22:19:09 +01:00
|
|
|
cmd: cmd,
|
2023-04-13 21:10:53 +01:00
|
|
|
log: log,
|
|
|
|
executable: executable,
|
|
|
|
args: args,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// run runs the process and decodes the response into the value pointed by `resp`.
|
|
|
|
// It returns an error if the Process fails to start, or if the Process exits with a non-zero status.
|
|
|
|
// NOTE: the `resp` value must be a pointer to a struct.
|
|
|
|
func (p *process) run(ctx context.Context, req, resp interface{}) (err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
p.log.Info("starting subprocess")
|
|
|
|
|
|
|
|
var buf, outbuf bytes.Buffer
|
|
|
|
writer := &zapWrapper{p.log.Named("subprocess")}
|
|
|
|
|
|
|
|
// encode the struct and write it to the buffer
|
|
|
|
enc := json.NewEncoder(&buf)
|
|
|
|
if err := enc.Encode(req); err != nil {
|
|
|
|
return errLazyFilewalker.Wrap(err)
|
|
|
|
}
|
|
|
|
|
2023-05-05 22:19:09 +01:00
|
|
|
if p.cmd == nil {
|
|
|
|
p.cmd = execwrapper.CommandContext(ctx, p.executable, p.args...)
|
2023-05-24 22:26:33 +01:00
|
|
|
} else {
|
|
|
|
args := append([]string{p.executable}, p.args...)
|
|
|
|
p.cmd.SetArgs(args)
|
2023-05-05 22:19:09 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
p.cmd.SetIn(&buf)
|
|
|
|
p.cmd.SetOut(&outbuf)
|
|
|
|
p.cmd.SetErr(writer)
|
2023-04-13 21:10:53 +01:00
|
|
|
|
2023-05-05 22:19:09 +01:00
|
|
|
if err := p.cmd.Start(); err != nil {
|
2023-04-13 21:10:53 +01:00
|
|
|
p.log.Error("failed to start subprocess", zap.Error(err))
|
|
|
|
return errLazyFilewalker.Wrap(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
p.log.Info("subprocess started")
|
|
|
|
|
2023-05-05 22:19:09 +01:00
|
|
|
if err := p.cmd.Wait(); err != nil {
|
2023-04-13 21:10:53 +01:00
|
|
|
var exitErr *execabs.ExitError
|
|
|
|
if errors.As(err, &exitErr) {
|
|
|
|
p.log.Info("subprocess exited with status", zap.Int("status", exitErr.ExitCode()), zap.Error(exitErr))
|
|
|
|
} else {
|
|
|
|
p.log.Error("subprocess exited with error", zap.Error(err))
|
|
|
|
}
|
|
|
|
return errLazyFilewalker.Wrap(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
p.log.Info("subprocess finished successfully")
|
|
|
|
|
|
|
|
// Decode and receive the response data struct from the subprocess
|
|
|
|
decoder := json.NewDecoder(&outbuf)
|
|
|
|
if err := decoder.Decode(&resp); err != nil {
|
|
|
|
p.log.Error("failed to decode response from subprocess", zap.Error(err))
|
|
|
|
return errLazyFilewalker.Wrap(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|