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