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