cibo

git clone git://git.lin.moe/cibo.git

  1package main
  2
  3// GOMEMLIMIT
  4// runtime/debug.SetMemoryLimit
  5
  6import (
  7	"context"
  8	"errors"
  9	"flag"
 10	"fmt"
 11	"io"
 12	"io/fs"
 13	"os"
 14	"regexp"
 15	"runtime"
 16	"slices"
 17	"strings"
 18
 19	"mvdan.cc/sh/v3/expand"
 20	"mvdan.cc/sh/v3/interp"
 21	"mvdan.cc/sh/v3/syntax"
 22)
 23
 24var command = flag.String("c", "", "command to be executed")
 25var fname = flag.String("f", "", "")
 26var validShellKey = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`)
 27var ignoredEnvs = []string{
 28	"PWD", "OLDPWD", "PATH", "HOME",
 29	"UID", "GID", "EUID", "PPID",
 30	"SHELL", "IFS", "OPTIND",
 31}
 32
 33func main() {
 34	flag.Parse()
 35
 36	r, err := interp.New(
 37		interp.Env(expand.ListEnviron()),
 38		interp.Interactive(false),
 39		// interp.StdIO(os.Stdin, os.Stdout, os.Stderr),
 40		interp.ExecHandlers(noExec),
 41		interp.OpenHandler(noOpen),
 42		interp.ReadDirHandler2(noReadDir),
 43		interp.StatHandler(noStat),
 44	)
 45
 46	if err != nil {
 47		panic(err)
 48	}
 49
 50	err = runAll(r)
 51	if es, ok := errors.AsType[interp.ExitStatus](err); ok {
 52		os.Exit(int(es))
 53	}
 54	if err != nil {
 55		fmt.Fprintln(os.Stderr, err)
 56		os.Exit(1)
 57	}
 58
 59	for k, v := range r.Vars {
 60		if !validShellKey.MatchString(k) {
 61			continue
 62		}
 63		if v.Local {
 64			continue
 65		}
 66
 67		if slices.ContainsFunc(ignoredEnvs, func(envname string) bool {
 68			if runtime.GOOS == "windows" {
 69				return strings.ToUpper(k) == envname
 70			}
 71			return k == envname
 72		}) {
 73			continue
 74		}
 75
 76		var s strings.Builder
 77		if v.Exported {
 78			s.WriteString("export ")
 79		}
 80		s.WriteString(k + "=")
 81		s.WriteString("'" + strings.ReplaceAll(v.String(), "'", "'\\''") + "'")
 82		fmt.Println(s.String())
 83	}
 84}
 85
 86func noExec(next interp.ExecHandlerFunc) interp.ExecHandlerFunc {
 87	return func(_ context.Context, args []string) error {
 88		cmd := ""
 89		if len(args) > 0 {
 90			cmd = args[0]
 91		}
 92		return fmt.Errorf("exec `%s` is not allowed", cmd)
 93	}
 94}
 95
 96func noOpen(_ context.Context, _ string, _ int, _ os.FileMode) (io.ReadWriteCloser, error) {
 97	return nil, fmt.Errorf("open is not allowed")
 98}
 99func noReadDir(_ context.Context, _ string) ([]fs.DirEntry, error) {
100	return nil, fmt.Errorf("read dir is not allowed")
101}
102func noStat(_ context.Context, _ string, _ bool) (fs.FileInfo, error) {
103	return nil, fmt.Errorf("read files' info is not allowed")
104}
105
106func runAll(r *interp.Runner) error {
107	if *command != "" {
108		if err := run(r, strings.NewReader(*command), ""); err != nil {
109			return err
110		}
111	} else if flag.NArg() == 0 {
112		if err := run(r, os.Stdin, ""); err != nil {
113			return err
114		}
115	} else {
116		for _, path := range flag.Args() {
117			if err := runPath(r, path); err != nil {
118				return err
119			}
120		}
121	}
122
123	if *fname != "" {
124		fn, ok := r.Funcs[*fname]
125		if !ok {
126			return fmt.Errorf("function %s undefined", *fname)
127		}
128
129		return runNode(r, fn)
130	}
131	return nil
132}
133
134func run(r *interp.Runner, reader io.Reader, name string) error {
135	prog, err := syntax.NewParser().Parse(reader, name)
136	if err != nil {
137		return err
138	}
139	r.Reset()
140	ctx := context.Background()
141	return r.Run(ctx, prog)
142}
143
144func runNode(r *interp.Runner, n syntax.Node) error {
145	ctx := context.Background()
146	return r.Run(ctx, n)
147}
148
149func runPath(r *interp.Runner, path string) error {
150	f, err := os.Open(path)
151	if err != nil {
152		return err
153	}
154	defer f.Close()
155	return run(r, f, path)
156}