2017-10-15 21:41:15 +02:00
|
|
|
// 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"
|
2017-10-15 21:41:15 +02:00
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
2017-11-19 22:26:37 +02:00
|
|
|
"runtime"
|
2018-01-03 19:12:40 +02:00
|
|
|
"strings"
|
2017-10-15 21:41:15 +02:00
|
|
|
"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"
|
2017-10-15 21:41:15 +02:00
|
|
|
)
|
|
|
|
|
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
|
2017-10-15 21:41:15 +02:00
|
|
|
}
|
|
|
|
|
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.
|
2017-10-15 21:41:15 +02:00
|
|
|
//
|
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()
|
|
|
|
}
|
|
|
|
|
2017-10-15 21:41:15 +02:00
|
|
|
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())
|
2017-10-15 21:41:15 +02:00
|
|
|
}
|
2018-09-01 16:00:49 +02:00
|
|
|
return ExitStatus(1)
|
2017-10-15 21:41:15 +02:00
|
|
|
case *exec.Error:
|
|
|
|
// did not start
|
2018-09-01 16:00:49 +02:00
|
|
|
fmt.Fprintf(mc.Stderr, "%v\n", err)
|
|
|
|
return ExitStatus(127)
|
2017-10-15 21:41:15 +02:00
|
|
|
default:
|
2017-11-19 22:26:37 +02:00
|
|
|
return err
|
2017-10-15 21:41:15 +02:00
|
|
|
}
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-10-15 21:41:15 +02:00
|
|
|
|
2019-09-27 00:04:09 +02:00
|
|
|
// OpenModule is the module responsible for opening a file. It is
|
2017-10-15 21:41:15 +02:00
|
|
|
// 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) {
|
2017-10-15 21:41:15 +02:00
|
|
|
return os.OpenFile(path, flag, perm)
|
2019-09-27 00:04:09 +02:00
|
|
|
}
|
2017-10-15 21:41:15 +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) {
|
2017-10-15 21:41:15 +02:00
|
|
|
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 }
|