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
ExpanderThread(BMessage * refs_message,BMessenger * messenger)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
~ExpanderThread()40 ExpanderThread::~ExpanderThread()
41 {
42 delete fWindowMessenger;
43 }
44
45
46 status_t
ThreadStartup()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 (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
ExecuteUnit(void)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
PushInput(BString text)145 ExpanderThread::PushInput(BString text)
146 {
147 text += "\n";
148 write(fStdIn, text.String(), text.Length());
149 }
150
151
152 status_t
ThreadShutdown(void)153 ExpanderThread::ThreadShutdown(void)
154 {
155 close(fStdIn);
156 close(fStdOut);
157 close(fStdErr);
158
159 return B_OK;
160 }
161
162
163 void
ThreadStartupFailed(status_t status)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
ExecuteUnitFailed(status_t status)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
ThreadShutdownFailed(status_t status)189 ExpanderThread::ThreadShutdownFailed(status_t status)
190 {
191 fprintf(stderr, "ExpanderThread::ThreadShutdownFailed() %s\n",
192 strerror(status));
193 }
194
195
196 status_t
ProcessRefs(BMessage * msg)197 ExpanderThread::ProcessRefs(BMessage *msg)
198 {
199 return B_OK;
200 }
201
202
203 thread_id
PipeCommand(int argc,const char ** argv,int & in,int & out,int & err,const char ** envp)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 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
281 status_t
SuspendExternalExpander()282 ExpanderThread::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
294 status_t
ResumeExternalExpander()295 ExpanderThread::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
307 status_t
InterruptExternalExpander()308 ExpanderThread::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
322 status_t
WaitOnExternalExpander()323 ExpanderThread::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