xref: /haiku/src/apps/debugger/user_interface/cli/CliContext.cpp (revision 7ee53ed3bd2222305c93a4959f8c587c373ed97c)
1 /*
2  * Copyright 2012, Ingo Weinhold, ingo_weinhold@gmx.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include "CliContext.h"
8 
9 #include <AutoDeleter.h>
10 #include <AutoLocker.h>
11 
12 #include "UserInterface.h"
13 
14 
15 // NOTE: This is a simple work-around for EditLine not having any kind of user
16 // data field. Hence in _GetPrompt() we don't have access to the context object.
17 // ATM only one CLI is possible in Debugger, so a static variable works well
18 // enough. Should that ever change, we would need a thread-safe
19 // EditLine* -> CliContext* map.
20 static CliContext* sCurrentContext;
21 
22 
23 // #pragma mark - Event
24 
25 
26 struct CliContext::Event : DoublyLinkedListLinkImpl<CliContext::Event> {
27 	Event(int type, Thread* thread = NULL)
28 		:
29 		fType(type),
30 		fThreadReference(thread)
31 	{
32 	}
33 
34 	int Type() const
35 	{
36 		return fType;
37 	}
38 
39 	Thread* GetThread() const
40 	{
41 		return fThreadReference.Get();
42 	}
43 
44 private:
45 	int					fType;
46 	BReference<Thread>	fThreadReference;
47 };
48 
49 
50 // #pragma mark - CliContext
51 
52 
53 CliContext::CliContext()
54 	:
55 	fLock("CliContext"),
56 	fTeam(NULL),
57 	fListener(NULL),
58 	fEditLine(NULL),
59 	fHistory(NULL),
60 	fPrompt(NULL),
61 	fBlockingSemaphore(-1),
62 	fInputLoopWaitingForEvents(0),
63 	fEventsOccurred(0),
64 	fInputLoopWaiting(false),
65 	fTerminating(false),
66 	fCurrentThread(NULL)
67 {
68 	sCurrentContext = this;
69 }
70 
71 
72 CliContext::~CliContext()
73 {
74 	Cleanup();
75 	sCurrentContext = NULL;
76 
77 	if (fBlockingSemaphore >= 0)
78 		delete_sem(fBlockingSemaphore);
79 }
80 
81 
82 status_t
83 CliContext::Init(Team* team, UserInterfaceListener* listener)
84 {
85 	fTeam = team;
86 	fListener = listener;
87 
88 	fTeam->AddListener(this);
89 
90 	status_t error = fLock.InitCheck();
91 	if (error != B_OK)
92 		return error;
93 
94 	fBlockingSemaphore = create_sem(0, "CliContext block");
95 	if (fBlockingSemaphore < 0)
96 		return fBlockingSemaphore;
97 
98 	fEditLine = el_init("Debugger", stdin, stdout, stderr);
99 	if (fEditLine == NULL)
100 		return B_ERROR;
101 
102 	fHistory = history_init();
103 	if (fHistory == NULL)
104 		return B_ERROR;
105 
106 	HistEvent historyEvent;
107 	history(fHistory, &historyEvent, H_SETSIZE, 100);
108 
109 	el_set(fEditLine, EL_HIST, &history, fHistory);
110 	el_set(fEditLine, EL_EDITOR, "emacs");
111 	el_set(fEditLine, EL_PROMPT, &_GetPrompt);
112 
113 	return B_OK;
114 }
115 
116 
117 void
118 CliContext::Cleanup()
119 {
120 	Terminating();
121 
122 	while (Event* event = fPendingEvents.RemoveHead())
123 		delete event;
124 
125 	if (fEditLine != NULL) {
126 		el_end(fEditLine);
127 		fEditLine = NULL;
128 	}
129 
130 	if (fHistory != NULL) {
131 		history_end(fHistory);
132 		fHistory = NULL;
133 	}
134 
135 	if (fTeam != NULL) {
136 		fTeam->RemoveListener(this);
137 		fTeam = NULL;
138 	}
139 }
140 
141 
142 void
143 CliContext::Terminating()
144 {
145 	AutoLocker<BLocker> locker(fLock);
146 
147 	fTerminating = true;
148 	_SignalInputLoop(EVENT_QUIT);
149 
150 	// TODO: Signal the input loop, should it be in PromptUser()!
151 }
152 
153 
154 thread_id
155 CliContext::CurrentThreadID() const
156 {
157 	return fCurrentThread != NULL ? fCurrentThread->ID() : -1;
158 }
159 
160 
161 void
162 CliContext::SetCurrentThread(Thread* thread)
163 {
164 	AutoLocker<BLocker> locker(fLock);
165 
166 	if (fCurrentThread != NULL)
167 		fCurrentThread->ReleaseReference();
168 
169 	fCurrentThread = thread;
170 
171 	if (fCurrentThread != NULL)
172 		fCurrentThread->AcquireReference();
173 }
174 
175 
176 void
177 CliContext::PrintCurrentThread()
178 {
179 	AutoLocker<Team> teamLocker(fTeam);
180 
181 	if (fCurrentThread != NULL) {
182 		printf("current thread: %" B_PRId32 " \"%s\"\n", fCurrentThread->ID(),
183 			fCurrentThread->Name());
184 	} else
185 		printf("no current thread\n");
186 }
187 
188 
189 const char*
190 CliContext::PromptUser(const char* prompt)
191 {
192 	fPrompt = prompt;
193 
194 	int count;
195 	const char* line = el_gets(fEditLine, &count);
196 
197 	fPrompt = NULL;
198 
199 	ProcessPendingEvents();
200 
201 	return line;
202 }
203 
204 
205 void
206 CliContext::AddLineToInputHistory(const char* line)
207 {
208 	HistEvent historyEvent;
209 	history(fHistory, &historyEvent, H_ENTER, line);
210 }
211 
212 
213 void
214 CliContext::QuitSession(bool killTeam)
215 {
216 	_PrepareToWaitForEvents(EVENT_QUIT);
217 
218 	fListener->UserInterfaceQuitRequested(
219 		killTeam
220 			? UserInterfaceListener::QUIT_OPTION_ASK_KILL_TEAM
221 			: UserInterfaceListener::QUIT_OPTION_ASK_RESUME_TEAM);
222 
223 	_WaitForEvents();
224 }
225 
226 
227 void
228 CliContext::WaitForThreadOrUser()
229 {
230 	ProcessPendingEvents();
231 
232 // TODO: Deal with SIGINT as well!
233 	for (;;) {
234 		_PrepareToWaitForEvents(
235 			EVENT_USER_INTERRUPT | EVENT_THREAD_STOPPED);
236 
237 		// check whether there are any threads stopped already
238 		Thread* stoppedThread = NULL;
239 		BReference<Thread> stoppedThreadReference;
240 
241 		AutoLocker<Team> teamLocker(fTeam);
242 
243 		for (ThreadList::ConstIterator it = fTeam->Threads().GetIterator();
244 				Thread* thread = it.Next();) {
245 			if (thread->State() == THREAD_STATE_STOPPED) {
246 				stoppedThread = thread;
247 				stoppedThreadReference.SetTo(thread);
248 				break;
249 			}
250 		}
251 
252 		teamLocker.Unlock();
253 
254 		if (stoppedThread != NULL) {
255 			if (fCurrentThread == NULL)
256 				fCurrentThread = stoppedThread;
257 
258 			_SignalInputLoop(EVENT_THREAD_STOPPED);
259 		}
260 
261 		uint32 events = _WaitForEvents();
262 		if ((events & EVENT_QUIT) != 0 || stoppedThread != NULL) {
263 			ProcessPendingEvents();
264 			return;
265 		}
266 	}
267 }
268 
269 
270 void
271 CliContext::ProcessPendingEvents()
272 {
273 	AutoLocker<Team> teamLocker(fTeam);
274 
275 	for (;;) {
276 		// get the next event
277 		AutoLocker<BLocker> locker(fLock);
278 		Event* event = fPendingEvents.RemoveHead();
279 		locker.Unlock();
280 		if (event == NULL)
281 			break;
282 		ObjectDeleter<Event> eventDeleter(event);
283 
284 		// process the event
285 		Thread* thread = event->GetThread();
286 
287 		switch (event->Type()) {
288 			case EVENT_QUIT:
289 			case EVENT_USER_INTERRUPT:
290 				break;
291 			case EVENT_THREAD_ADDED:
292 				printf("[new thread: %" B_PRId32 " \"%s\"]\n", thread->ID(),
293 					thread->Name());
294 				break;
295 			case EVENT_THREAD_REMOVED:
296 				printf("[thread terminated: %" B_PRId32 " \"%s\"]\n",
297 					thread->ID(), thread->Name());
298 				break;
299 			case EVENT_THREAD_STOPPED:
300 				printf("[thread stopped: %" B_PRId32 " \"%s\"]\n",
301 					thread->ID(), thread->Name());
302 				break;
303 		}
304 	}
305 }
306 
307 
308 void
309 CliContext::ThreadAdded(const Team::ThreadEvent& threadEvent)
310 {
311 	_QueueEvent(
312 		new(std::nothrow) Event(EVENT_THREAD_ADDED, threadEvent.GetThread()));
313 	_SignalInputLoop(EVENT_THREAD_ADDED);
314 }
315 
316 
317 void
318 CliContext::ThreadRemoved(const Team::ThreadEvent& threadEvent)
319 {
320 	_QueueEvent(
321 		new(std::nothrow) Event(EVENT_THREAD_REMOVED, threadEvent.GetThread()));
322 	_SignalInputLoop(EVENT_THREAD_REMOVED);
323 }
324 
325 
326 void
327 CliContext::ThreadStateChanged(const Team::ThreadEvent& threadEvent)
328 {
329 	if (threadEvent.GetThread()->State() != THREAD_STATE_STOPPED)
330 		return;
331 
332 	_QueueEvent(
333 		new(std::nothrow) Event(EVENT_THREAD_STOPPED, threadEvent.GetThread()));
334 	_SignalInputLoop(EVENT_THREAD_STOPPED);
335 }
336 
337 
338 void
339 CliContext::_QueueEvent(Event* event)
340 {
341 	if (event == NULL) {
342 		// no memory -- can't do anything about it
343 		return;
344 	}
345 
346 	AutoLocker<BLocker> locker(fLock);
347 	fPendingEvents.Add(event);
348 }
349 
350 
351 void
352 CliContext::_PrepareToWaitForEvents(uint32 eventMask)
353 {
354 	// Set the events we're going to wait for -- always wait for "quit".
355 	AutoLocker<BLocker> locker(fLock);
356 	fInputLoopWaitingForEvents = eventMask | EVENT_QUIT;
357 	fEventsOccurred = fTerminating ? EVENT_QUIT : 0;
358 }
359 
360 
361 uint32
362 CliContext::_WaitForEvents()
363 {
364 	AutoLocker<BLocker> locker(fLock);
365 
366 	if (fEventsOccurred == 0) {
367 		sem_id blockingSemaphore = fBlockingSemaphore;
368 		fInputLoopWaiting = true;
369 
370 		locker.Unlock();
371 
372 		while (acquire_sem(blockingSemaphore) == B_INTERRUPTED) {
373 		}
374 
375 		locker.Lock();
376 	}
377 
378 	uint32 events = fEventsOccurred;
379 	fEventsOccurred = 0;
380 	return events;
381 }
382 
383 
384 void
385 CliContext::_SignalInputLoop(uint32 events)
386 {
387 	AutoLocker<BLocker> locker(fLock);
388 
389 	if ((fInputLoopWaitingForEvents & events) == 0)
390 		return;
391 
392 	fEventsOccurred = fInputLoopWaitingForEvents & events;
393 	fInputLoopWaitingForEvents = 0;
394 
395 	if (fInputLoopWaiting) {
396 		fInputLoopWaiting = false;
397 		release_sem(fBlockingSemaphore);
398 	}
399 }
400 
401 
402 /*static*/ const char*
403 CliContext::_GetPrompt(EditLine* editLine)
404 {
405 	return sCurrentContext != NULL ? sCurrentContext->fPrompt : NULL;
406 }
407