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