1//===-- ProcessLaunchInfo.cpp ---------------------------------------------===//
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/LLDBLog.h"
17#include "lldb/Utility/Log.h"
18#include "lldb/Utility/StreamString.h"
19
20#include "llvm/Support/ConvertUTF.h"
21#include "llvm/Support/FileSystem.h"
22
23#if !defined(_WIN32)
24#include <climits>
25#endif
26
27using namespace lldb;
28using namespace lldb_private;
29
30// ProcessLaunchInfo member functions
31
32ProcessLaunchInfo::ProcessLaunchInfo()
33    : ProcessInfo(), m_working_dir(), m_plugin_name(), m_flags(0),
34      m_file_actions(), m_pty(new PseudoTerminal), m_monitor_callback(nullptr),
35      m_listener_sp(), m_hijack_listener_sp(), m_scripted_process_class_name(),
36      m_scripted_process_dictionary_sp() {}
37
38ProcessLaunchInfo::ProcessLaunchInfo(const FileSpec &stdin_file_spec,
39                                     const FileSpec &stdout_file_spec,
40                                     const FileSpec &stderr_file_spec,
41                                     const FileSpec &working_directory,
42                                     uint32_t launch_flags)
43    : ProcessInfo(), m_working_dir(), m_plugin_name(), m_flags(launch_flags),
44      m_file_actions(), m_pty(new PseudoTerminal),
45      m_scripted_process_class_name(), m_scripted_process_dictionary_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 = std::string(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  m_scripted_process_class_name.clear();
175  m_scripted_process_dictionary_sp.reset();
176}
177
178void ProcessLaunchInfo::NoOpMonitorCallback(lldb::pid_t pid, int signal,
179                                            int status) {
180  Log *log = GetLog(LLDBLog::Process);
181  LLDB_LOG(log, "pid = {0}, signal = {1}, status = {2}", pid, signal, status);
182}
183
184bool ProcessLaunchInfo::MonitorProcess() const {
185  if (m_monitor_callback && ProcessIDIsValid()) {
186    llvm::Expected<HostThread> maybe_thread =
187        Host::StartMonitoringChildProcess(m_monitor_callback, GetProcessID());
188    if (!maybe_thread)
189      LLDB_LOG(GetLog(LLDBLog::Host), "failed to launch host thread: {}",
190               llvm::toString(maybe_thread.takeError()));
191    return true;
192  }
193  return false;
194}
195
196void ProcessLaunchInfo::SetDetachOnError(bool enable) {
197  if (enable)
198    m_flags.Set(lldb::eLaunchFlagDetachOnError);
199  else
200    m_flags.Clear(lldb::eLaunchFlagDetachOnError);
201}
202
203llvm::Error ProcessLaunchInfo::SetUpPtyRedirection() {
204  Log *log = GetLog(LLDBLog::Process);
205
206  bool stdin_free = GetFileActionForFD(STDIN_FILENO) == nullptr;
207  bool stdout_free = GetFileActionForFD(STDOUT_FILENO) == nullptr;
208  bool stderr_free = GetFileActionForFD(STDERR_FILENO) == nullptr;
209  bool any_free = stdin_free || stdout_free || stderr_free;
210  if (!any_free)
211    return llvm::Error::success();
212
213  LLDB_LOG(log, "Generating a pty to use for stdin/out/err");
214
215  int open_flags = O_RDWR | O_NOCTTY;
216#if !defined(_WIN32)
217  // We really shouldn't be specifying platform specific flags that are
218  // intended for a system call in generic code.  But this will have to
219  // do for now.
220  open_flags |= O_CLOEXEC;
221#endif
222  if (llvm::Error Err = m_pty->OpenFirstAvailablePrimary(open_flags))
223    return Err;
224
225  const FileSpec secondary_file_spec(m_pty->GetSecondaryName());
226
227  if (stdin_free)
228    AppendOpenFileAction(STDIN_FILENO, secondary_file_spec, true, false);
229
230  if (stdout_free)
231    AppendOpenFileAction(STDOUT_FILENO, secondary_file_spec, false, true);
232
233  if (stderr_free)
234    AppendOpenFileAction(STDERR_FILENO, secondary_file_spec, false, true);
235  return llvm::Error::success();
236}
237
238bool ProcessLaunchInfo::ConvertArgumentsForLaunchingInShell(
239    Status &error, bool will_debug, bool first_arg_is_full_shell_command,
240    uint32_t num_resumes) {
241  error.Clear();
242
243  if (GetFlags().Test(eLaunchFlagLaunchInShell)) {
244    if (m_shell) {
245      std::string shell_executable = m_shell.GetPath();
246
247      const char **argv = GetArguments().GetConstArgumentVector();
248      if (argv == nullptr || argv[0] == nullptr)
249        return false;
250      Args shell_arguments;
251      shell_arguments.AppendArgument(shell_executable);
252      const llvm::Triple &triple = GetArchitecture().GetTriple();
253      if (triple.getOS() == llvm::Triple::Win32 &&
254          !triple.isWindowsCygwinEnvironment())
255        shell_arguments.AppendArgument(llvm::StringRef("/C"));
256      else
257        shell_arguments.AppendArgument(llvm::StringRef("-c"));
258
259      StreamString shell_command;
260      if (will_debug) {
261        // Add a modified PATH environment variable in case argv[0] is a
262        // relative path.
263        const char *argv0 = argv[0];
264        FileSpec arg_spec(argv0);
265        if (arg_spec.IsRelative()) {
266          // We have a relative path to our executable which may not work if we
267          // just try to run "a.out" (without it being converted to "./a.out")
268          FileSpec working_dir = GetWorkingDirectory();
269          // Be sure to put quotes around PATH's value in case any paths have
270          // spaces...
271          std::string new_path("PATH=\"");
272          const size_t empty_path_len = new_path.size();
273
274          if (working_dir) {
275            new_path += working_dir.GetPath();
276          } else {
277            llvm::SmallString<64> cwd;
278            if (! llvm::sys::fs::current_path(cwd))
279              new_path += cwd;
280          }
281          std::string curr_path;
282          if (HostInfo::GetEnvironmentVar("PATH", curr_path)) {
283            if (new_path.size() > empty_path_len)
284              new_path += ':';
285            new_path += curr_path;
286          }
287          new_path += "\" ";
288          shell_command.PutCString(new_path);
289        }
290
291        if (triple.getOS() != llvm::Triple::Win32 ||
292            triple.isWindowsCygwinEnvironment())
293          shell_command.PutCString("exec");
294
295        // Only Apple supports /usr/bin/arch being able to specify the
296        // architecture
297        if (GetArchitecture().IsValid() && // Valid architecture
298            GetArchitecture().GetTriple().getVendor() ==
299                llvm::Triple::Apple && // Apple only
300            GetArchitecture().GetCore() !=
301                ArchSpec::eCore_x86_64_x86_64h) // Don't do this for x86_64h
302        {
303          shell_command.Printf(" /usr/bin/arch -arch %s",
304                               GetArchitecture().GetArchitectureName());
305          // Set the resume count to 2:
306          // 1 - stop in shell
307          // 2 - stop in /usr/bin/arch
308          // 3 - then we will stop in our program
309          SetResumeCount(num_resumes + 1);
310        } else {
311          // Set the resume count to 1:
312          // 1 - stop in shell
313          // 2 - then we will stop in our program
314          SetResumeCount(num_resumes);
315        }
316      }
317
318      if (first_arg_is_full_shell_command) {
319        // There should only be one argument that is the shell command itself
320        // to be used as is
321        if (argv[0] && !argv[1])
322          shell_command.Printf("%s", argv[0]);
323        else
324          return false;
325      } else {
326        for (size_t i = 0; argv[i] != nullptr; ++i) {
327          std::string safe_arg = Args::GetShellSafeArgument(m_shell, argv[i]);
328          // Add a space to separate this arg from the previous one.
329          shell_command.PutCString(" ");
330          shell_command.PutCString(safe_arg);
331        }
332      }
333      shell_arguments.AppendArgument(shell_command.GetString());
334      m_executable = m_shell;
335      m_arguments = shell_arguments;
336      return true;
337    } else {
338      error.SetErrorString("invalid shell path");
339    }
340  } else {
341    error.SetErrorString("not launching in shell");
342  }
343  return false;
344}
345