123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749 |
- package lua
- import (
- "bufio"
- "errors"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "os/exec"
- "syscall"
- )
- var ioFuncs = map[string]LGFunction{
- "close": ioClose,
- "flush": ioFlush,
- "lines": ioLines,
- "input": ioInput,
- "output": ioOutput,
- "open": ioOpenFile,
- "popen": ioPopen,
- "read": ioRead,
- "type": ioType,
- "tmpfile": ioTmpFile,
- "write": ioWrite,
- }
- const lFileClass = "FILE*"
- type lFile struct {
- fp *os.File
- pp *exec.Cmd
- writer io.Writer
- reader *bufio.Reader
- stdout io.ReadCloser
- closed bool
- }
- type lFileType int
- const (
- lFileFile lFileType = iota
- lFileProcess
- )
- const fileDefOutIndex = 1
- const fileDefInIndex = 2
- const fileDefaultWriteBuffer = 4096
- const fileDefaultReadBuffer = 4096
- func checkFile(L *LState) *lFile {
- ud := L.CheckUserData(1)
- if file, ok := ud.Value.(*lFile); ok {
- return file
- }
- L.ArgError(1, "file expected")
- return nil
- }
- func errorIfFileIsClosed(L *LState, file *lFile) {
- if file.closed {
- L.ArgError(1, "file is closed")
- }
- }
- func newFile(L *LState, file *os.File, path string, flag int, perm os.FileMode, writable, readable bool) (*LUserData, error) {
- ud := L.NewUserData()
- var err error
- if file == nil {
- file, err = os.OpenFile(path, flag, perm)
- if err != nil {
- return nil, err
- }
- }
- lfile := &lFile{fp: file, pp: nil, writer: nil, reader: nil, stdout: nil, closed: false}
- ud.Value = lfile
- if writable {
- lfile.writer = file
- }
- if readable {
- lfile.reader = bufio.NewReaderSize(file, fileDefaultReadBuffer)
- }
- L.SetMetatable(ud, L.GetTypeMetatable(lFileClass))
- return ud, nil
- }
- func newProcess(L *LState, cmd string, writable, readable bool) (*LUserData, error) {
- ud := L.NewUserData()
- c, args := popenArgs(cmd)
- pp := exec.Command(c, args...)
- lfile := &lFile{fp: nil, pp: pp, writer: nil, reader: nil, stdout: nil, closed: false}
- ud.Value = lfile
- var err error
- if writable {
- lfile.writer, err = pp.StdinPipe()
- }
- if readable {
- lfile.stdout, err = pp.StdoutPipe()
- lfile.reader = bufio.NewReaderSize(lfile.stdout, fileDefaultReadBuffer)
- }
- if err != nil {
- return nil, err
- }
- err = pp.Start()
- if err != nil {
- return nil, err
- }
- L.SetMetatable(ud, L.GetTypeMetatable(lFileClass))
- return ud, nil
- }
- func (file *lFile) Type() lFileType {
- if file.fp == nil {
- return lFileProcess
- }
- return lFileFile
- }
- func (file *lFile) Name() string {
- switch file.Type() {
- case lFileFile:
- return fmt.Sprintf("file %s", file.fp.Name())
- case lFileProcess:
- return fmt.Sprintf("process %s", file.pp.Path)
- }
- return ""
- }
- 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
- }
- file.reader = bufio.NewReaderSize(file.fp, fileDefaultReadBuffer)
- }
- return nil
- }
- func fileDefOut(L *LState) *LUserData {
- return L.Get(UpvalueIndex(1)).(*LTable).RawGetInt(fileDefOutIndex).(*LUserData)
- }
- func fileDefIn(L *LState) *LUserData {
- return L.Get(UpvalueIndex(1)).(*LTable).RawGetInt(fileDefInIndex).(*LUserData)
- }
- func fileIsWritable(L *LState, file *lFile) int {
- if file.writer == nil {
- L.Push(LNil)
- L.Push(LString(fmt.Sprintf("%s is opened for only reading.", file.Name())))
- L.Push(LNumber(1)) // C-Lua compatibility: Original Lua pushes errno to the stack
- return 3
- }
- return 0
- }
- func fileIsReadable(L *LState, file *lFile) int {
- if file.reader == nil {
- L.Push(LNil)
- L.Push(LString(fmt.Sprintf("%s is opened for only writing.", file.Name())))
- L.Push(LNumber(1)) // C-Lua compatibility: Original Lua pushes errno to the stack
- return 3
- }
- 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},
- }
- func OpenIo(L *LState) int {
- mod := L.RegisterModule(IoLibName, map[string]LGFunction{}).(*LTable)
- mt := L.NewTypeMetatable(lFileClass)
- mt.RawSetString("__index", mt)
- 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)
- }
- uv := L.CreateTable(2, 0)
- uv.RawSetInt(fileDefOutIndex, mod.RawGetString("stdout"))
- uv.RawSetInt(fileDefInIndex, mod.RawGetString("stdin"))
- for name, fn := range ioFuncs {
- mod.RawSetString(name, L.NewClosure(fn, uv))
- }
- mod.RawSetString("lines", L.NewClosure(ioLines, uv, L.NewClosure(ioLinesIter, uv)))
- // Modifications are being made in-place rather than returned?
- L.Push(mod)
- return 1
- }
- var fileMethods = map[string]LGFunction{
- "__tostring": fileToString,
- "write": fileWrite,
- "close": fileClose,
- "flush": fileFlush,
- "lines": fileLines,
- "read": fileRead,
- "seek": fileSeek,
- "setvbuf": fileSetVBuf,
- }
- func fileToString(L *LState) int {
- file := checkFile(L)
- if file.Type() == lFileFile {
- if file.closed {
- L.Push(LString("file (closed)"))
- } else {
- L.Push(LString("file"))
- }
- } else {
- if file.closed {
- L.Push(LString("process (closed)"))
- } else {
- L.Push(LString("process"))
- }
- }
- return 1
- }
- func fileWriteAux(L *LState, file *lFile, idx int) int {
- if n := fileIsWritable(L, file); n != 0 {
- return n
- }
- errorIfFileIsClosed(L, file)
- top := L.GetTop()
- out := file.writer
- var err error
- for i := idx; i <= top; i++ {
- L.CheckTypes(i, LTNumber, LTString)
- s := LVAsString(L.Get(i))
- if _, err = out.Write(unsafeFastStringToReadOnlyBytes(s)); err != nil {
- goto errreturn
- }
- }
- file.AbandonReadBuffer()
- L.Push(LTrue)
- return 1
- errreturn:
- file.AbandonReadBuffer()
- L.Push(LNil)
- L.Push(LString(err.Error()))
- L.Push(LNumber(1)) // C-Lua compatibility: Original Lua pushes errno to the stack
- return 3
- }
- func fileCloseAux(L *LState, file *lFile) int {
- file.closed = true
- var err error
- if file.writer != nil {
- if bwriter, ok := file.writer.(*bufio.Writer); ok {
- if err = bwriter.Flush(); err != nil {
- goto errreturn
- }
- }
- }
- file.AbandonReadBuffer()
- switch file.Type() {
- case lFileFile:
- if err = file.fp.Close(); err != nil {
- goto errreturn
- }
- L.Push(LTrue)
- return 1
- case lFileProcess:
- if file.stdout != nil {
- file.stdout.Close() // ignore errors
- }
- err = file.pp.Wait()
- var exitStatus int // Initialised to zero value = 0
- if err != nil {
- if e2, ok := err.(*exec.ExitError); ok {
- if s, ok := e2.Sys().(syscall.WaitStatus); ok {
- exitStatus = s.ExitStatus()
- } else {
- err = errors.New("Unimplemented for system where exec.ExitError.Sys() is not syscall.WaitStatus.")
- }
- }
- } else {
- exitStatus = 0
- }
- L.Push(LNumber(exitStatus))
- return 1
- }
- errreturn:
- L.RaiseError(err.Error())
- return 0
- }
- func fileFlushAux(L *LState, file *lFile) int {
- if n := fileIsWritable(L, file); n != 0 {
- return n
- }
- errorIfFileIsClosed(L, file)
- if bwriter, ok := file.writer.(*bufio.Writer); ok {
- if err := bwriter.Flush(); err != nil {
- L.Push(LNil)
- L.Push(LString(err.Error()))
- return 2
- }
- }
- L.Push(LTrue)
- return 1
- }
- func fileReadAux(L *LState, file *lFile, idx int) int {
- if n := fileIsReadable(L, file); n != 0 {
- return n
- }
- errorIfFileIsClosed(L, file)
- if L.GetTop() == idx-1 {
- L.Push(LString("*l"))
- }
- var err error
- top := L.GetTop()
- for i := idx; i <= top; i++ {
- switch lv := L.Get(i).(type) {
- case LNumber:
- size := int64(lv)
- if size == 0 {
- _, err = file.reader.ReadByte()
- if err == io.EOF {
- L.Push(LNil)
- goto normalreturn
- }
- file.reader.UnreadByte()
- }
- var buf []byte
- var iseof bool
- buf, err, iseof = readBufioSize(file.reader, size)
- if iseof {
- L.Push(LNil)
- goto normalreturn
- }
- if err != nil {
- goto errreturn
- }
- L.Push(LString(string(buf)))
- case LString:
- options := L.CheckString(i)
- if len(options) > 0 && options[0] != '*' {
- L.ArgError(2, "invalid options:"+options)
- }
- for _, opt := range options[1:] {
- switch opt {
- case 'n':
- var v LNumber
- _, err = fmt.Fscanf(file.reader, LNumberScanFormat, &v)
- if err == io.EOF {
- L.Push(LNil)
- goto normalreturn
- }
- if err != nil {
- goto errreturn
- }
- L.Push(v)
- case 'a':
- var buf []byte
- buf, err = ioutil.ReadAll(file.reader)
- if err == io.EOF {
- L.Push(emptyLString)
- goto normalreturn
- }
- if err != nil {
- goto errreturn
- }
- L.Push(LString(string(buf)))
- case 'l':
- var buf []byte
- var iseof bool
- buf, err, iseof = readBufioLine(file.reader)
- if iseof {
- L.Push(LNil)
- goto normalreturn
- }
- if err != nil {
- goto errreturn
- }
- L.Push(LString(string(buf)))
- default:
- L.ArgError(2, "invalid options:"+string(opt))
- }
- }
- }
- }
- normalreturn:
- return L.GetTop() - top
- errreturn:
- L.Push(LNil)
- L.Push(LString(err.Error()))
- L.Push(LNumber(1)) // C-Lua compatibility: Original Lua pushes errno to the stack
- return 3
- }
- var fileSeekOptions = []string{"set", "cur", "end"}
- func fileSeek(L *LState) int {
- file := checkFile(L)
- if file.Type() != lFileFile {
- L.Push(LNil)
- L.Push(LString("can not seek a process."))
- return 2
- }
- top := L.GetTop()
- if top == 1 {
- L.Push(LString("cur"))
- L.Push(LNumber(0))
- } else if top == 2 {
- L.Push(LNumber(0))
- }
- var pos int64
- var err error
- err = file.AbandonReadBuffer()
- if err != nil {
- goto errreturn
- }
- pos, err = file.fp.Seek(L.CheckInt64(3), L.CheckOption(2, fileSeekOptions))
- if err != nil {
- goto errreturn
- }
- L.Push(LNumber(pos))
- return 1
- errreturn:
- L.Push(LNil)
- L.Push(LString(err.Error()))
- return 2
- }
- func fileWrite(L *LState) int {
- return fileWriteAux(L, checkFile(L), 2)
- }
- func fileClose(L *LState) int {
- return fileCloseAux(L, checkFile(L))
- }
- func fileFlush(L *LState) int {
- return fileFlushAux(L, checkFile(L))
- }
- func fileLinesIter(L *LState) int {
- var file *lFile
- if ud, ok := L.Get(1).(*LUserData); ok {
- file = ud.Value.(*lFile)
- } else {
- file = L.Get(UpvalueIndex(2)).(*LUserData).Value.(*lFile)
- }
- buf, _, err := file.reader.ReadLine()
- if err != nil {
- if err == io.EOF {
- L.Push(LNil)
- return 1
- }
- L.RaiseError(err.Error())
- }
- L.Push(LString(string(buf)))
- return 1
- }
- func fileLines(L *LState) int {
- file := checkFile(L)
- ud := L.CheckUserData(1)
- if n := fileIsReadable(L, file); n != 0 {
- return 0
- }
- L.Push(L.NewClosure(fileLinesIter, L.Get(UpvalueIndex(1)), ud))
- return 1
- }
- func fileRead(L *LState) int {
- return fileReadAux(L, checkFile(L), 2)
- }
- var filebufOptions = []string{"no", "full"}
- func fileSetVBuf(L *LState) int {
- var err error
- var writer io.Writer
- file := checkFile(L)
- if n := fileIsWritable(L, file); n != 0 {
- return n
- }
- switch filebufOptions[L.CheckOption(2, filebufOptions)] {
- case "no":
- switch file.Type() {
- case lFileFile:
- file.writer = file.fp
- case lFileProcess:
- file.writer, err = file.pp.StdinPipe()
- if err != nil {
- goto errreturn
- }
- }
- case "full", "line": // TODO line buffer not supported
- bufsize := L.OptInt(3, fileDefaultWriteBuffer)
- switch file.Type() {
- case lFileFile:
- file.writer = bufio.NewWriterSize(file.fp, bufsize)
- case lFileProcess:
- writer, err = file.pp.StdinPipe()
- if err != nil {
- goto errreturn
- }
- file.writer = bufio.NewWriterSize(writer, bufsize)
- }
- }
- L.Push(LTrue)
- return 1
- errreturn:
- L.Push(LNil)
- L.Push(LString(err.Error()))
- return 2
- }
- func ioInput(L *LState) int {
- if L.GetTop() == 0 {
- L.Push(fileDefIn(L))
- return 1
- }
- switch lv := L.Get(1).(type) {
- case LString:
- file, err := newFile(L, nil, string(lv), os.O_RDONLY, 0600, false, true)
- if err != nil {
- L.RaiseError(err.Error())
- }
- L.Get(UpvalueIndex(1)).(*LTable).RawSetInt(fileDefInIndex, file)
- L.Push(file)
- return 1
- case *LUserData:
- if _, ok := lv.Value.(*lFile); ok {
- L.Get(UpvalueIndex(1)).(*LTable).RawSetInt(fileDefInIndex, lv)
- L.Push(lv)
- return 1
- }
- }
- L.ArgError(1, "string or file expedted, but got "+L.Get(1).Type().String())
- return 0
- }
- func ioClose(L *LState) int {
- if L.GetTop() == 0 {
- return fileCloseAux(L, fileDefOut(L).Value.(*lFile))
- }
- return fileClose(L)
- }
- func ioFlush(L *LState) int {
- return fileFlushAux(L, fileDefOut(L).Value.(*lFile))
- }
- func ioLinesIter(L *LState) int {
- var file *lFile
- toclose := false
- if ud, ok := L.Get(1).(*LUserData); ok {
- file = ud.Value.(*lFile)
- } else {
- file = L.Get(UpvalueIndex(2)).(*LUserData).Value.(*lFile)
- toclose = true
- }
- buf, _, err := file.reader.ReadLine()
- if err != nil {
- if err == io.EOF {
- if toclose {
- fileCloseAux(L, file)
- }
- L.Push(LNil)
- return 1
- }
- L.RaiseError(err.Error())
- }
- L.Push(LString(string(buf)))
- return 1
- }
- func ioLines(L *LState) int {
- if L.GetTop() == 0 {
- L.Push(L.Get(UpvalueIndex(2)))
- L.Push(fileDefIn(L))
- return 2
- }
- path := L.CheckString(1)
- ud, err := newFile(L, nil, path, os.O_RDONLY, os.FileMode(0600), false, true)
- if err != nil {
- return 0
- }
- L.Push(L.NewClosure(ioLinesIter, L.Get(UpvalueIndex(1)), ud))
- return 1
- }
- var ioOpenOpions = []string{"r", "rb", "w", "wb", "a", "ab", "r+", "rb+", "w+", "wb+", "a+", "ab+"}
- func ioOpenFile(L *LState) int {
- path := L.CheckString(1)
- if L.GetTop() == 1 {
- L.Push(LString("r"))
- }
- mode := os.O_RDONLY
- perm := 0600
- writable := true
- readable := true
- switch ioOpenOpions[L.CheckOption(2, ioOpenOpions)] {
- case "r", "rb":
- mode = os.O_RDONLY
- writable = false
- case "w", "wb":
- mode = os.O_WRONLY | os.O_TRUNC | os.O_CREATE
- readable = false
- case "a", "ab":
- mode = os.O_WRONLY | os.O_APPEND | os.O_CREATE
- case "r+", "rb+":
- mode = os.O_RDWR
- case "w+", "wb+":
- mode = os.O_RDWR | os.O_TRUNC | os.O_CREATE
- case "a+", "ab+":
- mode = os.O_APPEND | os.O_RDWR | os.O_CREATE
- }
- file, err := newFile(L, nil, path, mode, os.FileMode(perm), writable, readable)
- if err != nil {
- L.Push(LNil)
- L.Push(LString(err.Error()))
- L.Push(LNumber(1)) // C-Lua compatibility: Original Lua pushes errno to the stack
- return 3
- }
- L.Push(file)
- return 1
- }
- var ioPopenOptions = []string{"r", "w"}
- func ioPopen(L *LState) int {
- cmd := L.CheckString(1)
- if L.GetTop() == 1 {
- L.Push(LString("r"))
- } else if L.GetTop() > 1 && (L.Get(2)).Type() == LTNil {
- L.SetTop(1)
- L.Push(LString("r"))
- }
- var file *LUserData
- var err error
- switch ioPopenOptions[L.CheckOption(2, ioPopenOptions)] {
- case "r":
- file, err = newProcess(L, cmd, false, true)
- case "w":
- file, err = newProcess(L, cmd, true, false)
- }
- if err != nil {
- L.Push(LNil)
- L.Push(LString(err.Error()))
- return 2
- }
- L.Push(file)
- return 1
- }
- func ioRead(L *LState) int {
- return fileReadAux(L, fileDefIn(L).Value.(*lFile), 1)
- }
- func ioType(L *LState) int {
- ud, udok := L.Get(1).(*LUserData)
- if !udok {
- L.Push(LNil)
- return 1
- }
- file, ok := ud.Value.(*lFile)
- if !ok {
- L.Push(LNil)
- return 1
- }
- if file.closed {
- L.Push(LString("closed file"))
- return 1
- }
- L.Push(LString("file"))
- return 1
- }
- func ioTmpFile(L *LState) int {
- file, err := ioutil.TempFile("", "")
- if err != nil {
- L.Push(LNil)
- L.Push(LString(err.Error()))
- return 2
- }
- L.G.tempFiles = append(L.G.tempFiles, file)
- ud, _ := newFile(L, file, "", 0, os.FileMode(0), true, true)
- L.Push(ud)
- return 1
- }
- func ioOutput(L *LState) int {
- if L.GetTop() == 0 {
- L.Push(fileDefOut(L))
- return 1
- }
- switch lv := L.Get(1).(type) {
- case LString:
- file, err := newFile(L, nil, string(lv), os.O_WRONLY|os.O_CREATE, 0600, true, false)
- if err != nil {
- L.RaiseError(err.Error())
- }
- L.Get(UpvalueIndex(1)).(*LTable).RawSetInt(fileDefOutIndex, file)
- L.Push(file)
- return 1
- case *LUserData:
- if _, ok := lv.Value.(*lFile); ok {
- L.Get(UpvalueIndex(1)).(*LTable).RawSetInt(fileDefOutIndex, lv)
- L.Push(lv)
- return 1
- }
- }
- L.ArgError(1, "string or file expedted, but got "+L.Get(1).Type().String())
- return 0
- }
- func ioWrite(L *LState) int {
- return fileWriteAux(L, fileDefOut(L).Value.(*lFile), 1)
- }
- //
|