DotNetDupe 4.0.6
C++17/20 Implementation of the .NET Base Class Library (BCL)
Loading...
Searching...
No Matches
Process.cpp
Go to the documentation of this file.
1#include "pch.h"
9
10#if defined(_WIN32)
11#include <windows.h>
12#include <tlhelp32.h>
13#include "Win32Internal.h"
14using namespace DotNetDupe::System::Internal;
15#else
16#include <unistd.h>
17#include <dirent.h>
18#include <sys/wait.h>
19#include <signal.h>
20#include <spawn.h>
21#include <vector>
22#include <string>
23#include <fstream>
24#include <algorithm>
25#include <cctype>
26#include <cerrno>
27extern char** environ;
28#endif
29
30namespace DotNetDupe {
31 namespace System {
32 namespace Diagnostics {
33
36
38 : FileName(sFileName), CreateNoWindow(false), UseShellExecute(false) {}
39
40 ProcessStartInfo::ProcessStartInfo(const String& sFileName, const String& sArguments)
41 : FileName(sFileName), Arguments(sArguments), CreateNoWindow(false), UseShellExecute(false) {}
42
44 : m_iId(0), m_iExitCode(0), m_bHasExited(true), m_pProcessHandle(nullptr) {}
45
46 Process::Process(int iId, const String& sProcessName, void* pProcessHandle)
47 : m_iId(iId), m_sProcessName(sProcessName), m_iExitCode(0), m_bHasExited(false), m_pProcessHandle(pProcessHandle) {}
48
50#if defined(_WIN32)
51 if (m_pProcessHandle != nullptr) {
52 CloseHandle((HANDLE)m_pProcessHandle);
53 }
54#endif
55 }
56
57#if defined(_WIN32)
58 static std::wstring BuildCommandLine(const ProcessStartInfo& info) {
59 std::wstring sWFileName = StringConvertInternal::Utf8ToWChar(info.FileName.GetRawString());
60 std::wstring sWArgs = StringConvertInternal::Utf8ToWChar(info.Arguments.GetRawString());
61 std::wstring sCmd = L"\"" + sWFileName + L"\"";
62 if (info.Arguments.GetLength() > 0) {
63 sCmd += L" " + sWArgs;
64 }
65 return sCmd;
66 }
67
68 static bool CreateWin32Process(const ProcessStartInfo& info, PROCESS_INFORMATION& pi) {
69 STARTUPINFOW si;
70 ZeroMemory(&si, sizeof(si));
71 si.cb = sizeof(si);
72 ZeroMemory(&pi, sizeof(pi));
73
74 std::wstring sCmd = BuildCommandLine(info);
75 DWORD dwFlags = info.CreateNoWindow ? CREATE_NO_WINDOW : 0;
76 if (::CreateProcessW(NULL, (LPWSTR)sCmd.c_str(), NULL, NULL, FALSE, dwFlags, NULL, NULL, &si, &pi)) {
77 return true;
78 }
79
80 DWORD dwErr = ::GetLastError();
81 if (dwErr == ERROR_FILE_NOT_FOUND || dwErr == ERROR_PATH_NOT_FOUND) {
82 throw IO::FileNotFoundException("The system cannot find the file specified.");
83 }
84 if (dwErr == ERROR_ACCESS_DENIED) {
85 throw UnauthorizedAccessException("Access denied starting process. Higher privileges required.");
86 }
87 return false;
88 }
89#else
90 static std::vector<std::string> ParsePosixArgs(const ProcessStartInfo& info) {
91 std::vector<std::string> argStrings;
92 argStrings.push_back(info.FileName.GetRawString());
93
94 std::string sArgs = info.Arguments.GetRawString();
95 std::string sCurrentArg;
96 bool bInQuotes = false;
97
98 for (size_t i = 0; i < sArgs.length(); ++i) {
99 char c = sArgs[i];
100 if (c == '\"') {
101 bInQuotes = !bInQuotes;
102 } else if (c == ' ' && !bInQuotes) {
103 if (!sCurrentArg.empty()) {
104 argStrings.push_back(sCurrentArg);
105 sCurrentArg.clear();
106 }
107 } else {
108 sCurrentArg += c;
109 }
110 }
111 if (!sCurrentArg.empty()) argStrings.push_back(sCurrentArg);
112 return argStrings;
113 }
114
115 static bool SpawnPosixProcess(const ProcessStartInfo& info, pid_t& pid) {
116 auto argStrings = ParsePosixArgs(info);
117 std::vector<char*> argv;
118 for (auto& s : argStrings) argv.push_back((char*)s.c_str());
119 argv.push_back(NULL);
120
121 int err = posix_spawn(&pid, info.FileName.GetRawString(), NULL, NULL, argv.data(), environ);
122 if (err == 0) return true;
123 if (err == ENOENT) throw IO::FileNotFoundException("The system cannot find the file specified.");
124 if (err == EACCES || err == EPERM) throw UnauthorizedAccessException("Access denied starting process.");
125 return false;
126 }
127#endif
128
129#if defined(_WIN32)
130 static bool StartProcessPlatform(const ProcessStartInfo& info, int& iId, void*& pHandle, bool& bExited) {
131 PROCESS_INFORMATION pi;
132 if (CreateWin32Process(info, pi)) {
133 iId = static_cast<int>(pi.dwProcessId);
134 pHandle = (void*)pi.hProcess;
135 ::CloseHandle(pi.hThread);
136 bExited = false;
137 return true;
138 }
139 return false;
140 }
141
142 static bool WaitForExitPlatform(void* pHandle, int iMilliseconds) {
143 DWORD dwTimeout = (iMilliseconds == -1) ? INFINITE : (DWORD)iMilliseconds;
144 return (WaitForSingleObject((HANDLE)pHandle, dwTimeout) == WAIT_OBJECT_0);
145 }
146
147 static void KillProcessPlatform(void* pHandle) {
148 if (!::TerminateProcess((HANDLE)pHandle, 1)) {
149 DWORD dwErr = ::GetLastError();
150 if (dwErr == ERROR_ACCESS_DENIED) throw UnauthorizedAccessException("Cannot terminate target process: access denied.");
151 if (dwErr == ERROR_INVALID_HANDLE) throw InvalidOperationException("Process has already exited.");
152 }
153 }
154
155 static bool RefreshProcessPlatform(void* pHandle, int& iExitCode) {
156 DWORD dwCode;
157 if (GetExitCodeProcess((HANDLE)pHandle, &dwCode) && dwCode != STILL_ACTIVE) {
158 iExitCode = static_cast<int>(dwCode);
159 return true;
160 }
161 return false;
162 }
163#else
164 static bool StartProcessPlatform(const ProcessStartInfo& info, int& iId, void*& pHandle, bool& bExited) {
165 pid_t pid;
166 if (SpawnPosixProcess(info, pid)) {
167 iId = static_cast<int>(pid);
168 pHandle = (void*)(intptr_t)pid;
169 bExited = false;
170 return true;
171 }
172 return false;
173 }
174
175 static bool WaitForExitPlatform(void* pHandle, int iMilliseconds, int& iExitCode) {
176 int iStatus;
177 pid_t pid = (pid_t)(intptr_t)pHandle;
178 if (iMilliseconds == -1) {
179 if (waitpid(pid, &iStatus, 0) == pid) {
180 iExitCode = WIFEXITED(iStatus) ? WEXITSTATUS(iStatus) : (WIFSIGNALED(iStatus) ? -WTERMSIG(iStatus) : 0);
181 return true;
182 }
183 return false;
184 }
185 int iElapsed = 0;
186 while (iElapsed < iMilliseconds) {
187 pid_t res = waitpid(pid, &iStatus, WNOHANG);
188 if (res == pid) {
189 iExitCode = WIFEXITED(iStatus) ? WEXITSTATUS(iStatus) : (WIFSIGNALED(iStatus) ? -WTERMSIG(iStatus) : 0);
190 return true;
191 }
192 if (res != 0 && errno == ECHILD) return true;
193 usleep(10000); iElapsed += 10;
194 }
195 return false;
196 }
197
198 static void KillProcessPlatform(void* pHandle, int& iExitCode) {
199 pid_t pid = (pid_t)(intptr_t)pHandle;
200 if (kill(pid, SIGKILL) != 0) {
201 if (errno == EPERM) throw UnauthorizedAccessException("Cannot terminate target process: access denied.");
202 if (errno == ESRCH) throw InvalidOperationException("Process has already exited.");
203 }
204 int iStatus;
205 if (waitpid(pid, &iStatus, 0) == pid) {
206 iExitCode = WIFEXITED(iStatus) ? WEXITSTATUS(iStatus) : (WIFSIGNALED(iStatus) ? -WTERMSIG(iStatus) : 0);
207 }
208 }
209
210 static bool RefreshProcessPlatform(void* pHandle, int& iExitCode) {
211 int iStatus;
212 pid_t res = waitpid((pid_t)(intptr_t)pHandle, &iStatus, WNOHANG);
213 if (res > 0) {
214 iExitCode = WIFEXITED(iStatus) ? WEXITSTATUS(iStatus) : (WIFSIGNALED(iStatus) ? -WTERMSIG(iStatus) : 0);
215 return true;
216 }
217 return (res == -1 && errno == ECHILD);
218 }
219#endif
220
222 if (m_objStartInfo.FileName.GetLength() == 0) return false;
223 return StartProcessPlatform(m_objStartInfo, m_iId, m_pProcessHandle, m_bHasExited);
224 }
225
227 return Start(ProcessStartInfo(sFileName));
228 }
229
230 SmartPointer<Process> Process::Start(const String& sFileName, const String& sArguments) {
231 return Start(ProcessStartInfo(sFileName, sArguments));
232 }
233
236 pProcess->SetStartInfo(objStartInfo);
237 try {
238 if (pProcess->Start()) return pProcess;
239 } catch (const IO::FileNotFoundException&) {
240 return SmartPointer<Process>(nullptr);
241 }
242 return SmartPointer<Process>(nullptr);
243 }
244
246 WaitForExit(-1);
247 }
248
249 bool Process::WaitForExit(int iMilliseconds) {
250 if (m_bHasExited || m_pProcessHandle == nullptr) return true;
251#if defined(_WIN32)
252 if (WaitForExitPlatform(m_pProcessHandle, iMilliseconds)) {
253 Refresh();
254 return true;
255 }
256 return false;
257#else
258 if (WaitForExitPlatform(m_pProcessHandle, iMilliseconds, m_iExitCode)) {
259 m_bHasExited = true;
260 return true;
261 }
262 return m_bHasExited;
263#endif
264 }
265
267 Refresh();
268 return m_bHasExited;
269 }
270
272 if (m_bHasExited || m_pProcessHandle == nullptr) return;
273#if defined(_WIN32)
274 KillProcessPlatform(m_pProcessHandle);
275 Refresh();
276#else
277 KillProcessPlatform(m_pProcessHandle, m_iExitCode);
278 m_bHasExited = true;
279#endif
280 }
281
283#if defined(_WIN32)
284 return static_cast<int>(::GetCurrentProcessId());
285#else
286 return static_cast<int>(getpid());
287#endif
288 }
289
290 void Process::Refresh() const {
291 if (m_bHasExited || m_pProcessHandle == nullptr) return;
292 if (RefreshProcessPlatform(m_pProcessHandle, m_iExitCode)) {
293 m_bHasExited = true;
294 }
295 }
296
297#if defined(_WIN32)
298 static bool QueryProcessNameById(int iProcessId, String& sOutName, HANDLE& hOutProc) {
300 hOutProc = ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, FALSE, static_cast<DWORD>(iProcessId));
301 if (hOutProc == NULL) return false;
302
304 WCHAR szPath[MAX_PATH] = { 0 }; DWORD dwLen = MAX_PATH;
305 if (::QueryFullProcessImageNameW(hOutProc, 0, szPath, &dwLen)) {
306 std::wstring ws(szPath);
307 size_t pos = ws.find_last_of(L"\\/");
308 std::wstring wsName = (pos != std::wstring::npos) ? ws.substr(pos + 1) : ws;
309 sOutName = StringConvertInternal::WCharToUtf8(wsName.c_str()).c_str();
310 return true;
311 }
312 return false;
313 }
314#else
315 static bool QueryLinuxProcessNameById(int iProcessId, String& sOutName) {
317 std::string sPath = "/proc/" + std::to_string(iProcessId) + "/comm";
318 std::ifstream commFile(sPath);
319 std::string sComm;
320
322 if (commFile.is_open() && std::getline(commFile, sComm)) {
323 sOutName = String(sComm.c_str());
324 return true;
325 }
326 return false;
327 }
328#endif
329
330#if defined(_WIN32)
333 HANDLE hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
334 if (hSnapshot == INVALID_HANDLE_VALUE) return;
335
337 PROCESSENTRY32W pe32; pe32.dwSize = sizeof(PROCESSENTRY32W);
338 if (::Process32FirstW(hSnapshot, &pe32)) {
339 do {
340 if (pe32.th32ProcessID == 0) continue;
341 lstProcs.Add(SmartPointer<Process>::NewShared(static_cast<int>(pe32.th32ProcessID), String(pe32.szExeFile), nullptr));
342 } while (::Process32NextW(hSnapshot, &pe32));
343 }
344
346 ::CloseHandle(hSnapshot);
347 }
348#else
349 static void EnumerateLinuxProcesses(Collections::Generic::List<SmartPointer<Process>>& lstProcs) {
350 DIR* dir = ::opendir("/proc");
351 if (!dir) return;
352 struct dirent* entry = nullptr;
353 while ((entry = ::readdir(dir)) != nullptr) {
354 if (entry->d_type == DT_DIR) {
355 std::string dname = entry->d_name;
356 if (std::all_of(dname.begin(), dname.end(), ::isdigit)) {
357 std::ifstream commFile("/proc/" + dname + "/comm");
358 std::string sComm;
359 if (commFile.is_open() && std::getline(commFile, sComm)) {
360 lstProcs.Add(SmartPointer<Process>::NewShared(std::stoi(dname), String(sComm.c_str()), nullptr));
361 }
362 }
363 }
364 }
365 ::closedir(dir);
366 }
367#endif
368
371#if defined(_WIN32)
372 EnumerateWin32Processes(lstProcs);
373#else
374 EnumerateLinuxProcesses(lstProcs);
375#endif
376 Array<SmartPointer<Process>> arrProcs(lstProcs.GetCount());
377 for (int i = 0; i < lstProcs.GetCount(); ++i) arrProcs[i] = lstProcs[i];
378 return arrProcs;
379 }
380
381 static bool FindProcessInSnapshot(int iProcessId, String& sOutName) {
382 auto arrProcs = Process::GetProcesses();
383 for (int i = 0; i < arrProcs.GetLength(); ++i) {
384 if (arrProcs[i]->GetId() == iProcessId) {
385 sOutName = arrProcs[i]->GetProcessName();
386 return true;
387 }
388 }
389 return false;
390 }
391
393 if (iProcessId <= 0) throw ArgumentException("Process ID must be greater than zero.");
394 String sName;
395#if defined(_WIN32)
396 HANDLE hProc = NULL;
397 if (!QueryProcessNameById(iProcessId, sName, hProc) && !FindProcessInSnapshot(iProcessId, sName)) {
398 throw ArgumentException("Process with specified ID is not running.");
399 }
400 return SmartPointer<Process>::NewShared(iProcessId, sName, hProc);
401#else
402 if (!QueryLinuxProcessNameById(iProcessId, sName) && !FindProcessInSnapshot(iProcessId, sName)) {
403 throw ArgumentException("Process with specified ID is not running.");
404 }
405 return SmartPointer<Process>::NewShared(iProcessId, sName, nullptr);
406#endif
407 }
408
412
413 static bool MatchProcessName(const String& sCandidate, const String& sTarget) {
414 if (sCandidate.Equals(sTarget)) return true;
415 String sCandNorm = sCandidate.EndsWith(".exe", true) ? sCandidate.Substring(0, sCandidate.GetLength() - 4) : sCandidate;
416 String sTargNorm = sTarget.EndsWith(".exe", true) ? sTarget.Substring(0, sTarget.GetLength() - 4) : sTarget;
417 return sCandNorm.ToLower().Equals(sTargNorm.ToLower());
418 }
419
421 if (sProcessName.IsEmpty()) return Array<SmartPointer<Process>>(0);
422 auto arrAll = GetProcesses();
424 for (int i = 0; i < arrAll.GetLength(); ++i) {
425 if (MatchProcessName(arrAll[i]->GetProcessName(), sProcessName)) {
426 lstMatches.Add(arrAll[i]);
427 }
428 }
429 Array<SmartPointer<Process>> arrResult(lstMatches.GetCount());
430 for (int i = 0; i < lstMatches.GetCount(); ++i) arrResult[i] = lstMatches[i];
431 return arrResult;
432 }
433 }
434 }
435}
Defines the exception thrown when an invalid argument is provided to a method.
The exception that is thrown when an attempt to access a file that does not exist on disk fails.
Defines the exception thrown when a method call is invalid for the object's current state.
Represents a strongly typed list of objects that can be accessed by index mirroring ....
Provides access to local and remote processes and enables you to start and stop local system processe...
Utility routines for high-performance UTF-8, UTF-16, and wide-character string conversions.
The exception that is thrown when the operating system denies access because of an I/O error or a spe...
ArgumentException(const String &sMessage)
Initializes a new instance of the ArgumentException class with a specified error message.
Provides methods for creating, manipulating, searching, and sorting arrays, thereby serving as the ba...
Definition Array.h:29
int GetLength() const
Gets the total number of elements in all dimensions of the Array.
Definition Array.h:142
Represents a strongly typed list of objects accessible by index.
Definition List.h:29
int GetCount() const
Gets the number of elements contained in the List.
Definition List.h:100
void Add(const T &item)
Adds an object to the end of the List.
Definition List.h:138
static int GetCurrentProcessId()
Gets the process identifier of the calling process.
Definition Process.cpp:282
bool Start()
Starts (or reuses) the process resource that is specified by the StartInfo property.
Definition Process.cpp:221
static Array< SmartPointer< Process > > GetProcesses()
Creates an array of new Process components and associates them with all active system processes.
Definition Process.cpp:369
String GetProcessName() const
Gets the name of the process.
Definition Process.h:89
static SmartPointer< Process > GetProcessById(int iProcessId)
Returns a Process component given the identifier of a process on the local computer.
Definition Process.cpp:392
void WaitForExit()
Instructs the Process component to wait indefinitely for the associated process to exit.
Definition Process.cpp:245
static Array< SmartPointer< Process > > GetProcessesByName(const String &sProcessName)
Creates an array of new Process components and associates them with all processes sharing the specifi...
Definition Process.cpp:420
void Kill()
Immediately stops the associated process.
Definition Process.cpp:271
bool GetHasExited() const
Gets a value indicating whether the associated process has been terminated.
Definition Process.cpp:266
static SmartPointer< Process > GetCurrentProcess()
Gets a new Process component and associates it with the currently active process.
Definition Process.cpp:409
Specifies a set of values that are used when you start a process.
Definition Process.h:17
The exception that is thrown when an attempt to access a file that does not exist on disk fails.
static std::wstring Utf8ToWChar(const char *pUtf8Str)
static std::string WCharToUtf8(const wchar_t *pWStr)
InvalidOperationException(const String &sMessage)
Initializes a new instance of the InvalidOperationException class with a specified error message.
A unified smart pointer that supports both unique and shared ownership semantics.
static SmartPointer< T > NewShared()
Creates a Shared SmartPointer, default constructing T.
Represents text as a sequence of UTF-8 code units with culture-invariant operations.
Definition String.h:74
String Substring(int iStartIndex) const
Definition String.cpp:659
String ToLower() const
Definition String.cpp:672
bool EndsWith(char ch, bool bIgnoreCase) const
Definition String.cpp:315
String()
Initializes a new instance of the String class to an empty string.
Definition String.cpp:64
static bool Equals(const String &sStr1, const String &sStr2)
Definition String.cpp:334
const char * GetRawString() const
Definition String.cpp:230
UnauthorizedAccessException()
Initializes a new instance of the UnauthorizedAccessException class with a default message.
Definition Exception.cpp:52
static void KillProcessPlatform(void *pHandle)
Definition Process.cpp:147
static bool WaitForExitPlatform(void *pHandle, int iMilliseconds)
Definition Process.cpp:142
static bool CreateWin32Process(const ProcessStartInfo &info, PROCESS_INFORMATION &pi)
Definition Process.cpp:68
static std::wstring BuildCommandLine(const ProcessStartInfo &info)
Definition Process.cpp:58
static bool MatchProcessName(const String &sCandidate, const String &sTarget)
Definition Process.cpp:413
static bool RefreshProcessPlatform(void *pHandle, int &iExitCode)
Definition Process.cpp:155
static bool FindProcessInSnapshot(int iProcessId, String &sOutName)
Definition Process.cpp:381
static void EnumerateWin32Processes(Collections::Generic::List< SmartPointer< Process > > &lstProcs)
Definition Process.cpp:331
static bool QueryProcessNameById(int iProcessId, String &sOutName, HANDLE &hOutProc)
Definition Process.cpp:298
static bool StartProcessPlatform(const ProcessStartInfo &info, int &iId, void *&pHandle, bool &bExited)
Definition Process.cpp:130