diff options
Diffstat (limited to 'tools/driver/IOChannel.cpp')
-rw-r--r-- | tools/driver/IOChannel.cpp | 647 |
1 files changed, 647 insertions, 0 deletions
diff --git a/tools/driver/IOChannel.cpp b/tools/driver/IOChannel.cpp new file mode 100644 index 000000000000..7adf2e4e85e9 --- /dev/null +++ b/tools/driver/IOChannel.cpp @@ -0,0 +1,647 @@ +//===-- IOChannel.cpp -------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "IOChannel.h" + +#include <map> + +#include "lldb/API/SBCommandInterpreter.h" +#include "lldb/API/SBDebugger.h" +#include "lldb/API/SBError.h" +#include "lldb/API/SBEvent.h" +#include "lldb/API/SBFileSpec.h" +#include "lldb/API/SBHostOS.h" +#include "lldb/API/SBListener.h" +#include "lldb/API/SBStringList.h" + +#include <string.h> +#include <limits.h> + +using namespace lldb; + +typedef std::map<EditLine *, std::string> PromptMap; +const char *g_default_prompt = "(lldb) "; +PromptMap g_prompt_map; + +// Printing the following string causes libedit to back up to the beginning of the line & blank it out. +const char undo_prompt_string[4] = { (char) 13, (char) 27, (char) 91, (char) 75}; + +static const char* +el_prompt(EditLine *el) +{ + PromptMap::const_iterator pos = g_prompt_map.find (el); + if (pos == g_prompt_map.end()) + return g_default_prompt; + return pos->second.c_str(); +} + +const char * +IOChannel::GetPrompt () +{ + PromptMap::const_iterator pos = g_prompt_map.find (m_edit_line); + if (pos == g_prompt_map.end()) + return g_default_prompt; + return pos->second.c_str(); +} + +bool +IOChannel::EditLineHasCharacters () +{ + const LineInfo *line_info = el_line(m_edit_line); + if (line_info) + { + // Sometimes we get called after the user has submitted the line, but before editline has + // cleared the buffer. In that case the cursor will be pointing at the newline. That's + // equivalent to having no characters on the line, since it has already been submitted. + if (*line_info->cursor == '\n') + return false; + else + return line_info->cursor != line_info->buffer; + } + else + return false; +} + + +void +IOChannel::EraseCharsBeforeCursor () +{ + const LineInfo *line_info = el_line(m_edit_line); + el_deletestr(m_edit_line, line_info->cursor - line_info->buffer); +} + +unsigned char +IOChannel::ElCompletionFn (EditLine *e, int ch) +{ + IOChannel *io_channel; + if (el_get(e, EL_CLIENTDATA, &io_channel) == 0) + { + return io_channel->HandleCompletion (e, ch); + } + else + { + return CC_ERROR; + } +} + +void +IOChannel::ElResize() +{ + el_resize(m_edit_line); +} + +unsigned char +IOChannel::HandleCompletion (EditLine *e, int ch) +{ + assert (e == m_edit_line); + + const LineInfo *line_info = el_line(m_edit_line); + SBStringList completions; + int page_size = 40; + + int num_completions = m_driver->GetDebugger().GetCommandInterpreter().HandleCompletion (line_info->buffer, + line_info->cursor, + line_info->lastchar, + 0, + -1, + completions); + + if (num_completions == -1) + { + el_insertstr (m_edit_line, m_completion_key); + return CC_REDISPLAY; + } + else if (num_completions == -2) + { + el_deletestr (m_edit_line, line_info->cursor - line_info->buffer); + el_insertstr (m_edit_line, completions.GetStringAtIndex(0)); + return CC_REDISPLAY; + } + + // If we get a longer match display that first. + const char *completion_str = completions.GetStringAtIndex(0); + if (completion_str != NULL && *completion_str != '\0') + { + el_insertstr (m_edit_line, completion_str); + return CC_REDISPLAY; + } + + if (num_completions > 1) + { + const char *comment = "\nAvailable completions:"; + + int num_elements = num_completions + 1; + OutWrite(comment, strlen (comment), NO_ASYNC); + if (num_completions < page_size) + { + for (int i = 1; i < num_elements; i++) + { + completion_str = completions.GetStringAtIndex(i); + OutWrite("\n\t", 2, NO_ASYNC); + OutWrite(completion_str, strlen (completion_str), NO_ASYNC); + } + OutWrite ("\n", 1, NO_ASYNC); + } + else + { + int cur_pos = 1; + char reply; + int got_char; + while (cur_pos < num_elements) + { + int endpoint = cur_pos + page_size; + if (endpoint > num_elements) + endpoint = num_elements; + for (; cur_pos < endpoint; cur_pos++) + { + completion_str = completions.GetStringAtIndex(cur_pos); + OutWrite("\n\t", 2, NO_ASYNC); + OutWrite(completion_str, strlen (completion_str), NO_ASYNC); + } + + if (cur_pos >= num_elements) + { + OutWrite("\n", 1, NO_ASYNC); + break; + } + + OutWrite("\nMore (Y/n/a): ", strlen ("\nMore (Y/n/a): "), NO_ASYNC); + reply = 'n'; + got_char = el_getc(m_edit_line, &reply); + if (got_char == -1 || reply == 'n') + break; + if (reply == 'a') + page_size = num_elements - cur_pos; + } + } + + } + + if (num_completions == 0) + return CC_REFRESH_BEEP; + else + return CC_REDISPLAY; +} + +IOChannel::IOChannel +( + FILE *editline_in, + FILE *editline_out, + FILE *out, + FILE *err, + Driver *driver +) : + SBBroadcaster ("IOChannel"), + m_output_mutex (), + m_enter_elgets_time (), + m_driver (driver), + m_read_thread (LLDB_INVALID_HOST_THREAD), + m_read_thread_should_exit (false), + m_out_file (out), + m_err_file (err), + m_command_queue (), + m_completion_key ("\t"), + m_edit_line (::el_init (SBHostOS::GetProgramFileSpec().GetFilename(), editline_in, editline_out, editline_out)), + m_history (history_init()), + m_history_event(), + m_getting_command (false), + m_expecting_prompt (false), + m_prompt_str (), + m_refresh_request_pending (false) +{ + assert (m_edit_line); + ::el_set (m_edit_line, EL_PROMPT, el_prompt); + ::el_set (m_edit_line, EL_EDITOR, "emacs"); + ::el_set (m_edit_line, EL_HIST, history, m_history); + + el_set (m_edit_line, EL_ADDFN, "lldb_complete", + "LLDB completion function", + IOChannel::ElCompletionFn); + el_set (m_edit_line, EL_BIND, m_completion_key, "lldb_complete", NULL); + el_set (m_edit_line, EL_BIND, "^r", "em-inc-search-prev", NULL); // Cycle through backwards search, entering string + el_set (m_edit_line, EL_BIND, "^w", "ed-delete-prev-word", NULL); // Delete previous word, behave like bash does. + el_set (m_edit_line, EL_BIND, "\e[3~", "ed-delete-next-char", NULL); // Fix the delete key. + el_set (m_edit_line, EL_CLIENTDATA, this); + + // Source $PWD/.editrc then $HOME/.editrc + ::el_source (m_edit_line, NULL); + + assert (m_history); + ::history (m_history, &m_history_event, H_SETSIZE, 800); + ::history (m_history, &m_history_event, H_SETUNIQUE, 1); + // Load history + HistorySaveLoad (false); + + // Set up mutex to make sure OutErr, OutWrite and RefreshPrompt do not interfere + // with each other when writing. + + int error; + ::pthread_mutexattr_t attr; + error = ::pthread_mutexattr_init (&attr); + assert (error == 0); + error = ::pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE); + assert (error == 0); + error = ::pthread_mutex_init (&m_output_mutex, &attr); + assert (error == 0); + error = ::pthread_mutexattr_destroy (&attr); + assert (error == 0); + + // Initialize time that ::el_gets was last called. + + m_enter_elgets_time.tv_sec = 0; + m_enter_elgets_time.tv_usec = 0; +} + +IOChannel::~IOChannel () +{ + // Save history + HistorySaveLoad (true); + + if (m_history != NULL) + { + ::history_end (m_history); + m_history = NULL; + } + + if (m_edit_line != NULL) + { + ::el_end (m_edit_line); + m_edit_line = NULL; + } + + ::pthread_mutex_destroy (&m_output_mutex); +} + +void +IOChannel::HistorySaveLoad (bool save) +{ + if (m_history != NULL) + { + char history_path[PATH_MAX]; + ::snprintf (history_path, sizeof(history_path), "~/.%s-history", SBHostOS::GetProgramFileSpec().GetFilename()); + if ((size_t)SBFileSpec::ResolvePath (history_path, history_path, sizeof(history_path)) < sizeof(history_path) - 1) + { + const char *path_ptr = history_path; + if (save) + ::history (m_history, &m_history_event, H_SAVE, path_ptr); + else + ::history (m_history, &m_history_event, H_LOAD, path_ptr); + } + } +} + +void +IOChannel::LibeditOutputBytesReceived (void *baton, const void *src, size_t src_len) +{ + // Make this a member variable. + // static std::string prompt_str; + IOChannel *io_channel = (IOChannel *) baton; + IOLocker locker (io_channel->m_output_mutex); + const char *bytes = (const char *) src; + + if (io_channel->IsGettingCommand() && io_channel->m_expecting_prompt) + { + io_channel->m_prompt_str.append (bytes, src_len); + // Log this to make sure the prompt is really what you think it is. + if (io_channel->m_prompt_str.find (el_prompt(io_channel->m_edit_line)) == 0) + { + io_channel->m_expecting_prompt = false; + io_channel->m_refresh_request_pending = false; + io_channel->OutWrite (io_channel->m_prompt_str.c_str(), + io_channel->m_prompt_str.size(), NO_ASYNC); + io_channel->m_prompt_str.clear(); + } + } + else + { + if (io_channel->m_prompt_str.size() > 0) + io_channel->m_prompt_str.clear(); + std::string tmp_str (bytes, src_len); + if (tmp_str.find (el_prompt (io_channel->m_edit_line)) == 0) + io_channel->m_refresh_request_pending = false; + io_channel->OutWrite (bytes, src_len, NO_ASYNC); + } +} + +IOChannel::LibeditGetInputResult +IOChannel::LibeditGetInput (std::string &new_line) +{ + IOChannel::LibeditGetInputResult retval = IOChannel::eLibeditGetInputResultUnknown; + if (m_edit_line != NULL) + { + int line_len = 0; + + // Set boolean indicating whether or not el_gets is trying to get input (i.e. whether or not to attempt + // to refresh the prompt after writing data). + SetGettingCommand (true); + m_expecting_prompt = true; + + // Call el_gets to prompt the user and read the user's input. + const char *line = ::el_gets (m_edit_line, &line_len); + + // Re-set the boolean indicating whether or not el_gets is trying to get input. + SetGettingCommand (false); + + if (line) + { + retval = IOChannel::eLibeditGetInputValid; + // strip any newlines off the end of the string... + while (line_len > 0 && (line[line_len - 1] == '\n' || line[line_len - 1] == '\r')) + --line_len; + if (line_len > 0) + { + ::history (m_history, &m_history_event, H_ENTER, line); + new_line.assign (line, line_len); // Omit the newline + } + else + { + retval = IOChannel::eLibeditGetInputEmpty; + // Someone just hit ENTER, return the empty string + new_line.clear(); + } + // Return true to indicate success even if a string is empty + return retval; + } + else + { + retval = (line_len == 0 ? IOChannel::eLibeditGetInputEOF : IOChannel::eLibeditGetInputResultError); + } + } + // Return false to indicate failure. This can happen when the file handle + // is closed (EOF). + new_line.clear(); + return retval; +} + +void * +IOChannel::IOReadThread (void *ptr) +{ + IOChannel *myself = static_cast<IOChannel *> (ptr); + myself->Run(); + return NULL; +} + +void +IOChannel::Run () +{ + SBListener listener("IOChannel::Run"); + std::string new_line; + + SBBroadcaster interpreter_broadcaster (m_driver->GetDebugger().GetCommandInterpreter().GetBroadcaster()); + listener.StartListeningForEvents (interpreter_broadcaster, + SBCommandInterpreter::eBroadcastBitResetPrompt | + SBCommandInterpreter::eBroadcastBitThreadShouldExit | + SBCommandInterpreter::eBroadcastBitQuitCommandReceived); + + listener.StartListeningForEvents (*this, + IOChannel::eBroadcastBitThreadShouldExit); + + listener.StartListeningForEvents (*m_driver, + Driver::eBroadcastBitReadyForInput | + Driver::eBroadcastBitThreadShouldExit); + + // Let anyone know that the IO channel is up and listening and ready for events + BroadcastEventByType (eBroadcastBitThreadDidStart); + bool done = false; + while (!done) + { + SBEvent event; + + listener.WaitForEvent (UINT32_MAX, event); + if (!event.IsValid()) + continue; + + const uint32_t event_type = event.GetType(); + + if (event.GetBroadcaster().IsValid()) + { + if (event.BroadcasterMatchesPtr (m_driver)) + { + if (event_type & Driver::eBroadcastBitReadyForInput) + { + std::string line; + + if (CommandQueueIsEmpty()) + { + IOChannel::LibeditGetInputResult getline_result = LibeditGetInput(line); + if (getline_result == IOChannel::eLibeditGetInputEOF) + { + // EOF occurred + // pretend that a quit was typed so the user gets a potential + // chance to confirm + line.assign("quit"); + } + else if (getline_result == IOChannel::eLibeditGetInputResultError || getline_result == IOChannel::eLibeditGetInputResultUnknown) + { + // some random error occurred, exit and don't ask because the state might be corrupt + done = true; + continue; + } + } + else + { + GetCommandFromQueue (line); + } + + // TO BE DONE: FIGURE OUT WHICH COMMANDS SHOULD NOT BE REPEATED IF USER PRESSES PLAIN 'RETURN' + // AND TAKE CARE OF THAT HERE. + + SBEvent line_event(IOChannel::eBroadcastBitHasUserInput, + line.c_str(), + line.size()); + BroadcastEvent (line_event); + } + else if (event_type & Driver::eBroadcastBitThreadShouldExit) + { + done = true; + continue; + } + } + else if (event.BroadcasterMatchesRef (interpreter_broadcaster)) + { + switch (event_type) + { + case SBCommandInterpreter::eBroadcastBitResetPrompt: + { + const char *new_prompt = SBEvent::GetCStringFromEvent (event); + if (new_prompt) + g_prompt_map[m_edit_line] = new_prompt; + } + break; + + case SBCommandInterpreter::eBroadcastBitThreadShouldExit: + case SBCommandInterpreter::eBroadcastBitQuitCommandReceived: + done = true; + break; + } + } + else if (event.BroadcasterMatchesPtr (this)) + { + if (event_type & IOChannel::eBroadcastBitThreadShouldExit) + { + done = true; + continue; + } + } + } + } + BroadcastEventByType (IOChannel::eBroadcastBitThreadDidExit); + m_driver = NULL; + m_read_thread = 0; +} + +bool +IOChannel::Start () +{ + if (IS_VALID_LLDB_HOST_THREAD(m_read_thread)) + return true; + + m_read_thread = SBHostOS::ThreadCreate ("<lldb.driver.commandline_io>", IOChannel::IOReadThread, this, + NULL); + + return (IS_VALID_LLDB_HOST_THREAD(m_read_thread)); +} + +bool +IOChannel::Stop () +{ + if (!IS_VALID_LLDB_HOST_THREAD(m_read_thread)) + return true; + + BroadcastEventByType (eBroadcastBitThreadShouldExit); + + // Don't call Host::ThreadCancel since el_gets won't respond to this + // function call -- the thread will just die and all local variables in + // IOChannel::Run() won't get destructed down which is bad since there is + // a local listener holding onto broadcasters... To ensure proper shutdown, + // a ^D (control-D) sequence (0x04) should be written to other end of the + // the "in" file handle that was passed into the contructor as closing the + // file handle doesn't seem to make el_gets() exit.... + return SBHostOS::ThreadJoin (m_read_thread, NULL, NULL); +} + +void +IOChannel::RefreshPrompt () +{ + // If we are not in the middle of getting input from the user, there is no need to + // refresh the prompt. + IOLocker locker (m_output_mutex); + if (! IsGettingCommand()) + return; + + // If we haven't finished writing the prompt, there's no need to refresh it. + if (m_expecting_prompt) + return; + + if (m_refresh_request_pending) + return; + + ::el_set (m_edit_line, EL_REFRESH); + m_refresh_request_pending = true; +} + +void +IOChannel::OutWrite (const char *buffer, size_t len, bool asynchronous) +{ + if (len == 0 || buffer == NULL) + return; + + // We're in the process of exiting -- IOChannel::Run() has already completed + // and set m_driver to NULL - it is time for us to leave now. We might not + // print the final ^D to stdout in this case. We need to do some re-work on + // how the I/O streams are managed at some point. + if (m_driver == NULL) + { + return; + } + + // Use the mutex to make sure OutWrite and ErrWrite do not interfere with each other's output. + IOLocker locker (m_output_mutex); + if (m_driver->EditlineReaderIsTop() && asynchronous) + ::fwrite (undo_prompt_string, 1, 4, m_out_file); + ::fwrite (buffer, 1, len, m_out_file); + if (asynchronous) + m_driver->GetDebugger().NotifyTopInputReader (eInputReaderAsynchronousOutputWritten); +} + +void +IOChannel::ErrWrite (const char *buffer, size_t len, bool asynchronous) +{ + if (len == 0 || buffer == NULL) + return; + + // Use the mutex to make sure OutWrite and ErrWrite do not interfere with each other's output. + IOLocker locker (m_output_mutex); + if (asynchronous) + ::fwrite (undo_prompt_string, 1, 4, m_err_file); + ::fwrite (buffer, 1, len, m_err_file); + if (asynchronous) + m_driver->GetDebugger().NotifyTopInputReader (eInputReaderAsynchronousOutputWritten); +} + +void +IOChannel::AddCommandToQueue (const char *command) +{ + m_command_queue.push (std::string(command)); +} + +bool +IOChannel::GetCommandFromQueue (std::string &cmd) +{ + if (m_command_queue.empty()) + return false; + cmd.swap(m_command_queue.front()); + m_command_queue.pop (); + return true; +} + +int +IOChannel::CommandQueueSize () const +{ + return m_command_queue.size(); +} + +void +IOChannel::ClearCommandQueue () +{ + while (!m_command_queue.empty()) + m_command_queue.pop(); +} + +bool +IOChannel::CommandQueueIsEmpty () const +{ + return m_command_queue.empty(); +} + +bool +IOChannel::IsGettingCommand () const +{ + return m_getting_command; +} + +void +IOChannel::SetGettingCommand (bool new_value) +{ + m_getting_command = new_value; +} + +IOLocker::IOLocker (pthread_mutex_t &mutex) : + m_mutex_ptr (&mutex) +{ + if (m_mutex_ptr) + ::pthread_mutex_lock (m_mutex_ptr); + +} + +IOLocker::~IOLocker () +{ + if (m_mutex_ptr) + ::pthread_mutex_unlock (m_mutex_ptr); +} |