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:
parent
f5c87882a0
commit
d1cf11a74d
|
@ -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);
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue