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