1
0
mirror of https://github.com/go-task/task.git synced 2024-12-16 10:59:23 +02:00
task/vendor/mvdan.cc/sh/v3/interp/module.go

169 lines
4.6 KiB
Go
Raw Normal View History

// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package interp
import (
"context"
2018-01-03 19:12:40 +02:00
"fmt"
"io"
"os"
"os/exec"
2017-11-19 22:26:37 +02:00
"runtime"
2018-01-03 19:12:40 +02:00
"strings"
"syscall"
2017-11-19 22:26:37 +02:00
"time"
2018-12-15 19:44:17 +02:00
2019-09-27 00:04:09 +02:00
"mvdan.cc/sh/v3/expand"
)
2018-09-01 16:00:49 +02:00
// FromModuleContext returns the ModuleCtx value stored in ctx, if any.
func FromModuleContext(ctx context.Context) (ModuleCtx, bool) {
mc, ok := ctx.Value(moduleCtxKey{}).(ModuleCtx)
return mc, ok
}
type moduleCtxKey struct{}
// ModuleCtx is the data passed to all the module functions via a context value.
// It contains some of the current state of the Runner, as well as some fields
// necessary to implement some of the modules.
type ModuleCtx struct {
2018-12-15 19:44:17 +02:00
Env expand.Environ
2017-11-19 22:26:37 +02:00
Dir string
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
KillTimeout time.Duration
}
2018-01-03 19:12:40 +02:00
// UnixPath fixes absolute unix paths on Windows, for example converting
// "C:\\CurDir\\dev\\null" to "/dev/null".
2018-09-01 16:00:49 +02:00
func (mc ModuleCtx) UnixPath(path string) string {
2018-01-03 19:12:40 +02:00
if runtime.GOOS != "windows" {
return path
}
2018-09-01 16:00:49 +02:00
path = strings.TrimPrefix(path, mc.Dir)
2018-01-03 19:12:40 +02:00
return strings.Replace(path, `\`, `/`, -1)
}
2019-09-27 00:04:09 +02:00
// ExecModule is the module responsible for executing a program. It is
2018-01-03 19:12:40 +02:00
// executed for all CallExpr nodes where the first argument is neither a
// declared function nor a builtin.
//
// Note that the name is included as the first argument. If path is an
// empty string, it means that the executable did not exist or was not
// found in $PATH.
//
2018-09-01 16:00:49 +02:00
// Use a return error of type ExitStatus to set the exit status. A nil error has
// the same effect as ExitStatus(0). If the error is of any other type, the
// interpreter will come to a stop.
2019-09-27 00:04:09 +02:00
type ExecModule = func(ctx context.Context, path string, args []string) error
2018-09-01 16:00:49 +02:00
2019-09-27 00:04:09 +02:00
func DefaultExec(ctx context.Context, path string, args []string) error {
2018-09-01 16:00:49 +02:00
mc, _ := FromModuleContext(ctx)
2018-01-03 19:12:40 +02:00
if path == "" {
2018-09-01 16:00:49 +02:00
fmt.Fprintf(mc.Stderr, "%q: executable file not found in $PATH\n", args[0])
return ExitStatus(127)
2018-01-03 19:12:40 +02:00
}
cmd := exec.Cmd{
Path: path,
Args: args,
2018-09-01 16:00:49 +02:00
Env: execEnv(mc.Env),
Dir: mc.Dir,
Stdin: mc.Stdin,
Stdout: mc.Stdout,
Stderr: mc.Stderr,
2018-01-03 19:12:40 +02:00
}
2017-11-19 22:26:37 +02:00
err := cmd.Start()
if err == nil {
2018-09-01 16:00:49 +02:00
if done := ctx.Done(); done != nil {
2017-11-19 22:26:37 +02:00
go func() {
2018-01-03 19:12:40 +02:00
<-done
2018-09-01 16:00:49 +02:00
if mc.KillTimeout <= 0 || runtime.GOOS == "windows" {
2018-01-03 19:12:40 +02:00
_ = cmd.Process.Signal(os.Kill)
return
}
2018-04-28 20:35:15 +02:00
// TODO: don't temporarily leak this goroutine
// if the program stops itself with the
// interrupt.
2018-01-03 19:12:40 +02:00
go func() {
2018-09-01 16:00:49 +02:00
time.Sleep(mc.KillTimeout)
2018-01-03 19:12:40 +02:00
_ = cmd.Process.Signal(os.Kill)
}()
_ = cmd.Process.Signal(os.Interrupt)
2017-11-19 22:26:37 +02:00
}()
2018-01-03 19:12:40 +02:00
}
2017-11-19 22:26:37 +02:00
err = cmd.Wait()
}
switch x := err.(type) {
case *exec.ExitError:
// started, but errored - default to 1 if OS
// doesn't have exit statuses
if status, ok := x.Sys().(syscall.WaitStatus); ok {
2018-09-01 16:00:49 +02:00
if status.Signaled() && ctx.Err() != nil {
return ctx.Err()
2018-01-03 19:12:40 +02:00
}
2018-09-01 16:00:49 +02:00
return ExitStatus(status.ExitStatus())
}
2018-09-01 16:00:49 +02:00
return ExitStatus(1)
case *exec.Error:
// did not start
2018-09-01 16:00:49 +02:00
fmt.Fprintf(mc.Stderr, "%v\n", err)
return ExitStatus(127)
default:
2017-11-19 22:26:37 +02:00
return err
}
2019-09-27 00:04:09 +02:00
}
func ExecBuiltin(name string, fn func(ModuleCtx, []string) error) func(ExecModule) ExecModule {
return func(next ExecModule) ExecModule {
return func(ctx context.Context, path string, args []string) error {
if args[0] == name {
mc, _ := FromModuleContext(ctx)
return fn(mc, args[1:])
}
return next(ctx, path, args)
}
}
}
2019-09-27 00:04:09 +02:00
// OpenModule is the module responsible for opening a file. It is
// executed for all files that are opened directly by the shell, such as
// in redirects. Files opened by executed programs are not included.
//
// The path parameter is absolute and has been cleaned.
//
// Use a return error of type *os.PathError to have the error printed to
2018-09-01 16:00:49 +02:00
// stderr and the exit status set to 1. If the error is of any other type, the
// interpreter will come to a stop.
2019-09-27 00:04:09 +02:00
type OpenModule = func(ctx context.Context, path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error)
2018-09-01 16:00:49 +02:00
2019-09-27 00:04:09 +02:00
func DefaultOpen(ctx context.Context, path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {
return os.OpenFile(path, flag, perm)
2019-09-27 00:04:09 +02:00
}
2019-09-27 00:04:09 +02:00
func OpenDevImpls(next OpenModule) OpenModule {
2018-09-01 16:00:49 +02:00
return func(ctx context.Context, path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {
mc, _ := FromModuleContext(ctx)
switch mc.UnixPath(path) {
case "/dev/null":
return devNull{}, nil
}
return next(ctx, path, flag, perm)
}
}
var _ io.ReadWriteCloser = devNull{}
type devNull struct{}
func (devNull) Read(p []byte) (int, error) { return 0, io.EOF }
func (devNull) Write(p []byte) (int, error) { return len(p), nil }
func (devNull) Close() error { return nil }