github.com/qiniu/gopher-lua@v0.2017.11/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 closed bool 36 } 37 38 type lFileType int 39 40 const ( 41 lFileFile lFileType = iota 42 lFileProcess 43 ) 44 45 const fileDefOutIndex = 1 46 const fileDefInIndex = 2 47 const fileDefaultWriteBuffer = 4096 48 const fileDefaultReadBuffer = 4096 49 50 func checkFile(L *LState) *lFile { 51 ud := L.CheckUserData(1) 52 if file, ok := ud.Value.(*lFile); ok { 53 return file 54 } 55 L.ArgError(1, "file expected") 56 return nil 57 } 58 59 func errorIfFileIsClosed(L *LState, file *lFile) { 60 if file.closed { 61 L.ArgError(1, "file is closed") 62 } 63 } 64 65 func newFile(L *LState, file *os.File, path string, flag int, perm os.FileMode, writable, readable bool) (*LUserData, error) { 66 ud := L.NewUserData() 67 var err error 68 if file == nil { 69 file, err = os.OpenFile(path, flag, perm) 70 if err != nil { 71 return nil, err 72 } 73 } 74 lfile := &lFile{fp: file, pp: nil, writer: nil, reader: nil, closed: false} 75 ud.Value = lfile 76 if writable { 77 lfile.writer = file 78 } 79 if readable { 80 lfile.reader = bufio.NewReaderSize(file, fileDefaultReadBuffer) 81 } 82 L.SetMetatable(ud, L.GetTypeMetatable(lFileClass)) 83 return ud, nil 84 } 85 86 func newProcess(L *LState, cmd string, writable, readable bool) (*LUserData, error) { 87 ud := L.NewUserData() 88 c, args := popenArgs(cmd) 89 pp := exec.Command(c, args...) 90 lfile := &lFile{fp: nil, pp: pp, writer: nil, reader: nil, closed: false} 91 ud.Value = lfile 92 93 var err error 94 if writable { 95 lfile.writer, err = pp.StdinPipe() 96 } 97 if readable { 98 var reader io.Reader 99 reader, err = pp.StdoutPipe() 100 lfile.reader = bufio.NewReaderSize(reader, 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 err = file.pp.Wait() 282 var exitStatus int // Initialised to zero value = 0 283 if err != nil { 284 if e2, ok := err.(*exec.ExitError); ok { 285 if s, ok := e2.Sys().(syscall.WaitStatus); ok { 286 exitStatus = s.ExitStatus() 287 } else { 288 err = errors.New("Unimplemented for system where exec.ExitError.Sys() is not syscall.WaitStatus.") 289 } 290 } 291 } else { 292 exitStatus = 0 293 } 294 L.Push(LNumber(exitStatus)) 295 return 1 296 } 297 298 errreturn: 299 L.RaiseError(err.Error()) 300 return 0 301 } 302 303 func fileFlushAux(L *LState, file *lFile) int { 304 if n := fileIsWritable(L, file); n != 0 { 305 return n 306 } 307 errorIfFileIsClosed(L, file) 308 309 if bwriter, ok := file.writer.(*bufio.Writer); ok { 310 if err := bwriter.Flush(); err != nil { 311 L.Push(LNil) 312 L.Push(LString(err.Error())) 313 return 2 314 } 315 } 316 L.Push(LTrue) 317 return 1 318 } 319 320 func fileReadAux(L *LState, file *lFile, idx int) int { 321 if n := fileIsReadable(L, file); n != 0 { 322 return n 323 } 324 errorIfFileIsClosed(L, file) 325 if L.GetTop() == idx-1 { 326 L.Push(LString("*l")) 327 } 328 var err error 329 top := L.GetTop() 330 for i := idx; i <= top; i++ { 331 switch lv := L.Get(i).(type) { 332 case LNumber: 333 size := int64(lv) 334 if size == 0 { 335 _, err = file.reader.ReadByte() 336 if err == io.EOF { 337 L.Push(LNil) 338 goto normalreturn 339 } 340 file.reader.UnreadByte() 341 } 342 var buf []byte 343 var iseof bool 344 buf, err, iseof = readBufioSize(file.reader, size) 345 if iseof { 346 L.Push(LNil) 347 goto normalreturn 348 } 349 if err != nil { 350 goto errreturn 351 } 352 L.Push(LString(string(buf))) 353 case LString: 354 options := L.CheckString(i) 355 if len(options) > 0 && options[0] != '*' { 356 L.ArgError(2, "invalid options:"+options) 357 } 358 for _, opt := range options[1:] { 359 switch opt { 360 case 'n': 361 var v LNumber 362 _, err = fmt.Fscanf(file.reader, LNumberScanFormat, &v) 363 if err == io.EOF { 364 L.Push(LNil) 365 goto normalreturn 366 } 367 if err != nil { 368 goto errreturn 369 } 370 L.Push(v) 371 case 'a': 372 var buf []byte 373 buf, err = ioutil.ReadAll(file.reader) 374 if err == io.EOF { 375 L.Push(LString("")) 376 goto normalreturn 377 } 378 if err != nil { 379 goto errreturn 380 } 381 L.Push(LString(string(buf))) 382 case 'l': 383 var buf []byte 384 var iseof bool 385 buf, err, iseof = readBufioLine(file.reader) 386 if iseof { 387 L.Push(LNil) 388 goto normalreturn 389 } 390 if err != nil { 391 goto errreturn 392 } 393 L.Push(LString(string(buf))) 394 default: 395 L.ArgError(2, "invalid options:"+string(opt)) 396 } 397 } 398 } 399 } 400 normalreturn: 401 return L.GetTop() - top 402 403 errreturn: 404 L.RaiseError(err.Error()) 405 //L.Push(LNil) 406 //L.Push(LString(err.Error())) 407 return 2 408 } 409 410 var fileSeekOptions = []string{"set", "cur", "end"} 411 412 func fileSeek(L *LState) int { 413 file := checkFile(L) 414 if file.Type() != lFileFile { 415 L.Push(LNil) 416 L.Push(LString("can not seek a process.")) 417 return 2 418 } 419 420 top := L.GetTop() 421 if top == 1 { 422 L.Push(LString("cur")) 423 L.Push(LNumber(0)) 424 } else if top == 2 { 425 L.Push(LNumber(0)) 426 } 427 428 var pos int64 429 var err error 430 431 err = file.AbandonReadBuffer() 432 if err != nil { 433 goto errreturn 434 } 435 436 pos, err = file.fp.Seek(L.CheckInt64(3), L.CheckOption(2, fileSeekOptions)) 437 if err != nil { 438 goto errreturn 439 } 440 441 L.Push(LNumber(pos)) 442 return 1 443 444 errreturn: 445 L.Push(LNil) 446 L.Push(LString(err.Error())) 447 return 2 448 } 449 450 func fileWrite(L *LState) int { 451 return fileWriteAux(L, checkFile(L), 2) 452 } 453 454 func fileClose(L *LState) int { 455 return fileCloseAux(L, checkFile(L)) 456 } 457 458 func fileFlush(L *LState) int { 459 return fileFlushAux(L, checkFile(L)) 460 } 461 462 func fileLinesIter(L *LState) int { 463 var file *lFile 464 if ud, ok := L.Get(1).(*LUserData); ok { 465 file = ud.Value.(*lFile) 466 } else { 467 file = L.Get(UpvalueIndex(2)).(*LUserData).Value.(*lFile) 468 } 469 buf, _, err := file.reader.ReadLine() 470 if err != nil { 471 if err == io.EOF { 472 L.Push(LNil) 473 return 1 474 } 475 L.RaiseError(err.Error()) 476 } 477 L.Push(LString(string(buf))) 478 return 1 479 } 480 481 func fileLines(L *LState) int { 482 file := checkFile(L) 483 ud := L.CheckUserData(1) 484 if n := fileIsReadable(L, file); n != 0 { 485 return 0 486 } 487 L.Push(L.NewClosure(fileLinesIter, L.Get(UpvalueIndex(1)), ud)) 488 return 1 489 } 490 491 func fileRead(L *LState) int { 492 return fileReadAux(L, checkFile(L), 2) 493 } 494 495 var filebufOptions = []string{"no", "full"} 496 497 func fileSetVBuf(L *LState) int { 498 var err error 499 var writer io.Writer 500 file := checkFile(L) 501 if n := fileIsWritable(L, file); n != 0 { 502 return n 503 } 504 switch filebufOptions[L.CheckOption(2, filebufOptions)] { 505 case "no": 506 switch file.Type() { 507 case lFileFile: 508 file.writer = file.fp 509 case lFileProcess: 510 file.writer, err = file.pp.StdinPipe() 511 if err != nil { 512 goto errreturn 513 } 514 } 515 case "full", "line": // TODO line buffer not supported 516 bufsize := L.OptInt(3, fileDefaultWriteBuffer) 517 switch file.Type() { 518 case lFileFile: 519 file.writer = bufio.NewWriterSize(file.fp, bufsize) 520 case lFileProcess: 521 writer, err = file.pp.StdinPipe() 522 if err != nil { 523 goto errreturn 524 } 525 file.writer = bufio.NewWriterSize(writer, bufsize) 526 } 527 } 528 L.Push(LTrue) 529 return 1 530 errreturn: 531 L.Push(LNil) 532 L.Push(LString(err.Error())) 533 return 2 534 } 535 536 func ioInput(L *LState) int { 537 if L.GetTop() == 0 { 538 L.Push(fileDefIn(L)) 539 return 1 540 } 541 switch lv := L.Get(1).(type) { 542 case LString: 543 file, err := newFile(L, nil, string(lv), os.O_RDONLY, 0600, false, true) 544 if err != nil { 545 L.RaiseError(err.Error()) 546 } 547 L.Get(UpvalueIndex(1)).(*LTable).RawSetInt(fileDefInIndex, file) 548 L.Push(file) 549 return 1 550 case *LUserData: 551 if _, ok := lv.Value.(*lFile); ok { 552 L.Get(UpvalueIndex(1)).(*LTable).RawSetInt(fileDefInIndex, lv) 553 L.Push(lv) 554 return 1 555 } 556 557 } 558 L.ArgError(1, "string or file expedted, but got "+L.Get(1).Type().String()) 559 return 0 560 } 561 562 func ioClose(L *LState) int { 563 if L.GetTop() == 0 { 564 return fileCloseAux(L, fileDefOut(L).Value.(*lFile)) 565 } 566 return fileClose(L) 567 } 568 569 func ioFlush(L *LState) int { 570 return fileFlushAux(L, fileDefOut(L).Value.(*lFile)) 571 } 572 573 func ioLinesIter(L *LState) int { 574 var file *lFile 575 toclose := false 576 if ud, ok := L.Get(1).(*LUserData); ok { 577 file = ud.Value.(*lFile) 578 } else { 579 file = L.Get(UpvalueIndex(2)).(*LUserData).Value.(*lFile) 580 toclose = true 581 } 582 buf, _, err := file.reader.ReadLine() 583 if err != nil { 584 if err == io.EOF { 585 if toclose { 586 fileCloseAux(L, file) 587 } 588 L.Push(LNil) 589 return 1 590 } 591 L.RaiseError(err.Error()) 592 } 593 L.Push(LString(string(buf))) 594 return 1 595 } 596 597 func ioLines(L *LState) int { 598 if L.GetTop() == 0 { 599 L.Push(L.Get(UpvalueIndex(2))) 600 L.Push(fileDefIn(L)) 601 return 2 602 } 603 604 path := L.CheckString(1) 605 ud, err := newFile(L, nil, path, os.O_RDONLY, os.FileMode(0600), false, true) 606 if err != nil { 607 return 0 608 } 609 L.Push(L.NewClosure(ioLinesIter, L.Get(UpvalueIndex(1)), ud)) 610 return 1 611 } 612 613 var ioOpenOpions = []string{"r", "rb", "w", "wb", "a", "ab", "r+", "rb+", "w+", "wb+", "a+", "ab+"} 614 615 func ioOpenFile(L *LState) int { 616 path := L.CheckString(1) 617 if L.GetTop() == 1 { 618 L.Push(LString("r")) 619 } 620 mode := os.O_RDONLY 621 perm := 0600 622 writable := true 623 readable := true 624 switch ioOpenOpions[L.CheckOption(2, ioOpenOpions)] { 625 case "r", "rb": 626 mode = os.O_RDONLY 627 writable = false 628 case "w", "wb": 629 mode = os.O_WRONLY | os.O_CREATE 630 readable = false 631 case "a", "ab": 632 mode = os.O_WRONLY | os.O_APPEND | os.O_CREATE 633 case "r+", "rb+": 634 mode = os.O_RDWR 635 case "w+", "wb+": 636 mode = os.O_RDWR | os.O_TRUNC | os.O_CREATE 637 case "a+", "ab+": 638 mode = os.O_APPEND | os.O_RDWR | os.O_CREATE 639 } 640 file, err := newFile(L, nil, path, mode, os.FileMode(perm), writable, readable) 641 if err != nil { 642 L.Push(LNil) 643 L.Push(LString(err.Error())) 644 L.Push(LNumber(1)) // C-Lua compatibility: Original Lua pushes errno to the stack 645 return 3 646 } 647 L.Push(file) 648 return 1 649 650 } 651 652 var ioPopenOptions = []string{"r", "w"} 653 654 func ioPopen(L *LState) int { 655 cmd := L.CheckString(1) 656 if L.GetTop() == 1 { 657 L.Push(LString("r")) 658 } 659 var file *LUserData 660 var err error 661 662 switch ioPopenOptions[L.CheckOption(2, ioPopenOptions)] { 663 case "r": 664 file, err = newProcess(L, cmd, false, true) 665 case "w": 666 file, err = newProcess(L, cmd, true, false) 667 } 668 if err != nil { 669 L.Push(LNil) 670 L.Push(LString(err.Error())) 671 return 2 672 } 673 L.Push(file) 674 return 1 675 } 676 677 func ioRead(L *LState) int { 678 return fileReadAux(L, fileDefIn(L).Value.(*lFile), 1) 679 } 680 681 func ioType(L *LState) int { 682 ud, udok := L.Get(1).(*LUserData) 683 if !udok { 684 L.Push(LNil) 685 return 1 686 } 687 file, ok := ud.Value.(*lFile) 688 if !ok { 689 L.Push(LNil) 690 return 1 691 } 692 if file.closed { 693 L.Push(LString("closed file")) 694 return 1 695 } 696 L.Push(LString("file")) 697 return 1 698 } 699 700 func ioTmpFile(L *LState) int { 701 file, err := ioutil.TempFile("", "") 702 if err != nil { 703 L.Push(LNil) 704 L.Push(LString(err.Error())) 705 return 2 706 } 707 L.G.tempFiles = append(L.G.tempFiles, file) 708 ud, _ := newFile(L, file, "", 0, os.FileMode(0), true, true) 709 L.Push(ud) 710 return 1 711 } 712 713 func ioOutput(L *LState) int { 714 if L.GetTop() == 0 { 715 L.Push(fileDefOut(L)) 716 return 1 717 } 718 switch lv := L.Get(1).(type) { 719 case LString: 720 file, err := newFile(L, nil, string(lv), os.O_WRONLY|os.O_CREATE, 0600, true, false) 721 if err != nil { 722 L.RaiseError(err.Error()) 723 } 724 L.Get(UpvalueIndex(1)).(*LTable).RawSetInt(fileDefOutIndex, file) 725 L.Push(file) 726 return 1 727 case *LUserData: 728 if _, ok := lv.Value.(*lFile); ok { 729 L.Get(UpvalueIndex(1)).(*LTable).RawSetInt(fileDefOutIndex, lv) 730 L.Push(lv) 731 return 1 732 } 733 734 } 735 L.ArgError(1, "string or file expedted, but got "+L.Get(1).Type().String()) 736 return 0 737 } 738 739 func ioWrite(L *LState) int { 740 return fileWriteAux(L, fileDefOut(L).Value.(*lFile), 1) 741 } 742 743 //