xref: /haiku/src/apps/expander/ExpanderThread.cpp (revision a3e794ae459fec76826407f8ba8c94cd3535f128)
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 
19 const char* ExpanderThreadName = "ExpanderThread";
20 
21 
22 ExpanderThread::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 
40 ExpanderThread::~ExpanderThread()
41 {
42 	delete fWindowMessenger;
43 }
44 
45 
46 status_t
47 ExpanderThread::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 ((status = 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 
106 status_t
107 ExpanderThread::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 
144 void
145 ExpanderThread::PushInput(BString text)
146 {
147 	text += "\n";
148 	write(fStdIn, text.String(), text.Length());
149 }
150 
151 
152 status_t
153 ExpanderThread::ThreadShutdown(void)
154 {
155 	close(fStdIn);
156 	close(fStdOut);
157 	close(fStdErr);
158 
159 	return B_OK;
160 }
161 
162 
163 void
164 ExpanderThread::ThreadStartupFailed(status_t status)
165 {
166 	fprintf(stderr, "ExpanderThread::ThreadStartupFailed() : %s\n",
167 		strerror(status));
168 
169 	Quit();
170 }
171 
172 
173 void
174 ExpanderThread::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 
188 void
189 ExpanderThread::ThreadShutdownFailed(status_t status)
190 {
191 	fprintf(stderr, "ExpanderThread::ThreadShutdownFailed() %s\n",
192 		strerror(status));
193 }
194 
195 
196 status_t
197 ExpanderThread::ProcessRefs(BMessage *msg)
198 {
199 	return B_OK;
200 }
201 
202 
203 thread_id
204 ExpanderThread::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 			return -1;
252 
253 		dup2(slave, 0);
254 		close(slave);
255 
256 		// "load" command.
257 		execv(argv[0], (char *const *)argv);
258 
259 		// shouldn't return
260 		return -1;
261 	}
262 
263 	// parent
264 	close (slave);
265 	in = master;
266 
267 	// Restore old FDs
268 	close(1); dup(old_out); close(old_out);
269 	close(2); dup(old_err); close(old_err);
270 
271 	// Theoretically I should do loads of error checking, but
272 	// the calls aren't very likely to fail, and that would
273 	// muddy up the example quite a bit. YMMV.
274 
275 	return pid;
276 }
277 
278 
279 status_t
280 ExpanderThread::SuspendExternalExpander()
281 {
282 	thread_info info;
283 	status_t status = get_thread_info(fThreadId, &info);
284 
285 	if (status == B_OK)
286 		return send_signal(-fThreadId, SIGSTOP);
287 	else
288 		return status;
289 }
290 
291 
292 status_t
293 ExpanderThread::ResumeExternalExpander()
294 {
295 	thread_info info;
296 	status_t status = get_thread_info(fThreadId, &info);
297 
298 	if (status == B_OK)
299 		return send_signal(-fThreadId, SIGCONT);
300 	else
301 		return status;
302 }
303 
304 
305 status_t
306 ExpanderThread::InterruptExternalExpander()
307 {
308 	thread_info info;
309 	status_t status = get_thread_info(fThreadId, &info);
310 
311 	if (status == B_OK) {
312 		status = send_signal(-fThreadId, SIGINT);
313 		WaitOnExternalExpander();
314 	}
315 
316 	return status;
317 }
318 
319 
320 status_t
321 ExpanderThread::WaitOnExternalExpander()
322 {
323 	thread_info info;
324 	status_t status = get_thread_info(fThreadId, &info);
325 
326 	if (status == B_OK)
327 		return wait_for_thread(fThreadId, &status);
328 	else
329 		return status;
330 }
331