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