1// AppRunner.cpp
2
3#include <errno.h>
4#include <stdlib.h>
5#include <unistd.h>
6
7#include <Autolock.h>
8#include <Entry.h>
9#include <Messenger.h>
10#include <String.h>
11
12#include <TestShell.h>
13#include <TestUtils.h>
14#include <cppunit/TestAssert.h>
15
16#include "AppRunner.h"
17
18static const char *kAppRunnerTeamPort = "app runner team port";
19
20// constructor
21AppRunner::AppRunner(bool requestQuitOnDestruction)
22		 : fOutputLock(),
23		   fRemotePort(-1),
24		   fOutput(),
25		   fReader(-1),
26		   fTeam(-1),
27		   fRef(),
28		   fMessenger(),
29		   fRequestQuitOnDestruction(requestQuitOnDestruction)
30{
31}
32
33// destructor
34AppRunner::~AppRunner()
35{
36	if (fRequestQuitOnDestruction)
37		WaitFor(true);
38	if (fReader >= 0) {
39		int32 result;
40		wait_for_thread(fReader, &result);
41	}
42}
43
44// Run
45status_t
46AppRunner::Run(const char *command, const char *args, bool findCommand)
47{
48	status_t error = (HasQuitted() ? B_OK : B_ERROR);
49	// get the app path
50	BString appPath;
51	if (findCommand) {
52		find_test_app(command, &appPath);
53		command = appPath.String();
54	}
55	get_ref_for_path(command, &fRef);
56	// add args, i.e. compose the command line
57	BString cmdLine(command);
58	if (args) {
59		cmdLine += " ";
60		cmdLine += args;
61	}
62	// lock the team port
63	bool teamPortLocked = false;
64	if (error == B_OK) {
65		teamPortLocked = _LockTeamPort();
66		if (!teamPortLocked)
67			error = B_ERROR;
68	}
69	// run the command
70	if (error == B_OK) {
71		cmdLine += " &";
72		if (system(cmdLine.String()) != 0)
73			error = errno;
74	}
75	// read the port ID
76	if (error == B_OK) {
77		fRemotePort = _ReadPortID(fMessenger);
78		if (fRemotePort < 0)
79			error = fRemotePort;
80	}
81	// unlock the team port
82	if (teamPortLocked)
83		_UnlockTeamPort();
84	// get the team ID
85	if (error == B_OK) {
86		port_info info;
87		error = get_port_info(fRemotePort, &info);
88		fTeam = info.team;
89	}
90	// spawn the reader thread
91	if (error == B_OK) {
92		fReader = spawn_thread(&_ReaderEntry, "AppRunner reader",
93							   B_NORMAL_PRIORITY, (void*)this);
94		if (fReader >= 0)
95			error = resume_thread(fReader);
96		else
97			error = fReader;
98	}
99	// cleanup on error
100	if (error != B_OK) {
101		if (fReader >= 0) {
102			kill_thread(fReader);
103			fReader = -1;
104		}
105	}
106	return error;
107}
108
109// HasQuitted
110bool
111AppRunner::HasQuitted()
112{
113	port_info info;
114	return (get_port_info(fRemotePort, &info) != B_OK);
115}
116
117// WaitFor
118void
119AppRunner::WaitFor(bool requestQuit)
120{
121	if (!HasQuitted() && requestQuit)
122		RequestQuit();
123	while (!HasQuitted())
124		snooze(10000);
125}
126
127// Team
128team_id
129AppRunner::Team()
130{
131	return fTeam;
132}
133
134// AppLooperPort
135port_id
136AppRunner::AppLooperPort()
137{
138	struct messenger_hack {
139		port_id	fPort;
140		int32	fHandlerToken;
141		team_id	fTeam;
142		int32	extra0;
143		int32	extra1;
144		bool	fPreferredTarget;
145		bool	extra2;
146		bool	extra3;
147		bool	extra4;
148	};
149	return ((messenger_hack*)&fMessenger)->fPort;
150}
151
152// GetRef
153status_t
154AppRunner::GetRef(entry_ref *ref)
155{
156	status_t error = (ref ? B_OK : B_ERROR);
157	if (error == B_OK)
158		*ref = fRef;
159	return error;
160}
161
162// RequestQuit
163status_t
164AppRunner::RequestQuit()
165{
166	status_t error = B_OK;
167	if (fTeam >= 0) {
168		BMessenger messenger(fMessenger);
169		error = messenger.SendMessage(B_QUIT_REQUESTED);
170	} else
171		error = fTeam;
172	return error;
173}
174
175// GetOutput
176status_t
177AppRunner::GetOutput(BString *buffer)
178{
179	status_t error = (buffer ? B_OK : B_BAD_VALUE);
180	if (error == B_OK) {
181		BAutolock locker(fOutputLock);
182		size_t size = fOutput.BufferLength();
183		const void *output = fOutput.Buffer();
184		if (size > 0)
185			buffer->SetTo((const char*)output, size);
186		else
187			*buffer = "";
188	}
189	return error;
190}
191
192// ReadOutput
193ssize_t
194AppRunner::ReadOutput(void *buffer, size_t size)
195{
196	BAutolock locker(fOutputLock);
197	return fOutput.Read(buffer, size);
198}
199
200// ReadOutputAt
201ssize_t
202AppRunner::ReadOutputAt(off_t position, void *buffer, size_t size)
203{
204	BAutolock locker(fOutputLock);
205	return fOutput.ReadAt(position, buffer, size);
206}
207
208// _ReaderEntry
209int32
210AppRunner::_ReaderEntry(void *data)
211{
212	int32 result = 0;
213	if (AppRunner *me = (AppRunner*)data)
214		result = me->_ReaderLoop();
215	return result;
216}
217
218// _ReaderLoop
219int32
220AppRunner::_ReaderLoop()
221{
222	char buffer[10240];
223	port_id port = fRemotePort;
224	status_t error = B_OK;
225	while (error == B_OK) {
226		int32 code;
227		ssize_t bytes = read_port(port, &code, buffer, sizeof(buffer) - 1);
228		if (bytes > 0) {
229			// write the buffer
230			BAutolock locker(fOutputLock);
231			off_t oldPosition = fOutput.Seek(0, SEEK_END);
232			fOutput.Write(buffer, bytes);
233			fOutput.Seek(oldPosition, SEEK_SET);
234		} else if (bytes < 0)
235			error = bytes;
236	}
237	fRemotePort = -1;
238	return 0;
239}
240
241// _LockTeamPort
242bool
243AppRunner::_LockTeamPort()
244{
245	bool result = fTeamPortLock.Lock();
246	// lazy port creation
247	if (result && fTeamPort < 0) {
248		fTeamPort = create_port(5, kAppRunnerTeamPort);
249		if (fTeamPort < 0) {
250			fTeamPortLock.Unlock();
251			result = false;
252		}
253	}
254	return result;
255}
256
257// _UnlockTeamPort
258void
259AppRunner::_UnlockTeamPort()
260{
261	fTeamPortLock.Unlock();
262}
263
264// _ReadPortID
265port_id
266AppRunner::_ReadPortID(BMessenger &messenger)
267{
268	port_id port = -1;
269	ssize_t size = read_port(fTeamPort, &port, &messenger, sizeof(BMessenger));
270	if (size < 0)
271		port = size;
272	return port;
273}
274
275
276// fTeamPort
277port_id	AppRunner::fTeamPort = -1;
278
279// fTeamPortLock
280BLocker	AppRunner::fTeamPortLock;
281
282
283// find_test_app
284status_t
285find_test_app(const char *testApp, BString *path)
286{
287	status_t error = (testApp && path ? B_OK : B_BAD_VALUE);
288	if (error == B_OK) {
289		*path = BTestShell::GlobalTestDir();
290		path->CharacterEscape(" \t\n!\"'`$&()?*+{}[]<>|", '\\');
291		*path += "/";
292		*path += testApp;
293		#ifdef TEST_R5
294			*path += "_r5";
295		#endif
296	}
297	return error;
298}
299
300// find_test_app
301status_t
302find_test_app(const char *testApp, entry_ref *ref)
303{
304	status_t error = (testApp && ref ? B_OK : B_BAD_VALUE);
305	BString path;
306	if (error == B_OK)
307		error = find_test_app(testApp, &path);
308	if (error == B_OK)
309		error = get_ref_for_path(path.String(), ref);
310	return error;
311}
312
313