1 module upromised.process;
2 import core.stdc.signal : SIGINT;
3 import deimos.libuv.uv : uv_loop_t, uv_process_t;
4 import upromised.loop : Loop;
5 import upromised.memory : gcrelease, gcretain, getSelf;
6 import upromised.stream : Stream;
7 import upromised.pipe : Pipe;
8 import upromised.promise : DelegatePromise, Promise, PromiseIterator;
9 import upromised.uv : handle, stream, uvCheck;
10 
11 shared static this() {
12 	import core.stdc.signal : signal, SIG_IGN;
13 	import core.sys.posix.signal : SIGPIPE;
14 	signal(SIGPIPE, SIG_IGN);
15 }
16 
17 class Process {
18 private:
19 	DelegatePromise!long wait_;
20 
21 public:
22 	__gshared Pipe STDIN;
23 	__gshared Pipe STDOUT;
24 	__gshared Pipe STDERR;
25 	shared static this() {
26 		import std.stdio : stderr, stdin, stdout;
27 		STDIN = cast(Pipe)cast(void*)&stdin;
28 		STDOUT = cast(Pipe)cast(void*)&stdout;
29 		STDERR = cast(Pipe)cast(void*)&stderr;
30 	}
31 
32 	uv_process_t self;
33 	this(Loop loop, string[] argsD, Pipe stdin = null, Pipe stdout = null, Pipe stderr = null) {
34 		this(cast(uv_loop_t*)loop.inner(), argsD, stdin, stdout, stderr);
35 	}
36 
37 	this(uv_loop_t* loop, string[] argsD, Pipe stdin = null, Pipe stdout = null, Pipe stderr = null) {
38 		import deimos.libuv.uv : uv_process_options_t, uv_close, uv_spawn, uv_stdio_container_t, uv_stdio_flags;
39 		import std.algorithm : map;
40 		import std.array : array;
41 		import std.stdio : File;
42 		import std.string : toStringz;
43 
44 		wait_ = new DelegatePromise!long;
45 		uv_process_options_t options;
46 		options.exit_cb = (self, exit_status, term_signal) nothrow {
47 			self.getSelf!Process.wait_.resolve(exit_status);
48 		};
49 
50 		uv_stdio_container_t[3] io;
51 		foreach(i, pipe; [stdin, stdout, stderr]) {
52 			if (pipe is STDIN || pipe is STDOUT || pipe is STDERR) {
53 				io[i].flags = uv_stdio_flags.UV_INHERIT_FD;
54 				io[i].data.fd = (cast(File*)cast(void*)pipe).fileno;
55 			} else if (pipe !is null) {
56 				io[i].flags = uv_stdio_flags.UV_CREATE_PIPE;
57 				if (i == 0) {
58 					io[i].flags |= uv_stdio_flags.UV_WRITABLE_PIPE;
59 				} else {
60 					io[i].flags |= uv_stdio_flags.UV_READABLE_PIPE;
61 				}
62 				io[i].data.stream = pipe.self.stream;
63 			} else {
64 				io[i].flags = uv_stdio_flags.UV_IGNORE;
65 			}
66 		}
67 
68 		auto args = argsD.map!(x => x.toStringz).array;
69 		args ~= null;
70 		options.file = args[0];
71 		options.args = cast(char**)args.ptr;
72 		options.stdio_count = 3;
73 		options.stdio = io.ptr;
74 		uv_spawn(loop, &self, &options).uvCheck();
75 		gcretain(this);
76 		wait_.finall(() {
77 			uv_close(self.handle, (selfSelf) nothrow {
78 				gcrelease(selfSelf.getSelf!Process);
79 			});
80 		});
81 	}
82 
83 	Promise!long wait() nothrow {
84 		return wait_;
85 	}
86 
87 	void kill(int signal = SIGINT) {
88 		import deimos.libuv.uv : uv_process_kill;
89 		
90 		uv_process_kill(&self, signal).uvCheck();
91 	}
92 
93 	static Stream stream(Loop loop, string[] args, Pipe stderr = Process.STDERR) {
94 		return new class Stream {
95 			Pipe stdin;
96 			Pipe stdout;
97 			Process process;
98 
99 			this() {
100 				stdin = new Pipe(loop);
101 				stdout = new Pipe(loop);
102 				process = new Process(loop, args, stdin, stdout, stderr);
103 			}
104 
105 			Promise!void shutdown() nothrow {
106 				return stdin.shutdown();
107 			}
108 
109 			Promise!void close() nothrow {
110 				return Promise!void.resolved().
111 				then(() => process.kill())
112 				.finall(() => stdin.close())
113 				.then(() => process.wait())
114 				.finall(() => stdout.close())
115 				.then((_) {});
116 			}
117 
118 			PromiseIterator!(const(ubyte)[]) read() nothrow {
119 				return stdout.read();
120 			}
121 
122 			Promise!void write(immutable(ubyte)[] data) nothrow {
123 				return stdin.write(data);
124 			}
125 		};
126 	}
127 }