xref: /haiku/src/apps/expander/ExpanderThread.cpp (revision b289aaf66bbf6e173aa90fa194fc256965f1b34d)
1 /*
2  * Copyright 2004-2006, 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 <Messenger.h>
7 #include <Path.h>
8 #include "ExpanderThread.h"
9 #include <image.h>
10 #include <signal.h>
11 #include <unistd.h>
12 #include <errno.h>
13 
14 const char * ExpanderThreadName	=	"ExpanderThread";
15 
16 ExpanderThread::ExpanderThread(BMessage * refs_message, BMessenger * messenger)
17 	:	GenericThread(ExpanderThreadName, B_NORMAL_PRIORITY, refs_message),
18 	fWindowMessenger(messenger),
19 	fThreadId(-1),
20 	fStdIn(-1),
21 	fStdOut(-1),
22 	fStdErr(-1),
23 	fExpanderOutput(NULL),
24 	fExpanderOutputString(),
25 	fExpanderOutputBuffer(new char [4096])
26 {
27 	SetDataStore(new BMessage(* refs_message));  // leak?
28 	// prevents bug with B_SIMPLE_DATA
29 	// (drag&drop messages)
30 }
31 
32 
33 ExpanderThread::~ExpanderThread()
34 {
35 	delete fWindowMessenger;
36 	delete [] fExpanderOutputBuffer;
37 }
38 
39 
40 status_t
41 ExpanderThread::ThreadStartup()
42 {
43 	status_t	status	=	B_OK;
44 	entry_ref	srcRef, destRef;
45 	BString 	cmd;
46 
47 	if ((status = GetDataStore()->FindRef("srcRef", &srcRef)) != B_OK)
48 		return status;
49 	if ((status = GetDataStore()->FindRef("destRef", &destRef)) == B_OK) {
50 		BPath path(&destRef);
51 		chdir(path.Path());
52 	}
53 	if ((status = GetDataStore()->FindString("cmd", &cmd)) != B_OK)
54 		return status;
55 
56 	BPath path(&srcRef);
57 	BString pathString(path.Path());
58 	pathString.CharacterEscape("\"$`", '\\');
59 	pathString.Prepend("\"");
60 	pathString.Append("\"");
61 	cmd.ReplaceAll("%s", pathString.String());
62 
63 	int32 argc = 3;
64 	const char ** argv = new const char * [argc + 1];
65 
66 	argv[0]	=	strdup("/bin/sh");
67 	argv[1]	=	strdup("-c");
68 	argv[2]	=	strdup(cmd.String());
69 	argv[argc] = NULL;
70 
71 	fThreadId = PipeCommand(argc, argv, fStdIn, fStdOut, fStdErr);
72 
73 	delete [] argv;
74 
75 	if (fThreadId < 0)
76 		return fThreadId;
77 
78 	// lower the command priority since it is a background task.
79 	set_thread_priority(fThreadId, B_LOW_PRIORITY);
80 
81 	resume_thread(fThreadId);
82 
83 	fExpanderOutput = fdopen(fStdOut, "r");
84 
85 	return B_OK;
86 }
87 
88 
89 status_t
90 ExpanderThread::ExecuteUnit(void)
91 {
92 	// read output from command
93 	// send it to window
94 
95 	char *output_string = fgets(fExpanderOutputBuffer , 4096 - 1, fExpanderOutput);
96 
97 	if (output_string == NULL)
98 		return EOF;
99 
100 	BMessage message('outp');
101 	message.AddString("output", output_string);
102 	for (int32 i = 0; i < 5; i++) {
103 		output_string = fgets(fExpanderOutputBuffer , 4096 - 1, fExpanderOutput);
104 		if (!output_string)
105 			break;
106 		message.AddString("output", output_string);
107 	}
108 	fWindowMessenger->SendMessage(&message);
109 
110 	return B_OK;
111 }
112 
113 
114 status_t
115 ExpanderThread::ThreadShutdown(void)
116 {
117 	close(fStdIn);
118 	close(fStdOut);
119 	close(fStdErr);
120 
121 	return B_OK;
122 }
123 
124 
125 void
126 ExpanderThread::ThreadStartupFailed(status_t status)
127 {
128 	fprintf(stderr, "ExpanderThread::ThreadStartupFailed() : %s\n", strerror(status));
129 
130 	Quit();
131 }
132 
133 
134 void
135 ExpanderThread::ExecuteUnitFailed(status_t status)
136 {
137 	if (status == EOF) {
138 		// thread has finished, been quit or killed, we don't know
139 		fWindowMessenger->SendMessage(new BMessage('exit'));
140 	} else {
141 		// explicit error - communicate error to Window
142 		fWindowMessenger->SendMessage(new BMessage('exrr'));
143 	}
144 
145 	Quit();
146 }
147 
148 
149 void
150 ExpanderThread::ThreadShutdownFailed(status_t status)
151 {
152 	fprintf(stderr, "ExpanderThread::ThreadShutdownFailed() %s\n", strerror(status));
153 }
154 
155 
156 status_t
157 ExpanderThread::ProcessRefs(BMessage *msg)
158 {
159 	return B_OK;
160 }
161 
162 
163 thread_id
164 ExpanderThread::PipeCommand(int argc, const char **argv, int &in, int &out, int &err, const char **envp)
165 {
166 	// This function written by Peter Folk <pfolk@uni.uiuc.edu>
167 	// and published in the BeDevTalk FAQ
168 	// http://www.abisoft.com/faq/BeDevTalk_FAQ.html#FAQ-209
169 
170 	// Save current FDs
171 	int old_in  =  dup(0);
172 	int old_out  =  dup(1);
173 	int old_err  =  dup(2);
174 
175 	int filedes[2];
176 
177 	/* Create new pipe FDs as stdin, stdout, stderr */
178 	pipe(filedes);  dup2(filedes[0], 0); close(filedes[0]);
179 	in = filedes[1];  // Write to in, appears on cmd's stdin
180 	pipe(filedes);  dup2(filedes[1], 1); close(filedes[1]);
181 	out = filedes[0]; // Read from out, taken from cmd's stdout
182 	pipe(filedes);  dup2(filedes[1], 2); close(filedes[1]);
183 	err = filedes[0]; // Read from err, taken from cmd's stderr
184 
185 	// "load" command.
186 	thread_id ret  =  load_image(argc, argv, envp);
187 
188 	if (ret < B_OK)
189 		return ret;
190 
191 	// thread ret is now suspended.
192 
193 	setpgid(ret, ret);
194 
195 	// Restore old FDs
196 	close(0); dup(old_in); close(old_in);
197 	close(1); dup(old_out); close(old_out);
198 	close(2); dup(old_err); close(old_err);
199 
200 	/* Theoretically I should do loads of error checking, but
201 	   the calls aren't very likely to fail, and that would
202 	   muddy up the example quite a bit.  YMMV. */
203 
204 	return ret;
205 }
206 
207 
208 status_t
209 ExpanderThread::SuspendExternalExpander()
210 {
211 	thread_info thread_info;
212 	status_t status = get_thread_info(fThreadId, &thread_info);
213 
214 	if (status == B_OK)
215 		return send_signal(-fThreadId, SIGSTOP);
216 	else
217 		return status;
218 }
219 
220 
221 status_t
222 ExpanderThread::ResumeExternalExpander()
223 {
224 	thread_info thread_info;
225 	status_t status = get_thread_info(fThreadId, &thread_info);
226 
227 	if (status == B_OK)
228 		return send_signal(-fThreadId, SIGCONT);
229 	else
230 		return status;
231 }
232 
233 
234 status_t
235 ExpanderThread::InterruptExternalExpander()
236 {
237 	thread_info thread_info;
238 	status_t status = get_thread_info(fThreadId, &thread_info);
239 
240 	if (status == B_OK) {
241 		status = send_signal(-fThreadId, SIGINT);
242 		WaitOnExternalExpander();
243 	}
244 	return status;
245 }
246 
247 
248 status_t
249 ExpanderThread::WaitOnExternalExpander()
250 {
251 	thread_info thread_info;
252 	status_t status = get_thread_info(fThreadId, &thread_info);
253 
254 	if (status == B_OK)
255 		return wait_for_thread(fThreadId, &status);
256 	else
257 		return status;
258 }
259