From 3f7d30f1bdeb46b483b46eecc45c9bf6d6960864 Mon Sep 17 00:00:00 2001 From: maja42 <2649265+maja42@users.noreply.github.com> Date: Mon, 20 Jun 2022 09:16:16 +0200 Subject: [PATCH] Allow alternative streams for stdin,stdout,stderr (fixes #349) --- _state.go | 2 +- auxlib.go | 8 +++--- baselib.go | 8 +++--- iolib.go | 72 +++++++++++++++++++++++++++++++++++++----------------- oslib.go | 20 ++++++++++++++- state.go | 21 +++++++++++++++- 6 files changed, 98 insertions(+), 33 deletions(-) diff --git a/_state.go b/_state.go index 960e8810..84c7a239 100644 --- a/_state.go +++ b/_state.go @@ -2028,7 +2028,7 @@ func (ls *LState) SetMx(mx int) { for atomic.LoadInt32(&ls.stop) == 0 { runtime.ReadMemStats(&s) if s.Alloc >= limit { - fmt.Println("out of memory") + fmt.Fprintln(ls.Options.Stdout, "out of memory") os.Exit(3) } time.Sleep(100 * time.Millisecond) diff --git a/auxlib.go b/auxlib.go index 61a3b8b6..5fba0f0d 100644 --- a/auxlib.go +++ b/auxlib.go @@ -345,19 +345,19 @@ func (ls *LState) CallMeta(obj LValue, event string) LValue { /* load and function call operations {{{ */ func (ls *LState) LoadFile(path string) (*LFunction, error) { - var file *os.File + var reader *bufio.Reader var err error if len(path) == 0 { - file = os.Stdin + reader = bufio.NewReader(ls.Options.Stdin) } else { - file, err = os.Open(path) + file, err := os.Open(path) defer file.Close() if err != nil { return nil, newApiErrorE(ApiErrorFile, err) } + reader = bufio.NewReader(file) } - reader := bufio.NewReader(file) // get the first character. c, err := reader.ReadByte() if err != nil && err != io.EOF { diff --git a/baselib.go b/baselib.go index aa2c08a9..b24f1c5c 100644 --- a/baselib.go +++ b/baselib.go @@ -200,7 +200,7 @@ func baseLoadFile(L *LState) int { var chunkname string var err error if L.GetTop() < 1 { - reader = os.Stdin + reader = L.Options.Stdin chunkname = "" } else { chunkname = L.CheckString(1) @@ -283,12 +283,12 @@ func basePCall(L *LState) int { func basePrint(L *LState) int { top := L.GetTop() for i := 1; i <= top; i++ { - fmt.Print(L.ToStringMeta(L.Get(i)).String()) + fmt.Fprint(L.Options.Stdout, L.ToStringMeta(L.Get(i)).String()) if i != top { - fmt.Print("\t") + fmt.Fprint(L.Options.Stdout, "\t") } } - fmt.Println("") + fmt.Fprintln(L.Options.Stdout, "") return 0 } diff --git a/iolib.go b/iolib.go index 4a86f893..810fbcbb 100644 --- a/iolib.go +++ b/iolib.go @@ -28,7 +28,7 @@ var ioFuncs = map[string]LGFunction{ const lFileClass = "FILE*" type lFile struct { - fp *os.File + fp io.ReadWriteCloser pp *exec.Cmd writer io.Writer reader *bufio.Reader @@ -63,7 +63,7 @@ func errorIfFileIsClosed(L *LState, file *lFile) { } } -func newFile(L *LState, file *os.File, path string, flag int, perm os.FileMode, writable, readable bool) (*LUserData, error) { +func newFile(L *LState, file io.ReadWriteCloser, path string, flag int, perm os.FileMode, writable, readable bool) (*LUserData, error) { ud := L.NewUserData() var err error if file == nil { @@ -121,7 +121,13 @@ func (file *lFile) Type() lFileType { func (file *lFile) Name() string { switch file.Type() { case lFileFile: - return fmt.Sprintf("file %s", file.fp.Name()) + switch f := file.fp.(type) { + case *os.File: + return fmt.Sprintf("file %s", f.Name()) + default: + return fmt.Sprintf("pipe") + } + case lFileProcess: return fmt.Sprintf("process %s", file.pp.Path) } @@ -130,11 +136,16 @@ func (file *lFile) Name() string { func (file *lFile) AbandonReadBuffer() error { if file.Type() == lFileFile && file.reader != nil { - _, err := file.fp.Seek(-int64(file.reader.Buffered()), 1) - if err != nil { - return err + if fp, ok := file.fp.(io.ReadSeeker); ok { + _, err := fp.Seek(-int64(file.reader.Buffered()), 1) + if err != nil { + return err + } + file.reader = bufio.NewReaderSize(fp, fileDefaultReadBuffer) + } else { + return errors.New("cannot seek") } - file.reader = bufio.NewReaderSize(file.fp, fileDefaultReadBuffer) + return nil } return nil } @@ -167,15 +178,20 @@ func fileIsReadable(L *LState, file *lFile) int { return 0 } -var stdFiles = []struct { - name string - file *os.File - writable bool - readable bool -}{ - {"stdout", os.Stdout, true, false}, - {"stdin", os.Stdin, false, true}, - {"stderr", os.Stderr, true, false}, +type nopWriter struct { + io.ReadCloser +} + +func (n nopWriter) Write(p []byte) (int, error) { + return 0, io.EOF +} + +type nopReader struct { + io.WriteCloser +} + +func (n nopReader) Read(p []byte) (int, error) { + return 0, io.EOF } func OpenIo(L *LState) int { @@ -185,10 +201,16 @@ func OpenIo(L *LState) int { L.SetFuncs(mt, fileMethods) mt.RawSetString("lines", L.NewClosure(fileLines, L.NewFunction(fileLinesIter))) - for _, finfo := range stdFiles { - file, _ := newFile(L, finfo.file, "", 0, os.FileMode(0), finfo.writable, finfo.readable) - mod.RawSetString(finfo.name, file) - } + // stdin + file, _ := newFile(L, nopWriter{L.Options.Stdin}, "", 0, os.FileMode(0), false, true) + mod.RawSetString("stdin", file) + // stdout + file, _ = newFile(L, nopReader{L.Options.Stdout}, "", 0, os.FileMode(0), true, false) + mod.RawSetString("stdout", file) + // stderr + file, _ = newFile(L, nopReader{L.Options.Stderr}, "", 0, os.FileMode(0), true, false) + mod.RawSetString("stderr", file) + uv := L.CreateTable(2, 0) uv.RawSetInt(fileDefOutIndex, mod.RawGetString("stdout")) uv.RawSetInt(fileDefInIndex, mod.RawGetString("stdin")) @@ -420,6 +442,12 @@ func fileSeek(L *LState) int { return 2 } + if _, ok := file.fp.(*os.File); !ok { + L.Push(LNil) + L.Push(LString("can not seek a pipe.")) + return 2 + } + top := L.GetTop() if top == 1 { L.Push(LString("cur")) @@ -436,7 +464,7 @@ func fileSeek(L *LState) int { goto errreturn } - pos, err = file.fp.Seek(L.CheckInt64(3), L.CheckOption(2, fileSeekOptions)) + pos, err = file.fp.(*os.File).Seek(L.CheckInt64(3), L.CheckOption(2, fileSeekOptions)) if err != nil { goto errreturn } @@ -735,7 +763,7 @@ func ioOutput(L *LState) int { } } - L.ArgError(1, "string or file expedted, but got "+L.Get(1).Type().String()) + L.ArgError(1, "string or file expected, but got "+L.Get(1).Type().String()) return 0 } diff --git a/oslib.go b/oslib.go index 256c8811..8aa6c824 100644 --- a/oslib.go +++ b/oslib.go @@ -1,6 +1,7 @@ package lua import ( + "io" "io/ioutil" "os" "strings" @@ -79,8 +80,20 @@ func osDiffTime(L *LState) int { } func osExecute(L *LState) int { + outReader, outWriter, err := os.Pipe() + errReader, errWriter, err := os.Pipe() + inReader, inWriter, err := os.Pipe() + inWriter.Close() + + copyDone := make(chan struct{}) + go func() { + io.Copy(L.Options.Stdout, outReader) + io.Copy(L.Options.Stderr, errReader) + close(copyDone) + }() + var procAttr os.ProcAttr - procAttr.Files = []*os.File{os.Stdin, os.Stdout, os.Stderr} + procAttr.Files = []*os.File{inReader, outWriter, errWriter} cmd, args := popenArgs(L.CheckString(1)) args = append([]string{cmd}, args...) process, err := os.StartProcess(cmd, args, &procAttr) @@ -90,6 +103,11 @@ func osExecute(L *LState) int { } ps, err := process.Wait() + + outWriter.Close() + errWriter.Close() + <-copyDone + if err != nil || !ps.Success() { L.Push(LNumber(1)) return 1 diff --git a/state.go b/state.go index a1ee672e..fd113ce7 100644 --- a/state.go +++ b/state.go @@ -109,6 +109,13 @@ type Options struct { // If `MinimizeStackMemory` is set, the call stack will be automatically grown or shrank up to a limit of // `CallStackSize` in order to minimize memory usage. This does incur a slight performance penalty. MinimizeStackMemory bool + + // Stdin stream. Defaults to os.Stdin + Stdin io.ReadCloser + // Stdout stream. Defaults to os.Stdout + Stdout io.WriteCloser + // Stderr stream. Defaults to os.Stderr + Stderr io.WriteCloser } /* }}} */ @@ -1351,6 +1358,9 @@ func NewState(opts ...Options) *LState { ls = newLState(Options{ CallStackSize: CallStackSize, RegistrySize: RegistrySize, + Stdin: os.Stdin, + Stdout: os.Stdout, + Stderr: os.Stderr, }) ls.OpenLibs() } else { @@ -1368,6 +1378,15 @@ func NewState(opts ...Options) *LState { opts[0].RegistryGrowStep = RegistryGrowStep } } + if opts[0].Stdin == nil { + opts[0].Stdin = os.Stdin + } + if opts[0].Stdout == nil { + opts[0].Stdout = os.Stdout + } + if opts[0].Stderr == nil { + opts[0].Stderr = os.Stderr + } ls = newLState(opts[0]) if !opts[0].SkipOpenLibs { ls.OpenLibs() @@ -2187,7 +2206,7 @@ func (ls *LState) SetMx(mx int) { for atomic.LoadInt32(&ls.stop) == 0 { runtime.ReadMemStats(&s) if s.Alloc >= limit { - fmt.Println("out of memory") + fmt.Fprintln(ls.Options.Stdout, "out of memory") os.Exit(3) } time.Sleep(100 * time.Millisecond)