10127ef0fSEd Maste //===-- ProcessLaunchInfo.cpp -----------------------------------*- C++ -*-===//
20127ef0fSEd Maste //
30127ef0fSEd Maste //                     The LLVM Compiler Infrastructure
40127ef0fSEd Maste //
50127ef0fSEd Maste // This file is distributed under the University of Illinois Open Source
60127ef0fSEd Maste // License. See LICENSE.TXT for details.
70127ef0fSEd Maste //
80127ef0fSEd Maste //===----------------------------------------------------------------------===//
90127ef0fSEd Maste 
104bb0738eSEd Maste #include <climits>
110127ef0fSEd Maste 
124bb0738eSEd Maste #include "lldb/Host/Config.h"
139f2f44ceSEd Maste #include "lldb/Host/FileSystem.h"
144bb0738eSEd Maste #include "lldb/Host/HostInfo.h"
150127ef0fSEd Maste #include "lldb/Target/FileAction.h"
164bb0738eSEd Maste #include "lldb/Target/ProcessLaunchInfo.h"
17f678e45dSDimitry Andric #include "lldb/Utility/Log.h"
18f678e45dSDimitry Andric #include "lldb/Utility/StreamString.h"
190127ef0fSEd Maste 
204bb0738eSEd Maste #include "llvm/Support/ConvertUTF.h"
21f678e45dSDimitry Andric #include "llvm/Support/FileSystem.h"
224bb0738eSEd Maste 
231c3bbb01SEd Maste #if !defined(_WIN32)
241c3bbb01SEd Maste #include <limits.h>
251c3bbb01SEd Maste #endif
261c3bbb01SEd Maste 
270127ef0fSEd Maste using namespace lldb;
280127ef0fSEd Maste using namespace lldb_private;
290127ef0fSEd Maste 
300127ef0fSEd Maste //----------------------------------------------------------------------------
310127ef0fSEd Maste // ProcessLaunchInfo member functions
320127ef0fSEd Maste //----------------------------------------------------------------------------
330127ef0fSEd Maste 
ProcessLaunchInfo()34435933ddSDimitry Andric ProcessLaunchInfo::ProcessLaunchInfo()
35435933ddSDimitry Andric     : ProcessInfo(), m_working_dir(), m_plugin_name(), m_flags(0),
36acac075bSDimitry Andric       m_file_actions(), m_pty(new PseudoTerminal), m_resume_count(0),
37acac075bSDimitry Andric       m_monitor_callback(nullptr), m_monitor_callback_baton(nullptr),
38acac075bSDimitry Andric       m_monitor_signals(false), m_listener_sp(), m_hijack_listener_sp() {}
390127ef0fSEd Maste 
ProcessLaunchInfo(const FileSpec & stdin_file_spec,const FileSpec & stdout_file_spec,const FileSpec & stderr_file_spec,const FileSpec & working_directory,uint32_t launch_flags)401c3bbb01SEd Maste ProcessLaunchInfo::ProcessLaunchInfo(const FileSpec &stdin_file_spec,
411c3bbb01SEd Maste                                      const FileSpec &stdout_file_spec,
421c3bbb01SEd Maste                                      const FileSpec &stderr_file_spec,
431c3bbb01SEd Maste                                      const FileSpec &working_directory,
44435933ddSDimitry Andric                                      uint32_t launch_flags)
45435933ddSDimitry Andric     : ProcessInfo(), m_working_dir(), m_plugin_name(), m_flags(launch_flags),
46acac075bSDimitry Andric       m_file_actions(), m_pty(new PseudoTerminal), m_resume_count(0),
47acac075bSDimitry Andric       m_monitor_callback(nullptr), m_monitor_callback_baton(nullptr),
48acac075bSDimitry Andric       m_monitor_signals(false), m_listener_sp(), m_hijack_listener_sp() {
49435933ddSDimitry Andric   if (stdin_file_spec) {
500127ef0fSEd Maste     FileAction file_action;
510127ef0fSEd Maste     const bool read = true;
520127ef0fSEd Maste     const bool write = false;
531c3bbb01SEd Maste     if (file_action.Open(STDIN_FILENO, stdin_file_spec, read, write))
540127ef0fSEd Maste       AppendFileAction(file_action);
550127ef0fSEd Maste   }
56435933ddSDimitry Andric   if (stdout_file_spec) {
570127ef0fSEd Maste     FileAction file_action;
580127ef0fSEd Maste     const bool read = false;
590127ef0fSEd Maste     const bool write = true;
601c3bbb01SEd Maste     if (file_action.Open(STDOUT_FILENO, stdout_file_spec, read, write))
610127ef0fSEd Maste       AppendFileAction(file_action);
620127ef0fSEd Maste   }
63435933ddSDimitry Andric   if (stderr_file_spec) {
640127ef0fSEd Maste     FileAction file_action;
650127ef0fSEd Maste     const bool read = false;
660127ef0fSEd Maste     const bool write = true;
671c3bbb01SEd Maste     if (file_action.Open(STDERR_FILENO, stderr_file_spec, read, write))
680127ef0fSEd Maste       AppendFileAction(file_action);
690127ef0fSEd Maste   }
700127ef0fSEd Maste   if (working_directory)
710127ef0fSEd Maste     SetWorkingDirectory(working_directory);
720127ef0fSEd Maste }
730127ef0fSEd Maste 
AppendCloseFileAction(int fd)74435933ddSDimitry Andric bool ProcessLaunchInfo::AppendCloseFileAction(int fd) {
750127ef0fSEd Maste   FileAction file_action;
76435933ddSDimitry Andric   if (file_action.Close(fd)) {
770127ef0fSEd Maste     AppendFileAction(file_action);
780127ef0fSEd Maste     return true;
790127ef0fSEd Maste   }
800127ef0fSEd Maste   return false;
810127ef0fSEd Maste }
820127ef0fSEd Maste 
AppendDuplicateFileAction(int fd,int dup_fd)83435933ddSDimitry Andric bool ProcessLaunchInfo::AppendDuplicateFileAction(int fd, int dup_fd) {
840127ef0fSEd Maste   FileAction file_action;
85435933ddSDimitry Andric   if (file_action.Duplicate(fd, dup_fd)) {
860127ef0fSEd Maste     AppendFileAction(file_action);
870127ef0fSEd Maste     return true;
880127ef0fSEd Maste   }
890127ef0fSEd Maste   return false;
900127ef0fSEd Maste }
910127ef0fSEd Maste 
AppendOpenFileAction(int fd,const FileSpec & file_spec,bool read,bool write)92435933ddSDimitry Andric bool ProcessLaunchInfo::AppendOpenFileAction(int fd, const FileSpec &file_spec,
93435933ddSDimitry Andric                                              bool read, bool write) {
940127ef0fSEd Maste   FileAction file_action;
95435933ddSDimitry Andric   if (file_action.Open(fd, file_spec, read, write)) {
960127ef0fSEd Maste     AppendFileAction(file_action);
970127ef0fSEd Maste     return true;
980127ef0fSEd Maste   }
990127ef0fSEd Maste   return false;
1000127ef0fSEd Maste }
1010127ef0fSEd Maste 
AppendSuppressFileAction(int fd,bool read,bool write)102435933ddSDimitry Andric bool ProcessLaunchInfo::AppendSuppressFileAction(int fd, bool read,
103435933ddSDimitry Andric                                                  bool write) {
1040127ef0fSEd Maste   FileAction file_action;
105*b5893f02SDimitry Andric   if (file_action.Open(fd, FileSpec(FileSystem::DEV_NULL), read, write)) {
1060127ef0fSEd Maste     AppendFileAction(file_action);
1070127ef0fSEd Maste     return true;
1080127ef0fSEd Maste   }
1090127ef0fSEd Maste   return false;
1100127ef0fSEd Maste }
1110127ef0fSEd Maste 
GetFileActionAtIndex(size_t idx) const112435933ddSDimitry Andric const FileAction *ProcessLaunchInfo::GetFileActionAtIndex(size_t idx) const {
1130127ef0fSEd Maste   if (idx < m_file_actions.size())
1140127ef0fSEd Maste     return &m_file_actions[idx];
1154bb0738eSEd Maste   return nullptr;
1160127ef0fSEd Maste }
1170127ef0fSEd Maste 
GetFileActionForFD(int fd) const118435933ddSDimitry Andric const FileAction *ProcessLaunchInfo::GetFileActionForFD(int fd) const {
119435933ddSDimitry Andric   for (size_t idx = 0, count = m_file_actions.size(); idx < count; ++idx) {
1200127ef0fSEd Maste     if (m_file_actions[idx].GetFD() == fd)
1210127ef0fSEd Maste       return &m_file_actions[idx];
1220127ef0fSEd Maste   }
1234bb0738eSEd Maste   return nullptr;
1240127ef0fSEd Maste }
1250127ef0fSEd Maste 
GetWorkingDirectory() const126435933ddSDimitry Andric const FileSpec &ProcessLaunchInfo::GetWorkingDirectory() const {
1271c3bbb01SEd Maste   return m_working_dir;
1280127ef0fSEd Maste }
1290127ef0fSEd Maste 
SetWorkingDirectory(const FileSpec & working_dir)130435933ddSDimitry Andric void ProcessLaunchInfo::SetWorkingDirectory(const FileSpec &working_dir) {
1311c3bbb01SEd Maste   m_working_dir = working_dir;
1320127ef0fSEd Maste }
1330127ef0fSEd Maste 
GetProcessPluginName() const134435933ddSDimitry Andric const char *ProcessLaunchInfo::GetProcessPluginName() const {
1354bb0738eSEd Maste   return (m_plugin_name.empty() ? nullptr : m_plugin_name.c_str());
1360127ef0fSEd Maste }
1370127ef0fSEd Maste 
SetProcessPluginName(llvm::StringRef plugin)138435933ddSDimitry Andric void ProcessLaunchInfo::SetProcessPluginName(llvm::StringRef plugin) {
139435933ddSDimitry Andric   m_plugin_name = plugin;
1400127ef0fSEd Maste }
1410127ef0fSEd Maste 
GetShell() const142435933ddSDimitry Andric const FileSpec &ProcessLaunchInfo::GetShell() const { return m_shell; }
1430127ef0fSEd Maste 
SetShell(const FileSpec & shell)144435933ddSDimitry Andric void ProcessLaunchInfo::SetShell(const FileSpec &shell) {
1457aa51b79SEd Maste   m_shell = shell;
146435933ddSDimitry Andric   if (m_shell) {
147*b5893f02SDimitry Andric     FileSystem::Instance().ResolveExecutableLocation(m_shell);
1480127ef0fSEd Maste     m_flags.Set(lldb::eLaunchFlagLaunchInShell);
149435933ddSDimitry Andric   } else
1500127ef0fSEd Maste     m_flags.Clear(lldb::eLaunchFlagLaunchInShell);
1510127ef0fSEd Maste }
1520127ef0fSEd Maste 
SetLaunchInSeparateProcessGroup(bool separate)153435933ddSDimitry Andric void ProcessLaunchInfo::SetLaunchInSeparateProcessGroup(bool separate) {
1540127ef0fSEd Maste   if (separate)
1550127ef0fSEd Maste     m_flags.Set(lldb::eLaunchFlagLaunchInSeparateProcessGroup);
1560127ef0fSEd Maste   else
1570127ef0fSEd Maste     m_flags.Clear(lldb::eLaunchFlagLaunchInSeparateProcessGroup);
1581c3bbb01SEd Maste }
1590127ef0fSEd Maste 
SetShellExpandArguments(bool expand)160435933ddSDimitry Andric void ProcessLaunchInfo::SetShellExpandArguments(bool expand) {
1611c3bbb01SEd Maste   if (expand)
1621c3bbb01SEd Maste     m_flags.Set(lldb::eLaunchFlagShellExpandArguments);
1631c3bbb01SEd Maste   else
1641c3bbb01SEd Maste     m_flags.Clear(lldb::eLaunchFlagShellExpandArguments);
1650127ef0fSEd Maste }
1660127ef0fSEd Maste 
Clear()167435933ddSDimitry Andric void ProcessLaunchInfo::Clear() {
1680127ef0fSEd Maste   ProcessInfo::Clear();
1691c3bbb01SEd Maste   m_working_dir.Clear();
1700127ef0fSEd Maste   m_plugin_name.clear();
1717aa51b79SEd Maste   m_shell.Clear();
1720127ef0fSEd Maste   m_flags.Clear();
1730127ef0fSEd Maste   m_file_actions.clear();
1740127ef0fSEd Maste   m_resume_count = 0;
1757aa51b79SEd Maste   m_listener_sp.reset();
1760127ef0fSEd Maste   m_hijack_listener_sp.reset();
1770127ef0fSEd Maste }
1780127ef0fSEd Maste 
SetMonitorProcessCallback(const Host::MonitorChildProcessCallback & callback,bool monitor_signals)179435933ddSDimitry Andric void ProcessLaunchInfo::SetMonitorProcessCallback(
180435933ddSDimitry Andric     const Host::MonitorChildProcessCallback &callback, bool monitor_signals) {
1810127ef0fSEd Maste   m_monitor_callback = callback;
1820127ef0fSEd Maste   m_monitor_signals = monitor_signals;
1830127ef0fSEd Maste }
1840127ef0fSEd Maste 
NoOpMonitorCallback(lldb::pid_t pid,bool exited,int signal,int status)1854ba319b5SDimitry Andric bool ProcessLaunchInfo::NoOpMonitorCallback(lldb::pid_t pid, bool exited, int signal, int status) {
1864ba319b5SDimitry Andric   Log *log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS);
1874ba319b5SDimitry Andric   LLDB_LOG(log, "pid = {0}, exited = {1}, signal = {2}, status = {3}", pid,
1884ba319b5SDimitry Andric            exited, signal, status);
1894ba319b5SDimitry Andric   return true;
1904ba319b5SDimitry Andric }
1914ba319b5SDimitry Andric 
MonitorProcess() const192435933ddSDimitry Andric bool ProcessLaunchInfo::MonitorProcess() const {
193435933ddSDimitry Andric   if (m_monitor_callback && ProcessIDIsValid()) {
194435933ddSDimitry Andric     Host::StartMonitoringChildProcess(m_monitor_callback, GetProcessID(),
1950127ef0fSEd Maste                                       m_monitor_signals);
1960127ef0fSEd Maste     return true;
1970127ef0fSEd Maste   }
1980127ef0fSEd Maste   return false;
1990127ef0fSEd Maste }
2000127ef0fSEd Maste 
SetDetachOnError(bool enable)201435933ddSDimitry Andric void ProcessLaunchInfo::SetDetachOnError(bool enable) {
2020127ef0fSEd Maste   if (enable)
2030127ef0fSEd Maste     m_flags.Set(lldb::eLaunchFlagDetachOnError);
2040127ef0fSEd Maste   else
2050127ef0fSEd Maste     m_flags.Clear(lldb::eLaunchFlagDetachOnError);
2060127ef0fSEd Maste }
2070127ef0fSEd Maste 
SetUpPtyRedirection()208*b5893f02SDimitry Andric llvm::Error ProcessLaunchInfo::SetUpPtyRedirection() {
209*b5893f02SDimitry Andric   Log *log = GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS);
210*b5893f02SDimitry Andric   LLDB_LOG(log, "Generating a pty to use for stdin/out/err");
2110127ef0fSEd Maste 
2121c3bbb01SEd Maste   int open_flags = O_RDWR | O_NOCTTY;
213435933ddSDimitry Andric #if !defined(_WIN32)
2144ba319b5SDimitry Andric   // We really shouldn't be specifying platform specific flags that are
2154ba319b5SDimitry Andric   // intended for a system call in generic code.  But this will have to
2164ba319b5SDimitry Andric   // do for now.
2171c3bbb01SEd Maste   open_flags |= O_CLOEXEC;
2181c3bbb01SEd Maste #endif
219*b5893f02SDimitry Andric   if (!m_pty->OpenFirstAvailableMaster(open_flags, nullptr, 0)) {
220*b5893f02SDimitry Andric     return llvm::createStringError(llvm::inconvertibleErrorCode(),
221*b5893f02SDimitry Andric                                    "PTY::OpenFirstAvailableMaster failed");
222*b5893f02SDimitry Andric   }
223*b5893f02SDimitry Andric   const FileSpec slave_file_spec(m_pty->GetSlaveName(nullptr, 0));
2247aa51b79SEd Maste 
2257aa51b79SEd Maste   // Only use the slave tty if we don't have anything specified for
2267aa51b79SEd Maste   // input and don't have an action for stdin
227*b5893f02SDimitry Andric   if (GetFileActionForFD(STDIN_FILENO) == nullptr)
2281c3bbb01SEd Maste     AppendOpenFileAction(STDIN_FILENO, slave_file_spec, true, false);
2290127ef0fSEd Maste 
2307aa51b79SEd Maste   // Only use the slave tty if we don't have anything specified for
2317aa51b79SEd Maste   // output and don't have an action for stdout
232*b5893f02SDimitry Andric   if (GetFileActionForFD(STDOUT_FILENO) == nullptr)
2331c3bbb01SEd Maste     AppendOpenFileAction(STDOUT_FILENO, slave_file_spec, false, true);
2340127ef0fSEd Maste 
2357aa51b79SEd Maste   // Only use the slave tty if we don't have anything specified for
2367aa51b79SEd Maste   // error and don't have an action for stderr
237*b5893f02SDimitry Andric   if (GetFileActionForFD(STDERR_FILENO) == nullptr)
2381c3bbb01SEd Maste     AppendOpenFileAction(STDERR_FILENO, slave_file_spec, false, true);
239*b5893f02SDimitry Andric   return llvm::Error::success();
2400127ef0fSEd Maste }
2410127ef0fSEd Maste 
ConvertArgumentsForLaunchingInShell(Status & error,bool localhost,bool will_debug,bool first_arg_is_full_shell_command,int32_t num_resumes)242435933ddSDimitry Andric bool ProcessLaunchInfo::ConvertArgumentsForLaunchingInShell(
2435517e702SDimitry Andric     Status &error, bool localhost, bool will_debug,
244435933ddSDimitry Andric     bool first_arg_is_full_shell_command, int32_t num_resumes) {
2450127ef0fSEd Maste   error.Clear();
2460127ef0fSEd Maste 
247435933ddSDimitry Andric   if (GetFlags().Test(eLaunchFlagLaunchInShell)) {
248435933ddSDimitry Andric     if (m_shell) {
2497aa51b79SEd Maste       std::string shell_executable = m_shell.GetPath();
2500127ef0fSEd Maste 
2510127ef0fSEd Maste       const char **argv = GetArguments().GetConstArgumentVector();
2524bb0738eSEd Maste       if (argv == nullptr || argv[0] == nullptr)
2530127ef0fSEd Maste         return false;
2540127ef0fSEd Maste       Args shell_arguments;
2550127ef0fSEd Maste       std::string safe_arg;
256435933ddSDimitry Andric       shell_arguments.AppendArgument(shell_executable);
2577aa51b79SEd Maste       const llvm::Triple &triple = GetArchitecture().GetTriple();
258435933ddSDimitry Andric       if (triple.getOS() == llvm::Triple::Win32 &&
259435933ddSDimitry Andric           !triple.isWindowsCygwinEnvironment())
260435933ddSDimitry Andric         shell_arguments.AppendArgument(llvm::StringRef("/C"));
2617aa51b79SEd Maste       else
262435933ddSDimitry Andric         shell_arguments.AppendArgument(llvm::StringRef("-c"));
2637aa51b79SEd Maste 
2640127ef0fSEd Maste       StreamString shell_command;
265435933ddSDimitry Andric       if (will_debug) {
2664ba319b5SDimitry Andric         // Add a modified PATH environment variable in case argv[0] is a
2674ba319b5SDimitry Andric         // relative path.
2680127ef0fSEd Maste         const char *argv0 = argv[0];
269*b5893f02SDimitry Andric         FileSpec arg_spec(argv0);
270435933ddSDimitry Andric         if (arg_spec.IsRelative()) {
2714ba319b5SDimitry Andric           // We have a relative path to our executable which may not work if we
2724ba319b5SDimitry Andric           // just try to run "a.out" (without it being converted to "./a.out")
2731c3bbb01SEd Maste           FileSpec working_dir = GetWorkingDirectory();
274435933ddSDimitry Andric           // Be sure to put quotes around PATH's value in case any paths have
275435933ddSDimitry Andric           // spaces...
2760127ef0fSEd Maste           std::string new_path("PATH=\"");
2770127ef0fSEd Maste           const size_t empty_path_len = new_path.size();
2780127ef0fSEd Maste 
279435933ddSDimitry Andric           if (working_dir) {
2801c3bbb01SEd Maste             new_path += working_dir.GetPath();
281435933ddSDimitry Andric           } else {
282f678e45dSDimitry Andric             llvm::SmallString<64> cwd;
283f678e45dSDimitry Andric             if (! llvm::sys::fs::current_path(cwd))
2840127ef0fSEd Maste               new_path += cwd;
2850127ef0fSEd Maste           }
2864bb0738eSEd Maste           std::string curr_path;
287435933ddSDimitry Andric           if (HostInfo::GetEnvironmentVar("PATH", curr_path)) {
2880127ef0fSEd Maste             if (new_path.size() > empty_path_len)
2890127ef0fSEd Maste               new_path += ':';
2900127ef0fSEd Maste             new_path += curr_path;
2910127ef0fSEd Maste           }
2920127ef0fSEd Maste           new_path += "\" ";
293435933ddSDimitry Andric           shell_command.PutCString(new_path);
2940127ef0fSEd Maste         }
2950127ef0fSEd Maste 
296435933ddSDimitry Andric         if (triple.getOS() != llvm::Triple::Win32 ||
297435933ddSDimitry Andric             triple.isWindowsCygwinEnvironment())
2980127ef0fSEd Maste           shell_command.PutCString("exec");
2990127ef0fSEd Maste 
300435933ddSDimitry Andric         // Only Apple supports /usr/bin/arch being able to specify the
301435933ddSDimitry Andric         // architecture
3020127ef0fSEd Maste         if (GetArchitecture().IsValid() && // Valid architecture
303435933ddSDimitry Andric             GetArchitecture().GetTriple().getVendor() ==
304435933ddSDimitry Andric                 llvm::Triple::Apple && // Apple only
305435933ddSDimitry Andric             GetArchitecture().GetCore() !=
306435933ddSDimitry Andric                 ArchSpec::eCore_x86_64_x86_64h) // Don't do this for x86_64h
3070127ef0fSEd Maste         {
308435933ddSDimitry Andric           shell_command.Printf(" /usr/bin/arch -arch %s",
309435933ddSDimitry Andric                                GetArchitecture().GetArchitectureName());
3100127ef0fSEd Maste           // Set the resume count to 2:
3110127ef0fSEd Maste           // 1 - stop in shell
3120127ef0fSEd Maste           // 2 - stop in /usr/bin/arch
3130127ef0fSEd Maste           // 3 - then we will stop in our program
3140127ef0fSEd Maste           SetResumeCount(num_resumes + 1);
315435933ddSDimitry Andric         } else {
3160127ef0fSEd Maste           // Set the resume count to 1:
3170127ef0fSEd Maste           // 1 - stop in shell
3180127ef0fSEd Maste           // 2 - then we will stop in our program
3190127ef0fSEd Maste           SetResumeCount(num_resumes);
3200127ef0fSEd Maste         }
3210127ef0fSEd Maste       }
3220127ef0fSEd Maste 
323435933ddSDimitry Andric       if (first_arg_is_full_shell_command) {
3244ba319b5SDimitry Andric         // There should only be one argument that is the shell command itself
3254ba319b5SDimitry Andric         // to be used as is
3260127ef0fSEd Maste         if (argv[0] && !argv[1])
3270127ef0fSEd Maste           shell_command.Printf("%s", argv[0]);
3280127ef0fSEd Maste         else
3290127ef0fSEd Maste           return false;
330435933ddSDimitry Andric       } else {
331435933ddSDimitry Andric         for (size_t i = 0; argv[i] != nullptr; ++i) {
332435933ddSDimitry Andric           const char *arg =
333435933ddSDimitry Andric               Args::GetShellSafeArgument(m_shell, argv[i], safe_arg);
3340127ef0fSEd Maste           shell_command.Printf(" %s", arg);
3350127ef0fSEd Maste         }
3360127ef0fSEd Maste       }
337435933ddSDimitry Andric       shell_arguments.AppendArgument(shell_command.GetString());
3387aa51b79SEd Maste       m_executable = m_shell;
3390127ef0fSEd Maste       m_arguments = shell_arguments;
3400127ef0fSEd Maste       return true;
341435933ddSDimitry Andric     } else {
3420127ef0fSEd Maste       error.SetErrorString("invalid shell path");
3430127ef0fSEd Maste     }
344435933ddSDimitry Andric   } else {
3450127ef0fSEd Maste     error.SetErrorString("not launching in shell");
3460127ef0fSEd Maste   }
3470127ef0fSEd Maste   return false;
3480127ef0fSEd Maste }
349