winapi_common
process.cpp
1 // Copyright (c) 2020 Egor Tensin <Egor.Tensin@gmail.com>
2 // This file is part of the "winapi-common" project.
3 // For details, see https://github.com/egor-tensin/winapi-common.
4 // Distributed under the MIT License.
5 
6 #include <winapi/cmd_line.hpp>
7 #include <winapi/error.hpp>
8 #include <winapi/handle.hpp>
9 #include <winapi/process.hpp>
10 #include <winapi/process_io.hpp>
11 #include <winapi/resource.hpp>
12 #include <winapi/utf8.hpp>
13 
14 // clang-format off
15 #include <windows.h>
16 #include <shellapi.h>
17 // clang-format on
18 
19 #include <cstddef>
20 #include <cstring>
21 #include <limits>
22 #include <sstream>
23 #include <stdexcept>
24 #include <string>
25 #include <utility>
26 #include <vector>
27 
28 namespace winapi {
29 namespace {
30 
31 using EscapedCommandLine = std::vector<wchar_t>;
32 
33 EscapedCommandLine escape_command_line(const CommandLine& cmd_line) {
34  const auto unicode_cmd_line = widen(cmd_line.to_string());
35  EscapedCommandLine buffer;
36  buffer.reserve(unicode_cmd_line.size() + 1);
37  buffer.assign(unicode_cmd_line.cbegin(), unicode_cmd_line.cend());
38  buffer.emplace_back(L'\0');
39  return buffer;
40 }
41 
42 Handle create_process(ProcessParameters& params) {
43  /*
44  * When creating a new console process, the options are:
45  * 1) inherit the parent console (the default),
46  * 2) CREATE_NO_WINDOW,
47  * 3) CREATE_NEW_CONSOLE,
48  * 4) DETACHED_PROCESS.
49  *
50  * Child processes can inherit the console.
51  * By that I mean they will display their output in the same window.
52  * If both the child process and the parent process read from stdin, there
53  * is no way to say which process will read any given input byte.
54  *
55  * There's an excellent guide into all the intricacies of the CreateProcess
56  * system call at
57  *
58  * https://github.com/rprichard/win32-console-docs/blob/master/README.md
59  *
60  * Another useful link is https://ikriv.com/dev/cpp/ConsoleProxy/flags.
61  */
62  static constexpr DWORD default_dwCreationFlags = CREATE_UNICODE_ENVIRONMENT;
63 
64  STARTUPINFOW startup_info;
65  std::memset(&startup_info, 0, sizeof(startup_info));
66  startup_info.cb = sizeof(startup_info);
67 
68  if (params.io) {
69  startup_info.dwFlags |= STARTF_USESTDHANDLES;
70  startup_info.hStdInput = static_cast<HANDLE>(params.io->std_in.handle);
71  startup_info.hStdOutput = static_cast<HANDLE>(params.io->std_out.handle);
72  startup_info.hStdError = static_cast<HANDLE>(params.io->std_err.handle);
73  }
74 
75  auto dwCreationFlags = default_dwCreationFlags;
76 
77  switch (params.console_mode) {
78  case ProcessParameters::ConsoleNone:
79  dwCreationFlags |= CREATE_NO_WINDOW;
80  break;
81  case ProcessParameters::ConsoleInherit:
82  // This is the default.
83  break;
84  case ProcessParameters::ConsoleNew:
85  dwCreationFlags |= CREATE_NEW_CONSOLE;
86  break;
87  }
88 
89  PROCESS_INFORMATION child_info;
90  std::memset(&child_info, 0, sizeof(child_info));
91 
92  {
93  auto cmd_line = escape_command_line(params.cmd_line);
94 
95  const auto ret = ::CreateProcessW(NULL,
96  cmd_line.data(),
97  NULL,
98  NULL,
99  TRUE,
100  dwCreationFlags,
101  NULL,
102  NULL,
103  &startup_info,
104  &child_info);
105 
106  if (!ret) {
107  throw error::windows(GetLastError(), "CreateProcessW");
108  }
109  }
110 
111  if (params.io) {
112  params.io->close();
113  }
114 
115  Handle process{child_info.hProcess};
116  Handle thread{child_info.hThread};
117 
118  return process;
119 }
120 
121 Handle shell_execute(const ShellParameters& params) {
122  const auto lpVerb = params.verb ? widen(*params.verb) : L"open";
123  const auto lpFile = widen(params.cmd_line.get_argv0());
124  const auto lpParameters = widen(params.cmd_line.args_to_string());
125 
126  static constexpr uint32_t default_fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI;
127 
128  auto fMask = default_fMask;
129  auto nShow = SW_SHOWDEFAULT;
130 
131  switch (params.console_mode) {
132  case ProcessParameters::ConsoleNone:
133  nShow = SW_HIDE;
134  break;
135  case ProcessParameters::ConsoleInherit:
136  fMask |= SEE_MASK_NO_CONSOLE;
137  break;
138  case ProcessParameters::ConsoleNew:
139  // This is the default.
140  break;
141  }
142 
143  SHELLEXECUTEINFOW info;
144  std::memset(&info, 0, sizeof(info));
145  info.cbSize = sizeof(info);
146  info.fMask = fMask;
147  info.lpVerb = lpVerb.c_str();
148  info.lpFile = lpFile.c_str();
149  if (!lpParameters.empty())
150  info.lpParameters = lpParameters.c_str();
151  info.nShow = nShow;
152 
153  if (!::ShellExecuteExW(&info)) {
154  throw error::windows(GetLastError(), "ShellExecuteExW");
155  }
156 
157  return Handle{info.hProcess};
158 }
159 
160 Handle open_process(DWORD id, DWORD permissions) {
161  Handle process{OpenProcess(permissions, FALSE, id)};
162  if (!process.is_valid()) {
163  throw error::windows(GetLastError(), "OpenProcess");
164  }
165  return process;
166 }
167 
168 class PathBuffer {
169 public:
170  PathBuffer() : m_size{min_size} { m_data.resize(m_size); }
171 
172  DWORD get_size() const { return m_size; }
173 
174  wchar_t* get_data() { return m_data.data(); }
175 
176  void grow() {
177  if (m_size < min_size) {
178  m_size = min_size;
179  } else {
180  // Check if we can still multiply by two.
181  if (std::numeric_limits<decltype(m_size)>::max() - m_size < m_size)
182  throw std::range_error{"Path buffer is too large"};
183  m_size *= 2;
184  }
185  m_data.resize(m_size);
186  }
187 
188 private:
189  static constexpr DWORD min_size = MAX_PATH;
190 
191  DWORD m_size;
192  std::vector<wchar_t> m_data;
193 };
194 
195 std::string get_current_exe_path(PathBuffer& buffer) {
196  SetLastError(ERROR_SUCCESS);
197 
198  const auto ec = ::GetModuleFileNameW(NULL, buffer.get_data(), buffer.get_size());
199 
200  if (ec == 0) {
201  throw error::windows(GetLastError(), "GetModuleFileNameW");
202  }
203 
204  if (ec == buffer.get_size() && GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
205  buffer.grow();
206  return get_current_exe_path(buffer);
207  }
208 
209  return narrow(buffer.get_data());
210 }
211 
212 std::string get_current_exe_path() {
213  PathBuffer buffer;
214  return get_current_exe_path(buffer);
215 }
216 
217 std::string get_exe_path(const Handle& process, PathBuffer& buffer) {
218  auto size = buffer.get_size();
219 
220  const auto ec = ::QueryFullProcessImageNameW(process.get(), 0, buffer.get_data(), &size);
221 
222  if (ec != 0) {
223  return narrow(buffer.get_data());
224  }
225 
226  if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
227  buffer.grow();
228  return get_exe_path(process, buffer);
229  }
230 
231  throw error::windows(GetLastError(), "QueryFullProcessImageNameW");
232 }
233 
234 std::string get_exe_path(const Handle& process) {
235  PathBuffer buffer;
236  return get_exe_path(process, buffer);
237 }
238 
239 } // namespace
240 
242  return Process{create_process(params)};
243 }
244 
246  ProcessParameters params{cmd_line};
247  return create(std::move(params));
248 }
249 
251  ProcessParameters params{cmd_line};
252  params.io = std::move(io);
253  return create(std::move(params));
254 }
255 
257  return Process{shell_execute(params)};
258 }
259 
261  ShellParameters params{cmd_line};
262  return shell(params);
263 }
264 
266  return Process{::GetCurrentProcessId(), Handle{::GetCurrentProcess()}};
267 }
268 
269 Process Process::open(DWORD id, DWORD permissions) {
270  return Process{id, open_process(id, permissions)};
271 }
272 
274  return open(id, read_permissions());
275 }
276 
278  return PROCESS_QUERY_INFORMATION;
279 }
280 
282  return default_permissions() | PROCESS_VM_READ;
283 }
284 
285 bool Process::is_running() const {
286  const auto ret = ::WaitForSingleObject(static_cast<HANDLE>(m_handle), 0);
287 
288  switch (ret) {
289  case WAIT_OBJECT_0:
290  return false;
291  case WAIT_TIMEOUT:
292  return true;
293  case WAIT_FAILED:
294  throw error::windows(GetLastError(), "WaitForSingleObject");
295  default:
296  // Shouldn't happen.
297  throw error::custom(ret, "WaitForSingleObject");
298  }
299 }
300 
301 void Process::wait() const {
302  const auto ret = ::WaitForSingleObject(static_cast<HANDLE>(m_handle), INFINITE);
303 
304  switch (ret) {
305  case WAIT_OBJECT_0:
306  return;
307  case WAIT_FAILED:
308  throw error::windows(GetLastError(), "WaitForSingleObject");
309  default:
310  // Shouldn't happen.
311  throw error::custom(ret, "WaitForSingleObject");
312  }
313 }
314 
315 void Process::terminate(int ec) const {
316  if (!::TerminateProcess(static_cast<HANDLE>(m_handle), static_cast<UINT>(ec))) {
317  throw error::windows(GetLastError(), "TerminateProcess");
318  }
319 }
320 
321 void Process::shut_down(int ec) const {
322  terminate(ec);
323  wait();
324 }
325 
327  DWORD ec = 0;
328 
329  const auto ret = ::GetExitCodeProcess(static_cast<HANDLE>(m_handle), &ec);
330 
331  if (!ret) {
332  throw error::windows(GetLastError(), "GetExitCodeProcess");
333  }
334 
335  if (ec == STILL_ACTIVE) {
336  throw std::runtime_error{"Attempted to query the exit code of a running process"};
337  }
338 
339  return static_cast<int>(ec);
340 }
341 
342 std::string Process::get_exe_path() const {
343  if (m_handle.get() == ::GetCurrentProcess()) {
344  return get_current_exe_path();
345  } else {
346  return winapi::get_exe_path(m_handle);
347  }
348 }
349 
350 HMODULE Process::get_exe_module() {
351  const auto module = ::GetModuleHandleW(NULL);
352  if (module == NULL) {
353  throw error::windows(GetLastError(), "GetModuleHandleW");
354  }
355  return module;
356 }
357 
358 std::string Process::get_resource_string(uint32_t id) {
359  wchar_t* s = nullptr;
360 
361  const auto nch = ::LoadStringW(get_exe_module(), id, reinterpret_cast<wchar_t*>(&s), 0);
362 
363  if (nch <= 0) {
364  throw error::windows(GetLastError(), "LoadStringW");
365  }
366 
367  return narrow(s, nch * sizeof(wchar_t));
368 }
369 
371  const auto module = get_exe_module();
372 
373  const auto src = ::FindResourceA(module, MAKEINTRESOURCEA(id), RT_RCDATA);
374 
375  if (src == NULL) {
376  throw error::windows(GetLastError(), "FindResourceA");
377  }
378 
379  const auto resource = ::LoadResource(module, src);
380 
381  if (resource == NULL) {
382  throw error::windows(GetLastError(), "LoadResource");
383  }
384 
385  const auto data = ::LockResource(resource);
386 
387  if (data == NULL) {
388  std::ostringstream oss;
389  oss << "Couldn't get data pointer for resource with ID " << id;
390  throw std::runtime_error{oss.str()};
391  }
392 
393  const auto nb = ::SizeofResource(module, src);
394 
395  return {data, nb};
396 }
397 
398 Process::Process(Handle&& handle) : Process{::GetProcessId(handle.get()), std::move(handle)} {}
399 
400 Process::Process(ID id, Handle&& handle) : m_id{id}, m_handle{std::move(handle)} {}
401 
402 } // namespace winapi
Command line for the current process or for launching new processes.
Definition: cmd_line.hpp:21
HANDLE wrapper.
Definition: handle.hpp:25
Create a new process or open an existing process.
Definition: process.hpp:54
void terminate(int ec=0) const
Definition: process.cpp:315
void shut_down(int ec=0) const
Definition: process.cpp:321
void wait() const
Definition: process.cpp:301
int get_exit_code() const
Definition: process.cpp:326
static Process open(ID id, DWORD permissions=default_permissions())
Definition: process.cpp:269
static Resource get_resource(uint32_t id)
Definition: process.cpp:370
static Process current()
Definition: process.cpp:265
static DWORD read_permissions()
Definition: process.cpp:281
static DWORD default_permissions()
Definition: process.cpp:277
bool is_running() const
Definition: process.cpp:285
static Process create(ProcessParameters)
Definition: process.cpp:241
std::string get_exe_path() const
Definition: process.cpp:342
static Process shell(const ShellParameters &)
Definition: process.cpp:256
static std::string get_resource_string(uint32_t id)
Definition: process.cpp:358
static Process open_r(ID)
Definition: process.cpp:273
Make std::system_error work with GetLastError().
Process parameters for Process::create().
Definition: process.hpp:24
Resources embedded in a PE (Portable Executable).
Definition: resource.hpp:15
Process parameters for Process::shell().
Definition: process.hpp:39
Child process IO settings.
Definition: process_io.hpp:61