Process.hx 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. /*
  2. * Copyright (C)2005-2019 Haxe Foundation
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a
  5. * copy of this software and associated documentation files (the "Software"),
  6. * to deal in the Software without restriction, including without limitation
  7. * the rights to use, copy, modify, merge, publish, distribute, sublicense,
  8. * and/or sell copies of the Software, and to permit persons to whom the
  9. * Software is furnished to do so, subject to the following conditions:
  10. *
  11. * The above copyright notice and this permission notice shall be included in
  12. * all copies or substantial portions of the Software.
  13. *
  14. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  20. * DEALINGS IN THE SOFTWARE.
  21. */
  22. package sys.io;
  23. import php.*;
  24. import haxe.io.*;
  25. import haxe.SysTools;
  26. using StringTools;
  27. using php.Global;
  28. @:forward(iterator)
  29. private abstract ProcessPipes(NativeIndexedArray<Resource>) from NativeIndexedArray<Resource> to NativeIndexedArray<Resource> {
  30. public var stdin(get, never):Resource;
  31. public var stdout(get, never):Resource;
  32. public var stderr(get, never):Resource;
  33. inline function get_stdin()
  34. return this[0];
  35. inline function get_stdout()
  36. return this[1];
  37. inline function get_stderr()
  38. return this[2];
  39. }
  40. private class ReadablePipe extends Input {
  41. var pipe:Resource;
  42. var tmpBytes:Bytes;
  43. public function new(pipe:Resource) {
  44. this.pipe = pipe;
  45. tmpBytes = Bytes.alloc(1);
  46. }
  47. override public function close():Void {
  48. pipe.fclose();
  49. }
  50. override public function readByte():Int {
  51. if (readBytes(tmpBytes, 0, 1) == 0)
  52. throw Error.Blocked;
  53. return tmpBytes.get(0);
  54. }
  55. override public function readBytes(s:Bytes, pos:Int, len:Int):Int {
  56. if (pipe.feof())
  57. throw new Eof();
  58. var result = pipe.fread(len);
  59. if (result == "")
  60. throw new Eof();
  61. if (result == false)
  62. return throw Error.Custom('Failed to read process output');
  63. var result:String = result;
  64. var bytes = Bytes.ofString(result);
  65. s.blit(pos, bytes, 0, result.strlen());
  66. return result.strlen();
  67. }
  68. }
  69. private class WritablePipe extends Output {
  70. var pipe:Resource;
  71. var tmpBytes:Bytes;
  72. public function new(pipe:Resource) {
  73. this.pipe = pipe;
  74. tmpBytes = Bytes.alloc(1);
  75. }
  76. override public function close():Void {
  77. pipe.fclose();
  78. }
  79. override public function writeByte(c:Int):Void {
  80. tmpBytes.set(0, c);
  81. writeBytes(tmpBytes, 0, 1);
  82. }
  83. override public function writeBytes(b:Bytes, pos:Int, l:Int):Int {
  84. var s = b.getString(pos, l);
  85. if (pipe.feof())
  86. throw new Eof();
  87. var result = Global.fwrite(pipe, s, l);
  88. if (result == false)
  89. throw Error.Custom('Failed to write to process input');
  90. return result;
  91. }
  92. }
  93. class Process {
  94. public var stdout(default, null):Input;
  95. public var stderr(default, null):Input;
  96. public var stdin(default, null):Output;
  97. var process:Resource;
  98. var pipes:ProcessPipes;
  99. var pid:Int = -1;
  100. var running:Bool = true;
  101. var _exitCode:Int = -1;
  102. public function new(cmd:String, ?args:Array<String>, ?detached:Bool):Void {
  103. if (detached)
  104. throw "Detached process is not supported on this platform";
  105. var descriptors = Syntax.arrayDecl(Syntax.arrayDecl('pipe', 'r'), Syntax.arrayDecl('pipe', 'w'), Syntax.arrayDecl('pipe', 'w'));
  106. var result = buildCmd(cmd, args).proc_open(descriptors, pipes);
  107. if (result == false)
  108. throw Error.Custom('Failed to start process: $cmd');
  109. process = result;
  110. updateStatus();
  111. stdin = new WritablePipe(pipes.stdin);
  112. stdout = new ReadablePipe(pipes.stdout);
  113. stderr = new ReadablePipe(pipes.stderr);
  114. }
  115. public function getPid():Int {
  116. return pid;
  117. }
  118. public function exitCode(block:Bool = true):Null<Int> {
  119. if (!block) {
  120. updateStatus();
  121. return (running ? null : _exitCode);
  122. }
  123. while (running) {
  124. var arr = Syntax.arrayDecl(process);
  125. try {
  126. Syntax.suppress(Global.stream_select(arr, arr, arr, null));
  127. } catch(_) {}
  128. updateStatus();
  129. }
  130. return _exitCode;
  131. }
  132. public function close():Void {
  133. if (!running)
  134. return;
  135. for (pipe in pipes)
  136. Global.fclose(pipe);
  137. process.proc_close();
  138. }
  139. public function kill():Void {
  140. process.proc_terminate();
  141. }
  142. function buildCmd(cmd:String, ?args:Array<String>):String {
  143. if (args == null)
  144. return cmd;
  145. return switch (Sys.systemName()) {
  146. case "Windows":
  147. [cmd.replace("/", "\\")].concat(args).map(SysTools.quoteWinArg.bind(_, true)).join(" ");
  148. case _:
  149. [cmd].concat(args).map(SysTools.quoteUnixArg).join(" ");
  150. }
  151. }
  152. function updateStatus():Void {
  153. if (!running)
  154. return;
  155. var status = process.proc_get_status();
  156. if (status == false)
  157. throw Error.Custom('Failed to obtain process status');
  158. var status:NativeAssocArray<Scalar> = status;
  159. pid = status['pid'];
  160. running = status['running'];
  161. _exitCode = status['exitcode'];
  162. }
  163. }