1#include "proc_open.h"
2
3#include <stdlib.h>
4#include <stdio.h>
5#include <ctype.h>
6#include <string.h>
7#include <errno.h>
8
9#ifdef WIN32
10# include <io.h>
11# include <fcntl.h>
12#else
13# include <sys/wait.h>
14# include <unistd.h>
15#endif
16
17
18#ifdef WIN32
19/* {{{ win32 stuff */
20# define SHELLENV "ComSpec"
21# define SECURITY_DC , SECURITY_ATTRIBUTES *security
22# define SECURITY_CC , security
23# define pipe(pair) (CreatePipe(&pair[0], &pair[1], security, 2048L) ? 0 : -1)
24static inline HANDLE dup_handle(HANDLE src, BOOL inherit, BOOL closeorig)
25{
26	HANDLE copy, self = GetCurrentProcess();
27
28	if (!DuplicateHandle(self, src, self, &copy, 0, inherit, DUPLICATE_SAME_ACCESS |
29				(closeorig ? DUPLICATE_CLOSE_SOURCE : 0)))
30		return NULL;
31	return copy;
32}
33# define close_descriptor(fd) CloseHandle(fd)
34static void pipe_close_parent(pipe_t *p) {
35	/* don't let the child inherit the parent side of the pipe */
36	p->parent = dup_handle(p->parent, FALSE, TRUE);
37}
38static void pipe_close_child(pipe_t *p) {
39	close_descriptor(p->child);
40	p->fd = _open_osfhandle((long)p->parent,
41			(p->fd == 0 ? O_RDONLY : O_WRONLY)|O_BINARY);
42}
43/* }}} */
44#else /* WIN32 */
45/* {{{ unix way */
46# define SHELLENV "SHELL"
47# define SECURITY_DC
48# define SECURITY_CC
49# define close_descriptor(fd) close(fd)
50static void pipe_close_parent(pipe_t *p) {
51	/* don't close stdin */
52	close_descriptor(p->parent);
53	if (dup2(p->child, p->fd) != p->fd) {
54		perror("pipe_child dup2");
55	} else {
56		close_descriptor(p->child);
57		p->child = p->fd;
58	}
59}
60static void pipe_close_child(pipe_t *p) {
61	close_descriptor(p->child);
62	p->fd = p->parent;
63}
64/* }}} */
65#endif /* WIN32 */
66
67/* {{{ pipe_close */
68static void pipe_close(pipe_t *p) {
69	close_descriptor(p->parent);
70	close_descriptor(p->child);
71#ifdef WIN32
72	close(p->fd);
73#endif
74}
75/* }}} */
76/* {{{ pipe_open */
77static int pipe_open(pipe_t *p, int fd SECURITY_DC) {
78	descriptor_t newpipe[2];
79
80	if (0 != pipe(newpipe)) {
81		fprintf(stderr, "can't open pipe");
82		return -1;
83	}
84	if (0 == fd) {
85		p->parent = newpipe[1]; /* write */
86		p->child  = newpipe[0]; /* read */
87	} else {
88		p->parent = newpipe[0]; /* read */
89		p->child  = newpipe[1]; /* write */
90	}
91	p->fd = fd;
92
93	return 0;
94}
95/* }}} */
96
97/* {{{ proc_open_pipes */
98static int proc_open_pipes(proc_handler_t *proc SECURITY_DC) {
99	if (pipe_open(&(proc->in), 0 SECURITY_CC) != 0) {
100		return -1;
101	}
102	if (pipe_open(&(proc->out), 1 SECURITY_CC) != 0) {
103		return -1;
104	}
105	if (pipe_open(&(proc->err), 2 SECURITY_CC) != 0) {
106		return -1;
107	}
108	return 0;
109}
110/* }}} */
111/* {{{ proc_close_pipes */
112static void proc_close_pipes(proc_handler_t *proc) {
113	pipe_close(&proc->in);
114	pipe_close(&proc->out);
115	pipe_close(&proc->err);
116}
117/* }}} */
118/* {{{ proc_close_parents */
119static void proc_close_parents(proc_handler_t *proc) {
120	pipe_close_parent(&proc->in);
121	pipe_close_parent(&proc->out);
122	pipe_close_parent(&proc->err);
123}
124/* }}} */
125/* {{{ proc_close_childs */
126static void proc_close_childs(proc_handler_t *proc) {
127	pipe_close_child(&proc->in);
128	pipe_close_child(&proc->out);
129	pipe_close_child(&proc->err);
130}
131/* }}} */
132
133#ifdef WIN32
134/* {{{ proc_close */
135int proc_close(proc_handler_t *proc) {
136	proc_pid_t child = proc->child;
137	DWORD wstatus;
138
139	proc_close_pipes(proc);
140	WaitForSingleObject(child, INFINITE);
141	GetExitCodeProcess(child, &wstatus);
142	CloseHandle(child);
143
144	return wstatus;
145}
146/* }}} */
147/* {{{ proc_open */
148int proc_open(proc_handler_t *proc, const char *command) {
149	PROCESS_INFORMATION pi;
150	STARTUPINFO si;
151	BOOL procok;
152	SECURITY_ATTRIBUTES security;
153	const char *shell = NULL;
154	const char *windir = NULL;
155	buffer *cmdline;
156
157	if (NULL == (shell = getenv(SHELLENV)) &&
158			NULL == (windir = getenv("SystemRoot")) &&
159			NULL == (windir = getenv("windir"))) {
160		fprintf(stderr, "One of %s,%%SystemRoot,%%windir is required", SHELLENV);
161		return -1;
162	}
163
164	/* we use this to allow the child to inherit handles */
165	memset(&security, 0, sizeof(security));
166	security.nLength = sizeof(security);
167	security.bInheritHandle = TRUE;
168	security.lpSecurityDescriptor = NULL;
169
170	if (proc_open_pipes(proc, &security) != 0) {
171		return -1;
172	}
173	proc_close_parents(proc);
174
175	memset(&si, 0, sizeof(si));
176	si.cb = sizeof(si);
177	si.dwFlags = STARTF_USESTDHANDLES;
178	si.hStdInput = proc->in.child;
179	si.hStdOutput = proc->out.child;
180	si.hStdError = proc->err.child;
181
182	memset(&pi, 0, sizeof(pi));
183
184	cmdline = buffer_init();
185	if (shell) {
186		buffer_append_string(cmdline, shell);
187	} else {
188		buffer_append_string(cmdline, windir);
189		buffer_append_string_len(cmdline, CONST_STR_LEN("\\system32\\cmd.exe"));
190	}
191	buffer_append_string_len(cmdline, CONST_STR_LEN(" /c "));
192	buffer_append_string(cmdline, command);
193	procok = CreateProcess(NULL, cmdline->ptr, &security, &security, TRUE,
194			NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
195
196	if (FALSE == procok) {
197		fprintf(stderr, "failed to CreateProcess: %s", cmdline->ptr);
198		buffer_free(cmdline);
199		return -1;
200	}
201	buffer_free(cmdline);
202
203	proc->child = pi.hProcess;
204	CloseHandle(pi.hThread);
205
206	proc_close_childs(proc);
207
208	return 0;
209}
210/* }}} */
211#else /* WIN32 */
212/* {{{ proc_close */
213int proc_close(proc_handler_t *proc) {
214	pid_t child = proc->child;
215	int wstatus;
216	pid_t wait_pid;
217
218	proc_close_pipes(proc);
219
220	do {
221		wait_pid = waitpid(child, &wstatus, 0);
222	} while (wait_pid == -1 && errno == EINTR);
223
224	if (wait_pid == -1) {
225		return -1;
226	} else {
227		if (WIFEXITED(wstatus))
228			wstatus = WEXITSTATUS(wstatus);
229	}
230
231	return wstatus;
232}
233/* }}} */
234/* {{{ proc_open */
235int proc_open(proc_handler_t *proc, const char *command) {
236	pid_t child;
237	const char *shell;
238
239	if (NULL == (shell = getenv(SHELLENV))) {
240		shell = "/bin/sh";
241	}
242
243	if (proc_open_pipes(proc) != 0) {
244		return -1;
245	}
246
247	/* the unix way */
248
249	child = fork();
250
251	if (child == 0) {
252		/* this is the child process */
253
254		/* close those descriptors that we just opened for the parent stuff,
255		 * dup new descriptors into required descriptors and close the original
256		 * cruft
257		 */
258		proc_close_parents(proc);
259
260		execl(shell, shell, "-c", command, (char *)NULL);
261		fprintf(stderr, "failed to execute shell: %s -c %s: %s\n", shell, command, strerror(errno));
262		_exit(127);
263
264	} else if (child < 0) {
265		fprintf(stderr, "failed to forking");
266		proc_close(proc);
267		return -1;
268
269	} else {
270		proc->child = child;
271		proc_close_childs(proc);
272		return 0;
273	}
274}
275/* }}} */
276#endif /* WIN32 */
277
278/* {{{ proc_read_fd_to_buffer */
279static void proc_read_fd_to_buffer(int fd, buffer *b) {
280	ssize_t s;
281
282	for (;;) {
283		buffer_string_prepare_append(b, 1024);
284		if ((s = read(fd, (void *)(b->ptr + buffer_string_length(b)), buffer_string_space(b))) <= 0) {
285			break;
286		}
287		buffer_commit(b, s);
288	}
289}
290/* }}} */
291/* {{{ proc_open_buffer */
292int proc_open_buffer(const char *command, buffer *in, buffer *out, buffer *err) {
293	proc_handler_t proc;
294
295	if (proc_open(&proc, command) != 0) {
296		return -1;
297	}
298
299	if (in) {
300		if (write(proc.in.fd, CONST_BUF_LEN(in)) < 0) {
301			perror("error writing pipe");
302			return -1;
303		}
304	}
305	pipe_close(&proc.in);
306
307	if (out) {
308		proc_read_fd_to_buffer(proc.out.fd, out);
309	}
310	pipe_close(&proc.out);
311
312	if (err) {
313		proc_read_fd_to_buffer(proc.err.fd, err);
314	} else {
315		buffer *tmp = buffer_init();
316		proc_read_fd_to_buffer(proc.err.fd, tmp);
317		if (!buffer_string_is_empty(tmp) &&  write(2, CONST_BUF_LEN(tmp)) < 0) {
318			perror("error writing pipe");
319			buffer_free(tmp);
320			return -1;
321		}
322		buffer_free(tmp);
323	}
324	pipe_close(&proc.err);
325
326	proc_close(&proc);
327
328	return 0;
329}
330/* }}} */
331
332/* {{{ test */
333#ifdef DEBUG_PROC_OPEN
334int main(void) {
335	proc_handler_t proc;
336	buffer *in = buffer_init(), *out = buffer_init(), *err = buffer_init();
337	int wstatus;
338
339#define FREE() do { \
340	buffer_free(in); \
341	buffer_free(out); \
342	buffer_free(err); \
343} while (0)
344
345#define RESET() do { \
346	buffer_reset(in); \
347	buffer_reset(out); \
348	buffer_reset(err); \
349	wstatus = proc_close(&proc); \
350	if (0&&wstatus != 0) { \
351		fprintf(stdout, "exitstatus %d\n", wstatus); \
352		return __LINE__ - 200; \
353	} \
354} while (0)
355
356#define ERROR_OUT() do { \
357	fprintf(stdout, "failed opening proc\n"); \
358	wstatus = proc_close(&proc); \
359	fprintf(stdout, "exitstatus %d\n", wstatus); \
360	FREE(); \
361	return __LINE__ - 300; \
362} while (0)
363
364#ifdef WIN32
365#define CMD_CAT "pause"
366#else
367#define CMD_CAT "cat"
368#endif
369
370	do {
371		fprintf(stdout, "test: echo 123 without read\n");
372		if (proc_open(&proc, "echo 321") != 0) {
373			ERROR_OUT();
374		}
375		close_descriptor(proc.in.parent);
376		close_descriptor(proc.out.parent);
377		close_descriptor(proc.err.parent);
378		RESET();
379
380		fprintf(stdout, "test: echo 321 with read\n"); fflush(stdout);
381		if (proc_open_buffer("echo 321", NULL, out, err) != 0) {
382			ERROR_OUT();
383		}
384		fprintf(stdout, "result: ->%s<-\n\n", out->ptr); fflush(stdout);
385		RESET();
386
387		fprintf(stdout, "test: echo 123 | " CMD_CAT "\n"); fflush(stdout);
388		buffer_copy_string_len(in, CONST_STR_LEN("123\n"));
389		if (proc_open_buffer(CMD_CAT, in, out, err) != 0) {
390			ERROR_OUT();
391		}
392		fprintf(stdout, "result: ->%s<-\n\n", out->ptr); fflush(stdout);
393		RESET();
394	} while (0);
395
396#undef RESET
397#undef ERROR_OUT
398
399	fprintf(stdout, "ok\n");
400
401	FREE();
402	return 0;
403}
404#endif /* DEBUG_PROC_OPEN */
405/* }}} */
406
407