Added a new host function that allows us to run shell command and get the output from them along with the status and signal:

Error
Host::RunShellCommand (const char *command,
                       const char *working_dir,
                       int *status_ptr,
                       int *signo_ptr,
                       std::string *command_output_ptr,
                       uint32_t timeout_sec);

This will allow us to use this functionality in the host lldb_private::Platform, and also use it in our lldb-platform binary. It leverages the existing code in Host::LaunchProcess and ProcessLaunchInfo.

llvm-svn: 154730
This commit is contained in:
Greg Clayton 2012-04-14 01:42:46 +00:00
parent f5c87882a0
commit d1cf11a74d
7 changed files with 296 additions and 25 deletions

View File

@ -414,6 +414,14 @@ public:
static Error
LaunchProcess (ProcessLaunchInfo &launch_info);
static Error
RunShellCommand (const char *command, // Shouldn't be NULL
const char *working_dir, // Pass NULL to use the current working directory
int *status_ptr, // Pass NULL if you don't want the process exit status
int *signo_ptr, // Pass NULL if you don't want the signal that caused the process to exit
std::string *command_output, // Pass NULL if you don't want the command output
uint32_t timeout_sec); // Timeout in seconds to wait for shell program to finish
static lldb::DataBufferSP
GetAuxvData (lldb_private::Process *process);

View File

@ -733,7 +733,10 @@ public:
}
bool
ConvertArgumentsForLaunchingInShell (Error &error, bool localhost);
ConvertArgumentsForLaunchingInShell (Error &error,
bool localhost,
bool will_debug,
bool first_arg_is_full_shell_command);
void
SetMonitorProcessCallback (Host::MonitorChildProcessCallback callback,

View File

@ -870,6 +870,75 @@ private:
DISALLOW_COPY_AND_ASSIGN (CommandObjectPlatformProcess);
};
class CommandObjectPlatformShell : public CommandObject
{
public:
CommandObjectPlatformShell (CommandInterpreter &interpreter) :
CommandObject (interpreter,
"platform shell",
"Run a shell command on a the selected platform.",
"platform shell <shell-command>",
0)
{
}
virtual
~CommandObjectPlatformShell ()
{
}
virtual bool
Execute (Args& command,
CommandReturnObject &result)
{
return false;
}
virtual bool
WantsRawCommandString() { return true; }
bool
ExecuteRawCommandString (const char *raw_command_line, CommandReturnObject &result)
{
// TODO: Implement "Platform::RunShellCommand()" and switch over to using
// the current platform when it is in the interface.
const char *working_dir = NULL;
std::string output;
int status = -1;
int signo = -1;
Error error (Host::RunShellCommand (raw_command_line, working_dir, &status, &signo, &output, 10));
if (!output.empty())
result.GetOutputStream().PutCString(output.c_str());
if (status > 0)
{
if (signo > 0)
{
const char *signo_cstr = Host::GetSignalAsCString(signo);
if (signo_cstr)
result.GetOutputStream().Printf("error: command returned with status %i and signal %s\n", status, signo_cstr);
else
result.GetOutputStream().Printf("error: command returned with status %i and signal %i\n", status, signo);
}
else
result.GetOutputStream().Printf("error: command returned with status %i\n", status);
}
if (error.Fail())
{
result.AppendError(error.AsCString());
result.SetStatus (eReturnStatusFailed);
}
else
{
result.SetStatus (eReturnStatusSuccessFinishResult);
}
return true;
}
protected:
};
//----------------------------------------------------------------------
// CommandObjectPlatform constructor
//----------------------------------------------------------------------
@ -885,6 +954,7 @@ CommandObjectPlatform::CommandObjectPlatform(CommandInterpreter &interpreter) :
LoadSubCommand ("connect", CommandObjectSP (new CommandObjectPlatformConnect (interpreter)));
LoadSubCommand ("disconnect", CommandObjectSP (new CommandObjectPlatformDisconnect (interpreter)));
LoadSubCommand ("process", CommandObjectSP (new CommandObjectPlatformProcess (interpreter)));
LoadSubCommand ("shell", CommandObjectSP (new CommandObjectPlatformShell (interpreter)));
}

View File

@ -1239,6 +1239,164 @@ Host::GetDummyTarget (lldb_private::Debugger &debugger)
return dummy_target;
}
struct ShellInfo
{
ShellInfo () :
process_reaped (false),
can_delete (false),
pid (LLDB_INVALID_PROCESS_ID),
signo(-1),
status(-1)
{
}
lldb_private::Predicate<bool> process_reaped;
lldb_private::Predicate<bool> can_delete;
lldb::pid_t pid;
int signo;
int status;
};
static bool
MonitorShellCommand (void *callback_baton,
lldb::pid_t pid,
bool exited, // True if the process did exit
int signo, // Zero for no signal
int status) // Exit value of process if signal is zero
{
ShellInfo *shell_info = (ShellInfo *)callback_baton;
shell_info->pid = pid;
shell_info->signo = signo;
shell_info->status = status;
// Let the thread running Host::RunShellCommand() know that the process
// exited and that ShellInfo has been filled in by broadcasting to it
shell_info->process_reaped.SetValue(1, eBroadcastAlways);
// Now wait for a handshake back from that thread running Host::RunShellCommand
// so we know that we can delete shell_info_ptr
shell_info->can_delete.WaitForValueEqualTo(true);
// Sleep a bit to allow the shell_info->can_delete.SetValue() to complete...
usleep(1000);
// Now delete the shell info that was passed into this function
delete shell_info;
return true;
}
Error
Host::RunShellCommand (const char *command,
const char *working_dir,
int *status_ptr,
int *signo_ptr,
std::string *command_output_ptr,
uint32_t timeout_sec)
{
Error error;
ProcessLaunchInfo launch_info;
launch_info.SetShell("/bin/bash");
launch_info.GetArguments().AppendArgument(command);
const bool localhost = true;
const bool will_debug = false;
const bool first_arg_is_full_shell_command = true;
launch_info.ConvertArgumentsForLaunchingInShell (error,
localhost,
will_debug,
first_arg_is_full_shell_command);
if (working_dir)
launch_info.SetWorkingDirectory(working_dir);
char output_file_path_buffer[L_tmpnam];
const char *output_file_path = NULL;
if (command_output_ptr)
{
// Create a temporary file to get the stdout/stderr and redirect the
// output of the command into this file. We will later read this file
// if all goes well and fill the data into "command_output_ptr"
output_file_path = ::tmpnam(output_file_path_buffer);
launch_info.AppendSuppressFileAction (STDIN_FILENO, true, false);
launch_info.AppendOpenFileAction(STDOUT_FILENO, output_file_path, false, true);
launch_info.AppendDuplicateFileAction(STDERR_FILENO, STDOUT_FILENO);
}
else
{
launch_info.AppendSuppressFileAction (STDIN_FILENO, true, false);
launch_info.AppendSuppressFileAction (STDOUT_FILENO, false, true);
launch_info.AppendSuppressFileAction (STDERR_FILENO, false, true);
}
// The process monitor callback will delete the 'shell_info_ptr' below...
std::auto_ptr<ShellInfo> shell_info_ap (new ShellInfo());
const bool monitor_signals = false;
launch_info.SetMonitorProcessCallback(MonitorShellCommand, shell_info_ap.get(), monitor_signals);
error = LaunchProcess (launch_info);
const lldb::pid_t pid = launch_info.GetProcessID();
if (pid != LLDB_INVALID_PROCESS_ID)
{
// The process successfully launched, so we can defer ownership of
// "shell_info" to the MonitorShellCommand callback function that will
// get called when the process dies. We release the std::auto_ptr as it
// doesn't need to delete the ShellInfo anymore.
ShellInfo *shell_info = shell_info_ap.release();
TimeValue timeout_time(TimeValue::Now());
timeout_time.OffsetWithSeconds(timeout_sec);
bool timed_out = false;
shell_info->process_reaped.WaitForValueEqualTo(true, &timeout_time, &timed_out);
if (timed_out)
{
error.SetErrorString("timed out waiting for shell command to complete");
// Kill the process since it didn't complete withint the timeout specified
::kill (pid, SIGKILL);
// Wait for the monitor callback to get the message
timeout_time = TimeValue::Now();
timeout_time.OffsetWithSeconds(1);
timed_out = false;
shell_info->process_reaped.WaitForValueEqualTo(true, &timeout_time, &timed_out);
}
else
{
if (status_ptr)
*status_ptr = shell_info->status;
if (signo_ptr)
*signo_ptr = shell_info->signo;
if (command_output_ptr)
{
command_output_ptr->clear();
FileSpec file_spec(output_file_path, File::eOpenOptionRead);
uint64_t file_size = file_spec.GetByteSize();
if (file_size > 0)
{
if (file_size > command_output_ptr->max_size())
{
error.SetErrorStringWithFormat("shell command output is too large to fit into a std::string");
}
else
{
command_output_ptr->resize(file_size);
file_spec.ReadFileContents(0, &((*command_output_ptr)[0]), command_output_ptr->size(), &error);
}
}
}
}
shell_info->can_delete.SetValue(true, eBroadcastAlways);
}
else
{
error.SetErrorString("failed to get process ID");
}
if (output_file_path)
::unlink (output_file_path);
// Handshake with the monitor thread, or just let it know in advance that
// it can delete "shell_info" in case we timed out and were not able to kill
// the process...
return error;
}
#if !defined (__APPLE__)
bool
Host::OpenFileInExternalEditor (const FileSpec &file_spec, uint32_t line_no)

View File

@ -330,7 +330,12 @@ PlatformLinux::LaunchProcess (ProcessLaunchInfo &launch_info)
if (launch_info.GetFlags().Test (eLaunchFlagLaunchInShell))
{
const bool is_localhost = true;
if (!launch_info.ConvertArgumentsForLaunchingInShell (error, is_localhost))
const bool will_debug = launch_info.GetFlags().Test(eLaunchFlagDebug);
const bool first_arg_is_full_shell_command = false;
if (!launch_info.ConvertArgumentsForLaunchingInShell (error,
is_localhost,
will_debug,
first_arg_is_full_shell_command))
return error;
}
error = Platform::LaunchProcess (launch_info);

View File

@ -574,7 +574,12 @@ Platform::LaunchProcess (ProcessLaunchInfo &launch_info)
if (launch_info.GetFlags().Test (eLaunchFlagLaunchInShell))
{
const bool is_localhost = true;
if (!launch_info.ConvertArgumentsForLaunchingInShell (error, is_localhost))
const bool will_debug = launch_info.GetFlags().Test(eLaunchFlagDebug);
const bool first_arg_is_full_shell_command = false;
if (!launch_info.ConvertArgumentsForLaunchingInShell (error,
is_localhost,
will_debug,
first_arg_is_full_shell_command))
return error;
}

View File

@ -303,7 +303,10 @@ ProcessLaunchInfo::FinalizeFileActions (Target *target, bool default_to_use_pty)
bool
ProcessLaunchInfo::ConvertArgumentsForLaunchingInShell (Error &error, bool localhost)
ProcessLaunchInfo::ConvertArgumentsForLaunchingInShell (Error &error,
bool localhost,
bool will_debug,
bool first_arg_is_full_shell_command)
{
error.Clear();
@ -334,37 +337,56 @@ ProcessLaunchInfo::ConvertArgumentsForLaunchingInShell (Error &error, bool local
Args shell_arguments;
std::string safe_arg;
shell_arguments.AppendArgument (shell_executable);
StreamString shell_command;
shell_arguments.AppendArgument ("-c");
shell_command.PutCString ("exec");
if (GetArchitecture().IsValid())
StreamString shell_command;
if (will_debug)
{
shell_command.Printf(" /usr/bin/arch -arch %s", GetArchitecture().GetArchitectureName());
// Set the resume count to 2:
// 1 - stop in shell
// 2 - stop in /usr/bin/arch
// 3 - then we will stop in our program
SetResumeCount(2);
}
else
{
// Set the resume count to 1:
// 1 - stop in shell
// 2 - then we will stop in our program
SetResumeCount(1);
shell_command.PutCString ("exec");
if (GetArchitecture().IsValid())
{
shell_command.Printf(" /usr/bin/arch -arch %s", GetArchitecture().GetArchitectureName());
// Set the resume count to 2:
// 1 - stop in shell
// 2 - stop in /usr/bin/arch
// 3 - then we will stop in our program
SetResumeCount(2);
}
else
{
// Set the resume count to 1:
// 1 - stop in shell
// 2 - then we will stop in our program
SetResumeCount(1);
}
}
const char **argv = GetArguments().GetConstArgumentVector ();
if (argv)
{
for (size_t i=0; argv[i] != NULL; ++i)
if (first_arg_is_full_shell_command)
{
const char *arg = Args::GetShellSafeArgument (argv[i], safe_arg);
shell_command.Printf(" %s", arg);
// There should only be one argument that is the shell command itself to be used as is
if (argv[0] && !argv[1])
shell_command.Printf("%s", argv[0]);
else
return false;
}
else
{
for (size_t i=0; argv[i] != NULL; ++i)
{
const char *arg = Args::GetShellSafeArgument (argv[i], safe_arg);
shell_command.Printf(" %s", arg);
}
}
shell_arguments.AppendArgument (shell_command.GetString().c_str());
}
shell_arguments.AppendArgument (shell_command.GetString().c_str());
else
{
return false;
}
m_executable.SetFile(shell_executable, false);
m_arguments = shell_arguments;
return true;