1//===-- ProcessLaunchInfo.cpp -----------------------------------*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include <climits>
10
11#include "lldb/Host/Config.h"
12#include "lldb/Host/FileAction.h"
13#include "lldb/Host/FileSystem.h"
14#include "lldb/Host/HostInfo.h"
15#include "lldb/Host/ProcessLaunchInfo.h"
16#include "lldb/Utility/Log.h"
17#include "lldb/Utility/StreamString.h"
18
19#include "llvm/Support/ConvertUTF.h"
20#include "llvm/Support/FileSystem.h"
21
22#if !defined(_WIN32)
23#include <limits.h>
24#endif
25
26using namespace lldb;
27using namespace lldb_private;
28
29// ProcessLaunchInfo member functions
30
31ProcessLaunchInfo::ProcessLaunchInfo()
32    : ProcessInfo(), m_working_dir(), m_plugin_name(), m_flags(0),
33      m_file_actions(), m_pty(new PseudoTerminal), m_resume_count(0),
34      m_monitor_callback(nullptr), m_monitor_callback_baton(nullptr),
35      m_monitor_signals(false), m_listener_sp(), m_hijack_listener_sp() {}
36
37ProcessLaunchInfo::ProcessLaunchInfo(const FileSpec &stdin_file_spec,
38                                     const FileSpec &stdout_file_spec,
39                                     const FileSpec &stderr_file_spec,
40                                     const FileSpec &working_directory,
41                                     uint32_t launch_flags)
42    : ProcessInfo(), m_working_dir(), m_plugin_name(), m_flags(launch_flags),
43      m_file_actions(), m_pty(new PseudoTerminal), m_resume_count(0),
44      m_monitor_callback(nullptr), m_monitor_callback_baton(nullptr),
45      m_monitor_signals(false), m_listener_sp(), m_hijack_listener_sp() {
46  if (stdin_file_spec) {
47    FileAction file_action;
48    const bool read = true;
49    const bool write = false;
50    if (file_action.Open(STDIN_FILENO, stdin_file_spec, read, write))
51      AppendFileAction(file_action);
52  }
53  if (stdout_file_spec) {
54    FileAction file_action;
55    const bool read = false;
56    const bool write = true;
57    if (file_action.Open(STDOUT_FILENO, stdout_file_spec, read, write))
58      AppendFileAction(file_action);
59  }
60  if (stderr_file_spec) {
61    FileAction file_action;
62    const bool read = false;
63    const bool write = true;
64    if (file_action.Open(STDERR_FILENO, stderr_file_spec, read, write))
65      AppendFileAction(file_action);
66  }
67  if (working_directory)
68    SetWorkingDirectory(working_directory);
69}
70
71bool ProcessLaunchInfo::AppendCloseFileAction(int fd) {
72  FileAction file_action;
73  if (file_action.Close(fd)) {
74    AppendFileAction(file_action);
75    return true;
76  }
77  return false;
78}
79
80bool ProcessLaunchInfo::AppendDuplicateFileAction(int fd, int dup_fd) {
81  FileAction file_action;
82  if (file_action.Duplicate(fd, dup_fd)) {
83    AppendFileAction(file_action);
84    return true;
85  }
86  return false;
87}
88
89bool ProcessLaunchInfo::AppendOpenFileAction(int fd, const FileSpec &file_spec,
90                                             bool read, bool write) {
91  FileAction file_action;
92  if (file_action.Open(fd, file_spec, read, write)) {
93    AppendFileAction(file_action);
94    return true;
95  }
96  return false;
97}
98
99bool ProcessLaunchInfo::AppendSuppressFileAction(int fd, bool read,
100                                                 bool write) {
101  FileAction file_action;
102  if (file_action.Open(fd, FileSpec(FileSystem::DEV_NULL), read, write)) {
103    AppendFileAction(file_action);
104    return true;
105  }
106  return false;
107}
108
109const FileAction *ProcessLaunchInfo::GetFileActionAtIndex(size_t idx) const {
110  if (idx < m_file_actions.size())
111    return &m_file_actions[idx];
112  return nullptr;
113}
114
115const FileAction *ProcessLaunchInfo::GetFileActionForFD(int fd) const {
116  for (size_t idx = 0, count = m_file_actions.size(); idx < count; ++idx) {
117    if (m_file_actions[idx].GetFD() == fd)
118      return &m_file_actions[idx];
119  }
120  return nullptr;
121}
122
123const FileSpec &ProcessLaunchInfo::GetWorkingDirectory() const {
124  return m_working_dir;
125}
126
127void ProcessLaunchInfo::SetWorkingDirectory(const FileSpec &working_dir) {
128  m_working_dir = working_dir;
129}
130
131const char *ProcessLaunchInfo::GetProcessPluginName() const {
132  return (m_plugin_name.empty() ? nullptr : m_plugin_name.c_str());
133}
134
135void ProcessLaunchInfo::SetProcessPluginName(llvm::StringRef plugin) {
136  m_plugin_name = plugin;
137}
138
139const FileSpec &ProcessLaunchInfo::GetShell() const { return m_shell; }
140
141void ProcessLaunchInfo::SetShell(const FileSpec &shell) {
142  m_shell = shell;
143  if (m_shell) {
144    FileSystem::Instance().ResolveExecutableLocation(m_shell);
145    m_flags.Set(lldb::eLaunchFlagLaunchInShell);
146  } else
147    m_flags.Clear(lldb::eLaunchFlagLaunchInShell);
148}
149
150void ProcessLaunchInfo::SetLaunchInSeparateProcessGroup(bool separate) {
151  if (separate)
152    m_flags.Set(lldb::eLaunchFlagLaunchInSeparateProcessGroup);
153  else
154    m_flags.Clear(lldb::eLaunchFlagLaunchInSeparateProcessGroup);
155}
156
157void ProcessLaunchInfo::SetShellExpandArguments(bool expand) {
158  if (expand)
159    m_flags.Set(lldb::eLaunchFlagShellExpandArguments);
160  else
161    m_flags.Clear(lldb::eLaunchFlagShellExpandArguments);
162}
163
164void ProcessLaunchInfo::Clear() {
165  ProcessInfo::Clear();
166  m_working_dir.Clear();
167  m_plugin_name.clear();
168  m_shell.Clear();
169  m_flags.Clear();
170  m_file_actions.clear();
171  m_resume_count = 0;
172  m_listener_sp.reset();
173  m_hijack_listener_sp.reset();
174}
175
176void ProcessLaunchInfo::SetMonitorProcessCallback(
177    const Host::MonitorChildProcessCallback &callback, bool monitor_signals) {
178  m_monitor_callback = callback;
179  m_monitor_signals = monitor_signals;
180}
181
182bool ProcessLaunchInfo::NoOpMonitorCallback(lldb::pid_t pid, bool exited, int signal, int status) {
183  Log *log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS);
184  LLDB_LOG(log, "pid = {0}, exited = {1}, signal = {2}, status = {3}", pid,
185           exited, signal, status);
186  return true;
187}
188
189bool ProcessLaunchInfo::MonitorProcess() const {
190  if (m_monitor_callback && ProcessIDIsValid()) {
191    llvm::Expected<HostThread> maybe_thread =
192    Host::StartMonitoringChildProcess(m_monitor_callback, GetProcessID(),
193                                      m_monitor_signals);
194    if (!maybe_thread)
195      LLDB_LOG(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST),
196               "failed to launch host thread: {}",
197               llvm::toString(maybe_thread.takeError()));
198    return true;
199  }
200  return false;
201}
202
203void ProcessLaunchInfo::SetDetachOnError(bool enable) {
204  if (enable)
205    m_flags.Set(lldb::eLaunchFlagDetachOnError);
206  else
207    m_flags.Clear(lldb::eLaunchFlagDetachOnError);
208}
209
210llvm::Error ProcessLaunchInfo::SetUpPtyRedirection() {
211  Log *log = GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS);
212  LLDB_LOG(log, "Generating a pty to use for stdin/out/err");
213
214  int open_flags = O_RDWR | O_NOCTTY;
215#if !defined(_WIN32)
216  // We really shouldn't be specifying platform specific flags that are
217  // intended for a system call in generic code.  But this will have to
218  // do for now.
219  open_flags |= O_CLOEXEC;
220#endif
221  if (!m_pty->OpenFirstAvailableMaster(open_flags, nullptr, 0)) {
222    return llvm::createStringError(llvm::inconvertibleErrorCode(),
223                                   "PTY::OpenFirstAvailableMaster failed");
224  }
225  const FileSpec slave_file_spec(m_pty->GetSlaveName(nullptr, 0));
226
227  // Only use the slave tty if we don't have anything specified for
228  // input and don't have an action for stdin
229  if (GetFileActionForFD(STDIN_FILENO) == nullptr)
230    AppendOpenFileAction(STDIN_FILENO, slave_file_spec, true, false);
231
232  // Only use the slave tty if we don't have anything specified for
233  // output and don't have an action for stdout
234  if (GetFileActionForFD(STDOUT_FILENO) == nullptr)
235    AppendOpenFileAction(STDOUT_FILENO, slave_file_spec, false, true);
236
237  // Only use the slave tty if we don't have anything specified for
238  // error and don't have an action for stderr
239  if (GetFileActionForFD(STDERR_FILENO) == nullptr)
240    AppendOpenFileAction(STDERR_FILENO, slave_file_spec, false, true);
241  return llvm::Error::success();
242}
243
244bool ProcessLaunchInfo::ConvertArgumentsForLaunchingInShell(
245    Status &error, bool localhost, bool will_debug,
246    bool first_arg_is_full_shell_command, int32_t num_resumes) {
247  error.Clear();
248
249  if (GetFlags().Test(eLaunchFlagLaunchInShell)) {
250    if (m_shell) {
251      std::string shell_executable = m_shell.GetPath();
252
253      const char **argv = GetArguments().GetConstArgumentVector();
254      if (argv == nullptr || argv[0] == nullptr)
255        return false;
256      Args shell_arguments;
257      std::string safe_arg;
258      shell_arguments.AppendArgument(shell_executable);
259      const llvm::Triple &triple = GetArchitecture().GetTriple();
260      if (triple.getOS() == llvm::Triple::Win32 &&
261          !triple.isWindowsCygwinEnvironment())
262        shell_arguments.AppendArgument(llvm::StringRef("/C"));
263      else
264        shell_arguments.AppendArgument(llvm::StringRef("-c"));
265
266      StreamString shell_command;
267      if (will_debug) {
268        // Add a modified PATH environment variable in case argv[0] is a
269        // relative path.
270        const char *argv0 = argv[0];
271        FileSpec arg_spec(argv0);
272        if (arg_spec.IsRelative()) {
273          // We have a relative path to our executable which may not work if we
274          // just try to run "a.out" (without it being converted to "./a.out")
275          FileSpec working_dir = GetWorkingDirectory();
276          // Be sure to put quotes around PATH's value in case any paths have
277          // spaces...
278          std::string new_path("PATH=\"");
279          const size_t empty_path_len = new_path.size();
280
281          if (working_dir) {
282            new_path += working_dir.GetPath();
283          } else {
284            llvm::SmallString<64> cwd;
285            if (! llvm::sys::fs::current_path(cwd))
286              new_path += cwd;
287          }
288          std::string curr_path;
289          if (HostInfo::GetEnvironmentVar("PATH", curr_path)) {
290            if (new_path.size() > empty_path_len)
291              new_path += ':';
292            new_path += curr_path;
293          }
294          new_path += "\" ";
295          shell_command.PutCString(new_path);
296        }
297
298        if (triple.getOS() != llvm::Triple::Win32 ||
299            triple.isWindowsCygwinEnvironment())
300          shell_command.PutCString("exec");
301
302        // Only Apple supports /usr/bin/arch being able to specify the
303        // architecture
304        if (GetArchitecture().IsValid() && // Valid architecture
305            GetArchitecture().GetTriple().getVendor() ==
306                llvm::Triple::Apple && // Apple only
307            GetArchitecture().GetCore() !=
308                ArchSpec::eCore_x86_64_x86_64h) // Don't do this for x86_64h
309        {
310          shell_command.Printf(" /usr/bin/arch -arch %s",
311                               GetArchitecture().GetArchitectureName());
312          // Set the resume count to 2:
313          // 1 - stop in shell
314          // 2 - stop in /usr/bin/arch
315          // 3 - then we will stop in our program
316          SetResumeCount(num_resumes + 1);
317        } else {
318          // Set the resume count to 1:
319          // 1 - stop in shell
320          // 2 - then we will stop in our program
321          SetResumeCount(num_resumes);
322        }
323      }
324
325      if (first_arg_is_full_shell_command) {
326        // There should only be one argument that is the shell command itself
327        // to be used as is
328        if (argv[0] && !argv[1])
329          shell_command.Printf("%s", argv[0]);
330        else
331          return false;
332      } else {
333        for (size_t i = 0; argv[i] != nullptr; ++i) {
334          const char *arg =
335              Args::GetShellSafeArgument(m_shell, argv[i], safe_arg);
336          shell_command.Printf(" %s", arg);
337        }
338      }
339      shell_arguments.AppendArgument(shell_command.GetString());
340      m_executable = m_shell;
341      m_arguments = shell_arguments;
342      return true;
343    } else {
344      error.SetErrorString("invalid shell path");
345    }
346  } else {
347    error.SetErrorString("not launching in shell");
348  }
349  return false;
350}
351