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