github.com/awirix/lua@v1.6.0/iolib.go (about)

     1  package lua
     2  
     3  import (
     4  	"bufio"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"os"
    10  	"os/exec"
    11  	"path/filepath"
    12  	"strings"
    13  	"syscall"
    14  )
    15  
    16  var ioFuncs = map[string]LGFunction{
    17  	"close":   ioClose,
    18  	"flush":   ioFlush,
    19  	"lines":   ioLines,
    20  	"input":   ioInput,
    21  	"output":  ioOutput,
    22  	"open":    ioOpenFile,
    23  	"popen":   ioPopen,
    24  	"read":    ioRead,
    25  	"type":    ioType,
    26  	"tmpfile": ioTmpFile,
    27  	"write":   ioWrite,
    28  }
    29  
    30  const lFileClass = "FILE*"
    31  
    32  type lFile struct {
    33  	fp     *os.File
    34  	pp     *exec.Cmd
    35  	writer io.Writer
    36  	reader *bufio.Reader
    37  	stdout io.ReadCloser
    38  	closed bool
    39  }
    40  
    41  type lFileType int
    42  
    43  const (
    44  	lFileFile lFileType = iota
    45  	lFileProcess
    46  )
    47  
    48  const fileDefOutIndex = 1
    49  const fileDefInIndex = 2
    50  const fileDefaultWriteBuffer = 4096
    51  const fileDefaultReadBuffer = 4096
    52  
    53  func checkFile(L *LState) *lFile {
    54  	ud := L.CheckUserData(1)
    55  	if file, ok := ud.Value.(*lFile); ok {
    56  		return file
    57  	}
    58  	L.ArgError(1, "file expected")
    59  	return nil
    60  }
    61  
    62  func errorIfFileIsClosed(L *LState, file *lFile) {
    63  	if file.closed {
    64  		L.ArgError(1, "file is closed")
    65  	}
    66  }
    67  
    68  func newFile(L *LState, file *os.File, path string, flag int, perm os.FileMode, writable, readable bool) (*LUserData, error) {
    69  	if L.Options.WorkingDir != "" && !filepath.IsAbs(path) {
    70  		path = filepath.Join(L.Options.WorkingDir, path)
    71  	}
    72  
    73  	path = filepath.Clean(path)
    74  
    75  	if L.Options.IsolateIO {
    76  		var abs string
    77  		if filepath.IsAbs(path) {
    78  			abs = path
    79  		} else {
    80  			var err error
    81  			abs, err = filepath.Abs(path)
    82  			if err != nil {
    83  				return nil, err
    84  			}
    85  		}
    86  
    87  		if !strings.HasPrefix(abs, L.Options.WorkingDir) {
    88  			return nil, fmt.Errorf("file %s is outside of the working directory", path)
    89  		}
    90  
    91  		// pass further
    92  	}
    93  
    94  	ud := L.NewUserData()
    95  	var err error
    96  	if file == nil {
    97  		file, err = os.OpenFile(path, flag, perm)
    98  		if err != nil {
    99  			return nil, err
   100  		}
   101  	}
   102  	lfile := &lFile{fp: file, pp: nil, writer: nil, reader: nil, stdout: nil, closed: false}
   103  	ud.Value = lfile
   104  	if writable {
   105  		lfile.writer = file
   106  	}
   107  	if readable {
   108  		lfile.reader = bufio.NewReaderSize(file, fileDefaultReadBuffer)
   109  	}
   110  	L.SetMetatable(ud, L.GetTypeMetatable(lFileClass))
   111  	return ud, nil
   112  }
   113  
   114  func newProcess(L *LState, cmd string, writable, readable bool) (*LUserData, error) {
   115  	ud := L.NewUserData()
   116  	c, args := popenArgs(cmd)
   117  	pp := exec.Command(c, args...)
   118  	lfile := &lFile{fp: nil, pp: pp, writer: nil, reader: nil, stdout: nil, closed: false}
   119  	ud.Value = lfile
   120  
   121  	var err error
   122  	if writable {
   123  		lfile.writer, err = pp.StdinPipe()
   124  	}
   125  	if readable {
   126  		lfile.stdout, err = pp.StdoutPipe()
   127  		lfile.reader = bufio.NewReaderSize(lfile.stdout, fileDefaultReadBuffer)
   128  	}
   129  	if err != nil {
   130  		return nil, err
   131  	}
   132  	err = pp.Start()
   133  	if err != nil {
   134  		return nil, err
   135  	}
   136  
   137  	L.SetMetatable(ud, L.GetTypeMetatable(lFileClass))
   138  	return ud, nil
   139  }
   140  
   141  func (file *lFile) Type() lFileType {
   142  	if file.fp == nil {
   143  		return lFileProcess
   144  	}
   145  	return lFileFile
   146  }
   147  
   148  func (file *lFile) Name() string {
   149  	switch file.Type() {
   150  	case lFileFile:
   151  		return fmt.Sprintf("file %s", file.fp.Name())
   152  	case lFileProcess:
   153  		return fmt.Sprintf("process %s", file.pp.Path)
   154  	}
   155  	return ""
   156  }
   157  
   158  func (file *lFile) AbandonReadBuffer() error {
   159  	if file.Type() == lFileFile && file.reader != nil {
   160  		_, err := file.fp.Seek(-int64(file.reader.Buffered()), 1)
   161  		if err != nil {
   162  			return err
   163  		}
   164  		file.reader = bufio.NewReaderSize(file.fp, fileDefaultReadBuffer)
   165  	}
   166  	return nil
   167  }
   168  
   169  func fileDefOut(L *LState) *LUserData {
   170  	return L.Get(UpvalueIndex(1)).(*LTable).RawGetInt(fileDefOutIndex).(*LUserData)
   171  }
   172  
   173  func fileDefIn(L *LState) *LUserData {
   174  	return L.Get(UpvalueIndex(1)).(*LTable).RawGetInt(fileDefInIndex).(*LUserData)
   175  }
   176  
   177  func fileIsWritable(L *LState, file *lFile) int {
   178  	if file.writer == nil {
   179  		L.Push(LNil)
   180  		L.Push(LString(fmt.Sprintf("%s is opened for only reading.", file.Name())))
   181  		L.Push(LNumber(1)) // C-Lua compatibility: Original Lua pushes errno to the stack
   182  		return 3
   183  	}
   184  	return 0
   185  }
   186  
   187  func fileIsReadable(L *LState, file *lFile) int {
   188  	if file.reader == nil {
   189  		L.Push(LNil)
   190  		L.Push(LString(fmt.Sprintf("%s is opened for only writing.", file.Name())))
   191  		L.Push(LNumber(1)) // C-Lua compatibility: Original Lua pushes errno to the stack
   192  		return 3
   193  	}
   194  	return 0
   195  }
   196  
   197  var stdFiles = []struct {
   198  	name     string
   199  	file     *os.File
   200  	writable bool
   201  	readable bool
   202  }{
   203  	{"stdout", os.Stdout, true, false},
   204  	{"stdin", os.Stdin, false, true},
   205  	{"stderr", os.Stderr, true, false},
   206  }
   207  
   208  func OpenIo(L *LState) int {
   209  	mod := L.RegisterModule(IoLibName, map[string]LGFunction{}).(*LTable)
   210  	mt := L.NewTypeMetatable(lFileClass)
   211  	mt.RawSetString("__index", mt)
   212  	L.SetFuncs(mt, fileMethods)
   213  	mt.RawSetString("lines", L.NewClosure(fileLines, L.NewFunction(fileLinesIter)))
   214  
   215  	for _, finfo := range stdFiles {
   216  		file, _ := newFile(L, finfo.file, "", 0, os.FileMode(0), finfo.writable, finfo.readable)
   217  		mod.RawSetString(finfo.name, file)
   218  	}
   219  	uv := L.CreateTable(2, 0)
   220  	uv.RawSetInt(fileDefOutIndex, mod.RawGetString("stdout"))
   221  	uv.RawSetInt(fileDefInIndex, mod.RawGetString("stdin"))
   222  	for name, fn := range ioFuncs {
   223  		mod.RawSetString(name, L.NewClosure(fn, uv))
   224  	}
   225  	mod.RawSetString("lines", L.NewClosure(ioLines, uv, L.NewClosure(ioLinesIter, uv)))
   226  	// Modifications are being made in-place rather than returned?
   227  	L.Push(mod)
   228  	return 1
   229  }
   230  
   231  var fileMethods = map[string]LGFunction{
   232  	"__tostring": fileToString,
   233  	"write":      fileWrite,
   234  	"close":      fileClose,
   235  	"flush":      fileFlush,
   236  	"lines":      fileLines,
   237  	"read":       fileRead,
   238  	"seek":       fileSeek,
   239  	"setvbuf":    fileSetVBuf,
   240  }
   241  
   242  func fileToString(L *LState) int {
   243  	file := checkFile(L)
   244  	if file.Type() == lFileFile {
   245  		if file.closed {
   246  			L.Push(LString("file (closed)"))
   247  		} else {
   248  			L.Push(LString("file"))
   249  		}
   250  	} else {
   251  		if file.closed {
   252  			L.Push(LString("process (closed)"))
   253  		} else {
   254  			L.Push(LString("process"))
   255  		}
   256  	}
   257  	return 1
   258  }
   259  
   260  func fileWriteAux(L *LState, file *lFile, idx int) int {
   261  	if n := fileIsWritable(L, file); n != 0 {
   262  		return n
   263  	}
   264  	errorIfFileIsClosed(L, file)
   265  	top := L.GetTop()
   266  	out := file.writer
   267  	var err error
   268  	for i := idx; i <= top; i++ {
   269  		L.CheckTypes(i, LTNumber, LTString)
   270  		s := LVAsString(L.Get(i))
   271  		if _, err = out.Write(unsafeFastStringToReadOnlyBytes(s)); err != nil {
   272  			goto errreturn
   273  		}
   274  	}
   275  
   276  	file.AbandonReadBuffer()
   277  	L.Push(LTrue)
   278  	return 1
   279  errreturn:
   280  
   281  	file.AbandonReadBuffer()
   282  	L.Push(LNil)
   283  	L.Push(LString(err.Error()))
   284  	L.Push(LNumber(1)) // C-Lua compatibility: Original Lua pushes errno to the stack
   285  	return 3
   286  }
   287  
   288  func fileCloseAux(L *LState, file *lFile) int {
   289  	file.closed = true
   290  	var err error
   291  	if file.writer != nil {
   292  		if bwriter, ok := file.writer.(*bufio.Writer); ok {
   293  			if err = bwriter.Flush(); err != nil {
   294  				goto errreturn
   295  			}
   296  		}
   297  	}
   298  	file.AbandonReadBuffer()
   299  
   300  	switch file.Type() {
   301  	case lFileFile:
   302  		if err = file.fp.Close(); err != nil {
   303  			goto errreturn
   304  		}
   305  		L.Push(LTrue)
   306  		return 1
   307  	case lFileProcess:
   308  		if file.stdout != nil {
   309  			file.stdout.Close() // ignore errors
   310  		}
   311  		err = file.pp.Wait()
   312  		var exitStatus int // Initialised to zero value = 0
   313  		if err != nil {
   314  			if e2, ok := err.(*exec.ExitError); ok {
   315  				if s, ok := e2.Sys().(syscall.WaitStatus); ok {
   316  					exitStatus = s.ExitStatus()
   317  				} else {
   318  					err = errors.New("Unimplemented for system where exec.ExitError.Sys() is not syscall.WaitStatus.")
   319  				}
   320  			}
   321  		} else {
   322  			exitStatus = 0
   323  		}
   324  		L.Push(LNumber(exitStatus))
   325  		return 1
   326  	}
   327  
   328  errreturn:
   329  	L.RaiseError(err.Error())
   330  	return 0
   331  }
   332  
   333  func fileFlushAux(L *LState, file *lFile) int {
   334  	if n := fileIsWritable(L, file); n != 0 {
   335  		return n
   336  	}
   337  	errorIfFileIsClosed(L, file)
   338  
   339  	if bwriter, ok := file.writer.(*bufio.Writer); ok {
   340  		if err := bwriter.Flush(); err != nil {
   341  			L.Push(LNil)
   342  			L.Push(LString(err.Error()))
   343  			return 2
   344  		}
   345  	}
   346  	L.Push(LTrue)
   347  	return 1
   348  }
   349  
   350  func fileReadAux(L *LState, file *lFile, idx int) int {
   351  	if n := fileIsReadable(L, file); n != 0 {
   352  		return n
   353  	}
   354  	errorIfFileIsClosed(L, file)
   355  	if L.GetTop() == idx-1 {
   356  		L.Push(LString("*l"))
   357  	}
   358  	var err error
   359  	top := L.GetTop()
   360  	for i := idx; i <= top; i++ {
   361  		switch lv := L.Get(i).(type) {
   362  		case LNumber:
   363  			size := int64(lv)
   364  			if size == 0 {
   365  				_, err = file.reader.ReadByte()
   366  				if err == io.EOF {
   367  					L.Push(LNil)
   368  					goto normalreturn
   369  				}
   370  				file.reader.UnreadByte()
   371  			}
   372  			var buf []byte
   373  			var iseof bool
   374  			buf, err, iseof = readBufioSize(file.reader, size)
   375  			if iseof {
   376  				L.Push(LNil)
   377  				goto normalreturn
   378  			}
   379  			if err != nil {
   380  				goto errreturn
   381  			}
   382  			L.Push(LString(buf))
   383  		case LString:
   384  			options := L.CheckString(i)
   385  			if len(options) > 0 && options[0] != '*' {
   386  				L.ArgError(2, "invalid options:"+options)
   387  			}
   388  			for _, opt := range options[1:] {
   389  				switch opt {
   390  				case 'n':
   391  					var v LNumber
   392  					_, err = fmt.Fscanf(file.reader, LNumberScanFormat, &v)
   393  					if err == io.EOF {
   394  						L.Push(LNil)
   395  						goto normalreturn
   396  					}
   397  					if err != nil {
   398  						goto errreturn
   399  					}
   400  					L.Push(v)
   401  				case 'a':
   402  					var buf []byte
   403  					buf, err = ioutil.ReadAll(file.reader)
   404  					if err == io.EOF {
   405  						L.Push(emptyLString)
   406  						goto normalreturn
   407  					}
   408  					if err != nil {
   409  						goto errreturn
   410  					}
   411  					L.Push(LString(string(buf)))
   412  				case 'l':
   413  					var buf []byte
   414  					var iseof bool
   415  					buf, err, iseof = readBufioLine(file.reader)
   416  					if iseof {
   417  						L.Push(LNil)
   418  						goto normalreturn
   419  					}
   420  					if err != nil {
   421  						goto errreturn
   422  					}
   423  					L.Push(LString(string(buf)))
   424  				default:
   425  					L.ArgError(2, "invalid options:"+string(opt))
   426  				}
   427  			}
   428  		}
   429  	}
   430  normalreturn:
   431  	return L.GetTop() - top
   432  
   433  errreturn:
   434  	L.RaiseError(err.Error())
   435  	//L.Push(LNil)
   436  	//L.Push(LString(err.Error()))
   437  	return 2
   438  }
   439  
   440  var fileSeekOptions = []string{"set", "cur", "end"}
   441  
   442  func fileSeek(L *LState) int {
   443  	file := checkFile(L)
   444  	if file.Type() != lFileFile {
   445  		L.Push(LNil)
   446  		L.Push(LString("can not seek a process."))
   447  		return 2
   448  	}
   449  
   450  	top := L.GetTop()
   451  	if top == 1 {
   452  		L.Push(LString("cur"))
   453  		L.Push(LNumber(0))
   454  	} else if top == 2 {
   455  		L.Push(LNumber(0))
   456  	}
   457  
   458  	var pos int64
   459  	var err error
   460  
   461  	err = file.AbandonReadBuffer()
   462  	if err != nil {
   463  		goto errreturn
   464  	}
   465  
   466  	pos, err = file.fp.Seek(L.CheckInt64(3), L.CheckOption(2, fileSeekOptions))
   467  	if err != nil {
   468  		goto errreturn
   469  	}
   470  
   471  	L.Push(LNumber(pos))
   472  	return 1
   473  
   474  errreturn:
   475  	L.Push(LNil)
   476  	L.Push(LString(err.Error()))
   477  	return 2
   478  }
   479  
   480  func fileWrite(L *LState) int {
   481  	return fileWriteAux(L, checkFile(L), 2)
   482  }
   483  
   484  func fileClose(L *LState) int {
   485  	return fileCloseAux(L, checkFile(L))
   486  }
   487  
   488  func fileFlush(L *LState) int {
   489  	return fileFlushAux(L, checkFile(L))
   490  }
   491  
   492  func fileLinesIter(L *LState) int {
   493  	var file *lFile
   494  	if ud, ok := L.Get(1).(*LUserData); ok {
   495  		file = ud.Value.(*lFile)
   496  	} else {
   497  		file = L.Get(UpvalueIndex(2)).(*LUserData).Value.(*lFile)
   498  	}
   499  	buf, _, err := file.reader.ReadLine()
   500  	if err != nil {
   501  		if err == io.EOF {
   502  			L.Push(LNil)
   503  			return 1
   504  		}
   505  		L.RaiseError(err.Error())
   506  	}
   507  	L.Push(LString(string(buf)))
   508  	return 1
   509  }
   510  
   511  func fileLines(L *LState) int {
   512  	file := checkFile(L)
   513  	ud := L.CheckUserData(1)
   514  	if n := fileIsReadable(L, file); n != 0 {
   515  		return 0
   516  	}
   517  	L.Push(L.NewClosure(fileLinesIter, L.Get(UpvalueIndex(1)), ud))
   518  	return 1
   519  }
   520  
   521  func fileRead(L *LState) int {
   522  	return fileReadAux(L, checkFile(L), 2)
   523  }
   524  
   525  var filebufOptions = []string{"no", "full"}
   526  
   527  func fileSetVBuf(L *LState) int {
   528  	var err error
   529  	var writer io.Writer
   530  	file := checkFile(L)
   531  	if n := fileIsWritable(L, file); n != 0 {
   532  		return n
   533  	}
   534  	switch filebufOptions[L.CheckOption(2, filebufOptions)] {
   535  	case "no":
   536  		switch file.Type() {
   537  		case lFileFile:
   538  			file.writer = file.fp
   539  		case lFileProcess:
   540  			file.writer, err = file.pp.StdinPipe()
   541  			if err != nil {
   542  				goto errreturn
   543  			}
   544  		}
   545  	case "full", "line": // TODO line buffer not supported
   546  		bufsize := L.OptInt(3, fileDefaultWriteBuffer)
   547  		switch file.Type() {
   548  		case lFileFile:
   549  			file.writer = bufio.NewWriterSize(file.fp, bufsize)
   550  		case lFileProcess:
   551  			writer, err = file.pp.StdinPipe()
   552  			if err != nil {
   553  				goto errreturn
   554  			}
   555  			file.writer = bufio.NewWriterSize(writer, bufsize)
   556  		}
   557  	}
   558  	L.Push(LTrue)
   559  	return 1
   560  errreturn:
   561  	L.Push(LNil)
   562  	L.Push(LString(err.Error()))
   563  	return 2
   564  }
   565  
   566  func ioInput(L *LState) int {
   567  	if L.GetTop() == 0 {
   568  		L.Push(fileDefIn(L))
   569  		return 1
   570  	}
   571  	switch lv := L.Get(1).(type) {
   572  	case LString:
   573  		file, err := newFile(L, nil, string(lv), os.O_RDONLY, 0600, false, true)
   574  		if err != nil {
   575  			L.RaiseError(err.Error())
   576  		}
   577  		L.Get(UpvalueIndex(1)).(*LTable).RawSetInt(fileDefInIndex, file)
   578  		L.Push(file)
   579  		return 1
   580  	case *LUserData:
   581  		if _, ok := lv.Value.(*lFile); ok {
   582  			L.Get(UpvalueIndex(1)).(*LTable).RawSetInt(fileDefInIndex, lv)
   583  			L.Push(lv)
   584  			return 1
   585  		}
   586  
   587  	}
   588  	L.ArgError(1, "string or file expedted, but got "+L.Get(1).Type().String())
   589  	return 0
   590  }
   591  
   592  func ioClose(L *LState) int {
   593  	if L.GetTop() == 0 {
   594  		return fileCloseAux(L, fileDefOut(L).Value.(*lFile))
   595  	}
   596  	return fileClose(L)
   597  }
   598  
   599  func ioFlush(L *LState) int {
   600  	return fileFlushAux(L, fileDefOut(L).Value.(*lFile))
   601  }
   602  
   603  func ioLinesIter(L *LState) int {
   604  	var file *lFile
   605  	toclose := false
   606  	if ud, ok := L.Get(1).(*LUserData); ok {
   607  		file = ud.Value.(*lFile)
   608  	} else {
   609  		file = L.Get(UpvalueIndex(2)).(*LUserData).Value.(*lFile)
   610  		toclose = true
   611  	}
   612  	buf, _, err := file.reader.ReadLine()
   613  	if err != nil {
   614  		if err == io.EOF {
   615  			if toclose {
   616  				fileCloseAux(L, file)
   617  			}
   618  			L.Push(LNil)
   619  			return 1
   620  		}
   621  		L.RaiseError(err.Error())
   622  	}
   623  	L.Push(LString(string(buf)))
   624  	return 1
   625  }
   626  
   627  func ioLines(L *LState) int {
   628  	if L.GetTop() == 0 {
   629  		L.Push(L.Get(UpvalueIndex(2)))
   630  		L.Push(fileDefIn(L))
   631  		return 2
   632  	}
   633  
   634  	path := L.CheckString(1)
   635  	ud, err := newFile(L, nil, path, os.O_RDONLY, os.FileMode(0600), false, true)
   636  	if err != nil {
   637  		return 0
   638  	}
   639  	L.Push(L.NewClosure(ioLinesIter, L.Get(UpvalueIndex(1)), ud))
   640  	return 1
   641  }
   642  
   643  var ioOpenOpions = []string{"r", "rb", "w", "wb", "a", "ab", "r+", "rb+", "w+", "wb+", "a+", "ab+"}
   644  
   645  func ioOpenFile(L *LState) int {
   646  	path := L.CheckString(1)
   647  	if L.GetTop() == 1 {
   648  		L.Push(LString("r"))
   649  	}
   650  	mode := os.O_RDONLY
   651  	perm := 0600
   652  	writable := true
   653  	readable := true
   654  	switch ioOpenOpions[L.CheckOption(2, ioOpenOpions)] {
   655  	case "r", "rb":
   656  		mode = os.O_RDONLY
   657  		writable = false
   658  	case "w", "wb":
   659  		mode = os.O_WRONLY | os.O_CREATE
   660  		readable = false
   661  	case "a", "ab":
   662  		mode = os.O_WRONLY | os.O_APPEND | os.O_CREATE
   663  	case "r+", "rb+":
   664  		mode = os.O_RDWR
   665  	case "w+", "wb+":
   666  		mode = os.O_RDWR | os.O_TRUNC | os.O_CREATE
   667  	case "a+", "ab+":
   668  		mode = os.O_APPEND | os.O_RDWR | os.O_CREATE
   669  	}
   670  	file, err := newFile(L, nil, path, mode, os.FileMode(perm), writable, readable)
   671  	if err != nil {
   672  		L.Push(LNil)
   673  		L.Push(LString(err.Error()))
   674  		L.Push(LNumber(1)) // C-Lua compatibility: Original Lua pushes errno to the stack
   675  		return 3
   676  	}
   677  	L.Push(file)
   678  	return 1
   679  
   680  }
   681  
   682  var ioPopenOptions = []string{"r", "w"}
   683  
   684  func ioPopen(L *LState) int {
   685  	cmd := L.CheckString(1)
   686  	if L.GetTop() == 1 {
   687  		L.Push(LString("r"))
   688  	}
   689  	var file *LUserData
   690  	var err error
   691  
   692  	switch ioPopenOptions[L.CheckOption(2, ioPopenOptions)] {
   693  	case "r":
   694  		file, err = newProcess(L, cmd, false, true)
   695  	case "w":
   696  		file, err = newProcess(L, cmd, true, false)
   697  	}
   698  	if err != nil {
   699  		L.Push(LNil)
   700  		L.Push(LString(err.Error()))
   701  		return 2
   702  	}
   703  	L.Push(file)
   704  	return 1
   705  }
   706  
   707  func ioRead(L *LState) int {
   708  	return fileReadAux(L, fileDefIn(L).Value.(*lFile), 1)
   709  }
   710  
   711  func ioType(L *LState) int {
   712  	ud, udok := L.Get(1).(*LUserData)
   713  	if !udok {
   714  		L.Push(LNil)
   715  		return 1
   716  	}
   717  	file, ok := ud.Value.(*lFile)
   718  	if !ok {
   719  		L.Push(LNil)
   720  		return 1
   721  	}
   722  	if file.closed {
   723  		L.Push(LString("closed file"))
   724  		return 1
   725  	}
   726  	L.Push(LString("file"))
   727  	return 1
   728  }
   729  
   730  func ioTmpFile(L *LState) int {
   731  	file, err := os.CreateTemp(L.Options.TempDir, "")
   732  	if err != nil {
   733  		L.Push(LNil)
   734  		L.Push(LString(err.Error()))
   735  		return 2
   736  	}
   737  	L.G.tempFiles = append(L.G.tempFiles, file)
   738  	ud, _ := newFile(L, file, "", 0, os.FileMode(0), true, true)
   739  	L.Push(ud)
   740  	return 1
   741  }
   742  
   743  func ioOutput(L *LState) int {
   744  	if L.GetTop() == 0 {
   745  		L.Push(fileDefOut(L))
   746  		return 1
   747  	}
   748  	switch lv := L.Get(1).(type) {
   749  	case LString:
   750  		file, err := newFile(L, nil, string(lv), os.O_WRONLY|os.O_CREATE, 0600, true, false)
   751  		if err != nil {
   752  			L.RaiseError(err.Error())
   753  		}
   754  		L.Get(UpvalueIndex(1)).(*LTable).RawSetInt(fileDefOutIndex, file)
   755  		L.Push(file)
   756  		return 1
   757  	case *LUserData:
   758  		if _, ok := lv.Value.(*lFile); ok {
   759  			L.Get(UpvalueIndex(1)).(*LTable).RawSetInt(fileDefOutIndex, lv)
   760  			L.Push(lv)
   761  			return 1
   762  		}
   763  
   764  	}
   765  	L.ArgError(1, "string or file expedted, but got "+L.Get(1).Type().String())
   766  	return 0
   767  }
   768  
   769  func ioWrite(L *LState) int {
   770  	return fileWriteAux(L, fileDefOut(L).Value.(*lFile), 1)
   771  }
   772  
   773  //