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