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 //