Files
clang-p2996/lldb/tools/driver/IOChannel.cpp
Richard Mitton 25b092835e Fixed a race condition where the prompt would randomly go missing.
el_gets was using fflush to output it's string, but because we have our own filter running on the piped pty output, fflush only causes the prompt to be written into the pipe, and does not cause the filter code to run immediately.

The simplest fix is to manually block and wait for all editline output to be processed.

This fixes PR 14637.

llvm-svn: 191392
2013-09-25 18:45:59 +00:00

686 lines
21 KiB
C++

//===-- 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_editline_out (editline_out),
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, "\033[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);
error = ::pthread_cond_init (&m_output_cond, NULL);
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_cond_destroy (&m_output_cond);
::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;
bool flush = false;
// See if we have a 'flush' syncronization point in there.
if (src_len > 0 && bytes[src_len-1] == 0)
{
src_len--;
flush = true;
}
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);
}
if (flush)
{
// Signal that we have finished all data up to the sync point.
IOLocker locker (io_channel->m_output_mutex);
io_channel->m_output_flushed = true;
pthread_cond_signal (&io_channel->m_output_cond);
}
}
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);
// Force the piped output from el_gets to finish processing.
// el_gets does an fflush internally, which is not sufficient here; it only
// writes the data into m_editline_out, but doesn't affect whether our worker
// thread will read that data yet. So we block here manually.
{
IOLocker locker (m_output_mutex);
m_output_flushed = false;
// Write a synchronization point into the stream, so we can guarantee
// LibeditOutputBytesReceived has processed everything up till that mark.
fputc (0, m_editline_out);
while (!m_output_flushed && pthread_cond_wait (&m_output_cond, &m_output_mutex))
{ /* wait */ }
}
// 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);
}