1/*
2 * Copyright 2011-2012, Rene Gollent, rene@gollent.com.
3 * Copyright 2012, Ingo Weinhold, ingo_weinhold@gmx.de.
4 * Distributed under the terms of the MIT License.
5 */
6
7
8#include "CommandLineUserInterface.h"
9
10#include <stdio.h>
11
12#include <algorithm>
13
14#include <ArgumentVector.h>
15#include <AutoDeleter.h>
16#include <Referenceable.h>
17
18#include "CliContext.h"
19#include "CliContinueCommand.h"
20#include "CliDebugReportCommand.h"
21#include "CliDumpMemoryCommand.h"
22#include "CliPrintVariableCommand.h"
23#include "CliQuitCommand.h"
24#include "CliStackFrameCommand.h"
25#include "CliStackTraceCommand.h"
26#include "CliStopCommand.h"
27#include "CliThreadCommand.h"
28#include "CliThreadsCommand.h"
29#include "CliVariablesCommand.h"
30
31
32static const char* kDebuggerPrompt = "debugger> ";
33
34
35// #pragma mark - CommandEntry
36
37
38struct CommandLineUserInterface::CommandEntry {
39	CommandEntry(const BString& name, CliCommand* command)
40		:
41		fName(name),
42		fCommand(command)
43	{
44	}
45
46	const BString& Name() const
47	{
48		return fName;
49	}
50
51	CliCommand* Command() const
52	{
53		return fCommand.Get();
54	}
55
56private:
57	BString					fName;
58	BReference<CliCommand>	fCommand;
59};
60
61
62// #pragma mark - HelpCommand
63
64
65struct CommandLineUserInterface::HelpCommand : CliCommand {
66	HelpCommand(CommandLineUserInterface* userInterface)
67		:
68		CliCommand("print help for a command or a list of all commands",
69			"%s [ <command> ]\n"
70			"Prints help for command <command>, if given, or a list of all "
71				"commands\n"
72			"otherwise."),
73		fUserInterface(userInterface)
74	{
75	}
76
77	virtual void Execute(int argc, const char* const* argv, CliContext& context)
78	{
79		if (argc > 2) {
80			PrintUsage(argv[0]);
81			return;
82		}
83
84		fUserInterface->_PrintHelp(argc == 2 ? argv[1] : NULL);
85	}
86
87private:
88	CommandLineUserInterface* fUserInterface;
89};
90
91
92// #pragma mark - CommandLineUserInterface
93
94
95CommandLineUserInterface::CommandLineUserInterface(bool saveReport,
96	const char* reportPath)
97	:
98	fCommands(20, true),
99	fReportPath(reportPath),
100	fSaveReport(saveReport),
101	fShowSemaphore(-1),
102	fShown(false),
103	fTerminating(false)
104{
105}
106
107
108CommandLineUserInterface::~CommandLineUserInterface()
109{
110	if (fShowSemaphore >= 0)
111		delete_sem(fShowSemaphore);
112}
113
114
115const char*
116CommandLineUserInterface::ID() const
117{
118	return "BasicCommandLineUserInterface";
119}
120
121
122status_t
123CommandLineUserInterface::Init(Team* team, UserInterfaceListener* listener)
124{
125	status_t error = fContext.Init(team, listener);
126	if (error != B_OK)
127		return error;
128
129	error = _RegisterCommands();
130	if (error != B_OK)
131		return error;
132
133	fShowSemaphore = create_sem(0, "show CLI");
134	if (fShowSemaphore < 0)
135		return fShowSemaphore;
136
137	team->AddListener(this);
138
139	return B_OK;
140}
141
142
143void
144CommandLineUserInterface::Show()
145{
146	fShown = true;
147	release_sem(fShowSemaphore);
148}
149
150
151void
152CommandLineUserInterface::Terminate()
153{
154	fTerminating = true;
155
156	if (fShown) {
157		fContext.Terminating();
158
159		// Wait for input loop to finish.
160		while (acquire_sem(fShowSemaphore) == B_INTERRUPTED) {
161		}
162	} else {
163		// The main thread will still be blocked in Run(). Unblock it.
164		delete_sem(fShowSemaphore);
165		fShowSemaphore = -1;
166	}
167
168	fContext.Cleanup();
169}
170
171
172status_t
173CommandLineUserInterface::LoadSettings(const TeamUiSettings* settings)
174{
175	return B_OK;
176}
177
178
179status_t
180CommandLineUserInterface::SaveSettings(TeamUiSettings*& settings) const
181{
182	return B_OK;
183}
184
185
186void
187CommandLineUserInterface::NotifyUser(const char* title, const char* message,
188	user_notification_type type)
189{
190}
191
192
193int32
194CommandLineUserInterface::SynchronouslyAskUser(const char* title,
195	const char* message, const char* choice1, const char* choice2,
196	const char* choice3)
197{
198	return -1;
199}
200
201
202void
203CommandLineUserInterface::Run()
204{
205	// Wait for the Show() semaphore to be released.
206	status_t error;
207	do {
208		error = acquire_sem(fShowSemaphore);
209	} while (error == B_INTERRUPTED);
210
211	if (error != B_OK)
212		return;
213
214	if (!fSaveReport) {
215		_InputLoop();
216		// Release the Show() semaphore to signal Terminate().
217		release_sem(fShowSemaphore);
218	} else {
219		ArgumentVector args;
220		char buffer[256];
221		const char* parseErrorLocation;
222		snprintf(buffer, sizeof(buffer), "save-report %s",
223			fReportPath != NULL ? fReportPath : "");
224		args.Parse(buffer, &parseErrorLocation);
225		_ExecuteCommand(args.ArgumentCount(), args.Arguments());
226	}
227}
228
229
230void
231CommandLineUserInterface::DebugReportChanged(
232	const Team::DebugReportEvent& event)
233{
234	printf("Successfully saved debug report to %s\n",
235		event.GetReportPath());
236
237	if (fSaveReport) {
238		fContext.QuitSession(true);
239		// Release the Show() semaphore to signal Terminate().
240		release_sem(fShowSemaphore);
241	}
242}
243
244
245/*static*/ status_t
246CommandLineUserInterface::_InputLoopEntry(void* data)
247{
248	return ((CommandLineUserInterface*)data)->_InputLoop();
249}
250
251
252status_t
253CommandLineUserInterface::_InputLoop()
254{
255	thread_id currentThread = -1;
256
257	while (!fTerminating) {
258		// Wait for a thread or Ctrl-C.
259		fContext.WaitForThreadOrUser();
260		if (fContext.IsTerminating())
261			break;
262
263		// Print the active thread, if it changed.
264		if (fContext.CurrentThreadID() != currentThread) {
265			fContext.PrintCurrentThread();
266			currentThread = fContext.CurrentThreadID();
267		}
268
269		// read a command line
270		const char* line = fContext.PromptUser(kDebuggerPrompt);
271		if (line == NULL)
272			break;
273
274		// parse the command line
275		ArgumentVector args;
276		const char* parseErrorLocation;
277		switch (args.Parse(line, &parseErrorLocation)) {
278			case ArgumentVector::NO_ERROR:
279				break;
280			case ArgumentVector::NO_MEMORY:
281				printf("Insufficient memory parsing the command line.\n");
282				continue;
283			case ArgumentVector::UNTERMINATED_QUOTED_STRING:
284				printf("Parse error: Unterminated quoted string starting at "
285					"character %zu.\n", parseErrorLocation - line + 1);
286				continue;
287			case ArgumentVector::TRAILING_BACKSPACE:
288				printf("Parse error: trailing backspace.\n");
289				continue;
290		}
291
292		if (args.ArgumentCount() == 0)
293			continue;
294
295		// add line to history
296		fContext.AddLineToInputHistory(line);
297
298		// execute command
299		_ExecuteCommand(args.ArgumentCount(), args.Arguments());
300	}
301
302	return B_OK;
303}
304
305
306status_t
307CommandLineUserInterface::_RegisterCommands()
308{
309	if (_RegisterCommand("bt sc", new(std::nothrow) CliStackTraceCommand)
310		&& _RegisterCommand("continue", new(std::nothrow) CliContinueCommand)
311		&& _RegisterCommand("db ds dw dl string", new(std::nothrow)
312			CliDumpMemoryCommand)
313		&& _RegisterCommand("frame", new(std::nothrow) CliStackFrameCommand)
314		&& _RegisterCommand("help", new(std::nothrow) HelpCommand(this))
315		&& _RegisterCommand("print", new(std::nothrow) CliPrintVariableCommand)
316		&& _RegisterCommand("quit", new(std::nothrow) CliQuitCommand)
317		&& _RegisterCommand("save-report",
318			new(std::nothrow) CliDebugReportCommand)
319		&& _RegisterCommand("stop", new(std::nothrow) CliStopCommand)
320		&& _RegisterCommand("thread", new(std::nothrow) CliThreadCommand)
321		&& _RegisterCommand("threads", new(std::nothrow) CliThreadsCommand)
322		&& _RegisterCommand("variables",
323			new(std::nothrow) CliVariablesCommand)) {
324		fCommands.SortItems(&_CompareCommandEntries);
325		return B_OK;
326	}
327
328	return B_NO_MEMORY;
329}
330
331
332bool
333CommandLineUserInterface::_RegisterCommand(const BString& name,
334	CliCommand* command)
335{
336	BReference<CliCommand> commandReference(command, true);
337	if (name.IsEmpty() || command == NULL)
338		return false;
339
340	BString nextName;
341	int32 startIndex = 0;
342	int32 spaceIndex;
343	do {
344		spaceIndex = name.FindFirst(' ', startIndex);
345		if (spaceIndex == B_ERROR)
346			spaceIndex = name.Length();
347		name.CopyInto(nextName, startIndex, spaceIndex - startIndex);
348
349		CommandEntry* entry = new(std::nothrow) CommandEntry(nextName,
350			command);
351		if (entry == NULL || !fCommands.AddItem(entry)) {
352			delete entry;
353			return false;
354		}
355		startIndex = spaceIndex + 1;
356	} while (startIndex < name.Length());
357
358	return true;
359}
360
361
362void
363CommandLineUserInterface::_ExecuteCommand(int argc, const char* const* argv)
364{
365	CommandEntry* commandEntry = _FindCommand(argv[0]);
366	if (commandEntry != NULL)
367		commandEntry->Command()->Execute(argc, argv, fContext);
368}
369
370
371CommandLineUserInterface::CommandEntry*
372CommandLineUserInterface::_FindCommand(const char* commandName)
373{
374	size_t commandNameLength = strlen(commandName);
375
376	// try to find an exact match first
377	CommandEntry* commandEntry = NULL;
378	for (int32 i = 0; CommandEntry* entry = fCommands.ItemAt(i); i++) {
379		if (entry->Name() == commandName) {
380			commandEntry = entry;
381			break;
382		}
383	}
384
385	// If nothing found yet, try partial matches, but only, if they are
386	// unambiguous.
387	if (commandEntry == NULL) {
388		for (int32 i = 0; CommandEntry* entry = fCommands.ItemAt(i); i++) {
389			if (entry->Name().Compare(commandName, commandNameLength) == 0) {
390				if (commandEntry != NULL) {
391					printf("Error: Ambiguous command \"%s\".\n", commandName);
392					return NULL;
393				}
394
395				commandEntry = entry;
396			}
397		}
398	}
399
400	if (commandEntry == NULL) {
401		printf("Error: Unknown command \"%s\".\n", commandName);
402		return NULL;
403	}
404
405	return commandEntry;
406}
407
408
409void
410CommandLineUserInterface::_PrintHelp(const char* commandName)
411{
412	// If a command name is given, print the usage for that one.
413	if (commandName != NULL) {
414		CommandEntry* commandEntry = _FindCommand(commandName);
415		if (commandEntry != NULL)
416			commandEntry->Command()->PrintUsage(commandEntry->Name().String());
417		return;
418	}
419
420	// No command name given -- print a list of all commands.
421
422	// determine longest command name
423	int32 longestCommandName = 0;
424	for (int32 i = 0; CommandEntry* entry = fCommands.ItemAt(i); i++) {
425		longestCommandName
426			= std::max(longestCommandName, entry->Name().Length());
427	}
428
429	// print the command list
430	for (int32 i = 0; CommandEntry* entry = fCommands.ItemAt(i); i++) {
431		printf("%*s  -  %s\n", (int)longestCommandName, entry->Name().String(),
432			entry->Command()->Summary());
433	}
434}
435
436
437/*static */
438int
439CommandLineUserInterface::_CompareCommandEntries(const CommandEntry* command1,
440	const CommandEntry* command2)
441{
442	return ::Compare(command1->Name(), command2->Name());
443}
444
445
446