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