1/*
2 * Copyright 2004-2010, J��r��me Duval. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 * Original code from ZipOMatic by jonas.sundstrom@kirilla.com
5 */
6#include "ExpanderThread.h"
7
8
9#include <errno.h>
10#include <image.h>
11#include <signal.h>
12#include <termios.h>
13#include <unistd.h>
14
15#include <Messenger.h>
16#include <Path.h>
17
18
19const char* ExpanderThreadName = "ExpanderThread";
20
21
22ExpanderThread::ExpanderThread(BMessage* refs_message, BMessenger* messenger)
23	:
24	GenericThread(ExpanderThreadName, B_NORMAL_PRIORITY, refs_message),
25	fWindowMessenger(messenger),
26	fThreadId(-1),
27	fStdIn(-1),
28	fStdOut(-1),
29	fStdErr(-1),
30	fExpanderOutput(NULL),
31	fExpanderError(NULL)
32{
33	SetDataStore(new BMessage(*refs_message));
34		// leak?
35	// prevents bug with B_SIMPLE_DATA
36	// (drag&drop messages)
37}
38
39
40ExpanderThread::~ExpanderThread()
41{
42	delete fWindowMessenger;
43}
44
45
46status_t
47ExpanderThread::ThreadStartup()
48{
49	status_t status = B_OK;
50	entry_ref srcRef;
51	entry_ref destRef;
52	BString cmd;
53
54	if ((status = GetDataStore()->FindRef("srcRef", &srcRef)) != B_OK)
55		return status;
56
57	if (GetDataStore()->FindRef("destRef", &destRef) == B_OK) {
58		BPath path(&destRef);
59		chdir(path.Path());
60	}
61
62	if ((status = GetDataStore()->FindString("cmd", &cmd)) != B_OK)
63		return status;
64
65	BPath path(&srcRef);
66	BString pathString(path.Path());
67	pathString.CharacterEscape("\\\"$`", '\\');
68	pathString.Prepend("\"");
69	pathString.Append("\"");
70	cmd.ReplaceAll("%s", pathString.String());
71
72	int32 argc = 3;
73	const char** argv = new const char * [argc + 1];
74
75	argv[0] = strdup("/bin/sh");
76	argv[1] = strdup("-c");
77	argv[2] = strdup(cmd.String());
78	argv[argc] = NULL;
79
80	fThreadId = PipeCommand(argc, argv, fStdIn, fStdOut, fStdErr);
81
82	delete [] argv;
83
84	if (fThreadId < 0)
85		return fThreadId;
86
87	// lower the command priority since it is a background task.
88	set_thread_priority(fThreadId, B_LOW_PRIORITY);
89
90	resume_thread(fThreadId);
91
92	int flags = fcntl(fStdOut, F_GETFL, 0);
93	flags |= O_NONBLOCK;
94	fcntl(fStdOut, F_SETFL, flags);
95	flags = fcntl(fStdErr, F_GETFL, 0);
96	flags |= O_NONBLOCK;
97	fcntl(fStdErr, F_SETFL, flags);
98
99	fExpanderOutput = fdopen(fStdOut, "r");
100	fExpanderError = fdopen(fStdErr, "r");
101
102	return B_OK;
103}
104
105
106status_t
107ExpanderThread::ExecuteUnit(void)
108{
109	// read output and error from command
110	// send it to window
111
112	BMessage message('outp');
113	bool outputAdded = false;
114	for (int32 i = 0; i < 50; i++) {
115		char* output_string = fgets(fExpanderOutputBuffer , LINE_MAX,
116			fExpanderOutput);
117		if (output_string == NULL)
118			break;
119
120		message.AddString("output", output_string);
121		outputAdded = true;
122	}
123	if (outputAdded)
124		fWindowMessenger->SendMessage(&message);
125
126	if (feof(fExpanderOutput))
127		return EOF;
128
129	char* error_string = fgets(fExpanderOutputBuffer, LINE_MAX,
130		fExpanderError);
131	if (error_string != NULL && strcmp(error_string, "\n")) {
132		BMessage message('errp');
133		message.AddString("error", error_string);
134		fWindowMessenger->SendMessage(&message);
135	}
136
137	// streams are non blocking, sleep every 100ms
138	snooze(100000);
139
140	return B_OK;
141}
142
143
144void
145ExpanderThread::PushInput(BString text)
146{
147	text += "\n";
148	write(fStdIn, text.String(), text.Length());
149}
150
151
152status_t
153ExpanderThread::ThreadShutdown(void)
154{
155	close(fStdIn);
156	close(fStdOut);
157	close(fStdErr);
158
159	return B_OK;
160}
161
162
163void
164ExpanderThread::ThreadStartupFailed(status_t status)
165{
166	fprintf(stderr, "ExpanderThread::ThreadStartupFailed() : %s\n",
167		strerror(status));
168
169	Quit();
170}
171
172
173void
174ExpanderThread::ExecuteUnitFailed(status_t status)
175{
176	if (status == EOF) {
177		// thread has finished, been quit or killed, we don't know
178		fWindowMessenger->SendMessage(new BMessage('exit'));
179	} else {
180		// explicit error - communicate error to Window
181		fWindowMessenger->SendMessage(new BMessage('exrr'));
182	}
183
184	Quit();
185}
186
187
188void
189ExpanderThread::ThreadShutdownFailed(status_t status)
190{
191	fprintf(stderr, "ExpanderThread::ThreadShutdownFailed() %s\n",
192		strerror(status));
193}
194
195
196status_t
197ExpanderThread::ProcessRefs(BMessage *msg)
198{
199	return B_OK;
200}
201
202
203thread_id
204ExpanderThread::PipeCommand(int argc, const char** argv, int& in, int& out,
205	int& err, const char** envp)
206{
207	// This function written by Peter Folk <pfolk@uni.uiuc.edu>
208	// and published in the BeDevTalk FAQ
209	// http://www.abisoft.com/faq/BeDevTalk_FAQ.html#FAQ-209
210
211	// Save current FDs
212	int old_out = dup(1);
213	int old_err = dup(2);
214
215	int filedes[2];
216
217	// create new pipe FDs as stdout, stderr
218	pipe(filedes);  dup2(filedes[1], 1); close(filedes[1]);
219	out = filedes[0]; // Read from out, taken from cmd's stdout
220	pipe(filedes);  dup2(filedes[1], 2); close(filedes[1]);
221	err = filedes[0]; // Read from err, taken from cmd's stderr
222
223	// taken from pty.cpp
224	// create a tty for stdin, as utilities don't generally use stdin
225	int master = posix_openpt(O_RDWR);
226	if (master < 0)
227		return -1;
228
229	int slave;
230	const char* ttyName;
231	if (grantpt(master) != 0 || unlockpt(master) != 0
232		|| (ttyName = ptsname(master)) == NULL
233		|| (slave = open(ttyName, O_RDWR | O_NOCTTY)) < 0) {
234		close(master);
235		return -1;
236	}
237
238	int pid = fork();
239	if (pid < 0) {
240		close(master);
241		close(slave);
242		return -1;
243	}
244
245	// child
246	if (pid == 0) {
247		close(master);
248
249		setsid();
250		if (ioctl(slave, TIOCSCTTY, NULL) != 0) {
251			close(slave);
252			return -1;
253		}
254
255		dup2(slave, 0);
256		close(slave);
257
258		// "load" command.
259		execv(argv[0], (char *const *)argv);
260
261		// shouldn't return
262		return -1;
263	}
264
265	// parent
266	close (slave);
267	in = master;
268
269	// Restore old FDs
270	close(1); dup(old_out); close(old_out);
271	close(2); dup(old_err); close(old_err);
272
273	// Theoretically I should do loads of error checking, but
274	// the calls aren't very likely to fail, and that would
275	// muddy up the example quite a bit. YMMV.
276
277	return pid;
278}
279
280
281status_t
282ExpanderThread::SuspendExternalExpander()
283{
284	thread_info info;
285	status_t status = get_thread_info(fThreadId, &info);
286
287	if (status == B_OK)
288		return send_signal(-fThreadId, SIGSTOP);
289	else
290		return status;
291}
292
293
294status_t
295ExpanderThread::ResumeExternalExpander()
296{
297	thread_info info;
298	status_t status = get_thread_info(fThreadId, &info);
299
300	if (status == B_OK)
301		return send_signal(-fThreadId, SIGCONT);
302	else
303		return status;
304}
305
306
307status_t
308ExpanderThread::InterruptExternalExpander()
309{
310	thread_info info;
311	status_t status = get_thread_info(fThreadId, &info);
312
313	if (status == B_OK) {
314		status = send_signal(-fThreadId, SIGINT);
315		WaitOnExternalExpander();
316	}
317
318	return status;
319}
320
321
322status_t
323ExpanderThread::WaitOnExternalExpander()
324{
325	thread_info info;
326	status_t status = get_thread_info(fThreadId, &info);
327
328	if (status == B_OK)
329		return wait_for_thread(fThreadId, &status);
330	else
331		return status;
332}
333