FreeRDP
Loading...
Searching...
No Matches
process.c
1
21#include <winpr/config.h>
22
23#include <winpr/handle.h>
24#include "../handle/nonehandle.h"
25
26#include <winpr/thread.h>
27#include <winpr/wlog.h>
28
53#ifndef _WIN32
54
55#include <winpr/assert.h>
56#include <winpr/crt.h>
57#include <winpr/path.h>
58#include <winpr/environment.h>
59
60#include <grp.h>
61
62#include <signal.h>
63#include <sys/types.h>
64#include <sys/wait.h>
65
66#ifdef __linux__
67#include <sys/syscall.h>
68#include <fcntl.h>
69#include <errno.h>
70#endif /* __linux__ */
71
72#include "thread.h"
73
74#include "../security/security.h"
75
76#ifndef NSIG
77#ifdef SIGMAX
78#define NSIG SIGMAX
79#else
80#define NSIG 64
81#endif
82#endif
83
99static char* FindApplicationPath(char* application)
100{
101 LPCSTR pathName = "PATH";
102 char* path = nullptr;
103 char* save = nullptr;
104 DWORD nSize = 0;
105 LPSTR lpSystemPath = nullptr;
106 char* filename = nullptr;
107
108 if (!application)
109 return nullptr;
110
111 if (application[0] == '/')
112 return _strdup(application);
113
114 nSize = GetEnvironmentVariableA(pathName, nullptr, 0);
115
116 if (!nSize)
117 return _strdup(application);
118
119 lpSystemPath = (LPSTR)malloc(nSize);
120
121 if (!lpSystemPath)
122 return nullptr;
123
124 if (GetEnvironmentVariableA(pathName, lpSystemPath, nSize) != nSize - 1)
125 {
126 free(lpSystemPath);
127 return nullptr;
128 }
129
130 save = nullptr;
131 path = strtok_s(lpSystemPath, ":", &save);
132
133 while (path)
134 {
135 filename = GetCombinedPath(path, application);
136
137 if (winpr_PathFileExists(filename))
138 {
139 break;
140 }
141
142 free(filename);
143 filename = nullptr;
144 path = strtok_s(nullptr, ":", &save);
145 }
146
147 free(lpSystemPath);
148 return filename;
149}
150
151static HANDLE CreateProcessHandle(pid_t pid);
152static BOOL ProcessHandleCloseHandle(HANDLE handle);
153
154static BOOL CreateProcessExA(HANDLE hToken, WINPR_ATTR_UNUSED DWORD dwLogonFlags,
155 LPCSTR lpApplicationName, WINPR_ATTR_UNUSED LPSTR lpCommandLine,
156 WINPR_ATTR_UNUSED LPSECURITY_ATTRIBUTES lpProcessAttributes,
157 WINPR_ATTR_UNUSED LPSECURITY_ATTRIBUTES lpThreadAttributes,
158 WINPR_ATTR_UNUSED BOOL bInheritHandles,
159 WINPR_ATTR_UNUSED DWORD dwCreationFlags, LPVOID lpEnvironment,
160 LPCSTR lpCurrentDirectory, LPSTARTUPINFOA lpStartupInfo,
161 LPPROCESS_INFORMATION lpProcessInformation)
162{
163 pid_t pid = 0;
164 int numArgs = 0;
165 LPSTR* pArgs = nullptr;
166 char** envp = nullptr;
167 char* filename = nullptr;
168 HANDLE thread = nullptr;
169 HANDLE process = nullptr;
170 WINPR_ACCESS_TOKEN* token = nullptr;
171 LPTCH lpszEnvironmentBlock = nullptr;
172 BOOL ret = FALSE;
173 sigset_t oldSigMask;
174 sigset_t newSigMask;
175 BOOL restoreSigMask = FALSE;
176 numArgs = 0;
177 lpszEnvironmentBlock = nullptr;
178 /* https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessa
179 */
180
181 if (lpCommandLine && lpApplicationName)
182 {
183 char* str = nullptr;
184 size_t len = 0;
185 winpr_asprintf(&str, &len, "%s %s", lpApplicationName, lpCommandLine);
186 if (!str)
187 return FALSE;
188 pArgs = CommandLineToArgvA(str, &numArgs);
189 free(str);
190 }
191 else if (lpCommandLine)
192 pArgs = CommandLineToArgvA(lpCommandLine, &numArgs);
193 else
194 pArgs = CommandLineToArgvA(lpApplicationName, &numArgs);
195
196 if (!pArgs)
197 return FALSE;
198
199 token = (WINPR_ACCESS_TOKEN*)hToken;
200
201 if (lpEnvironment)
202 {
203 envp = EnvironmentBlockToEnvpA(lpEnvironment);
204 }
205 else
206 {
207 lpszEnvironmentBlock = GetEnvironmentStrings();
208
209 if (!lpszEnvironmentBlock)
210 goto finish;
211
212 envp = EnvironmentBlockToEnvpA(lpszEnvironmentBlock);
213 }
214
215 if (!envp)
216 goto finish;
217
218 filename = FindApplicationPath(pArgs[0]);
219
220 if (nullptr == filename)
221 goto finish;
222
223 /* block all signals so that the child can safely reset the caller's handlers */
224 sigfillset(&newSigMask);
225 restoreSigMask = !pthread_sigmask(SIG_SETMASK, &newSigMask, &oldSigMask);
226 /* fork and exec */
227 pid = fork();
228
229 if (pid < 0)
230 {
231 /* fork failure */
232 goto finish;
233 }
234
235 if (pid == 0)
236 {
237 /* child process */
238#ifndef __sun
239 int maxfd = 0;
240#endif
241 sigset_t set = WINPR_C_ARRAY_INIT;
242 struct sigaction act = WINPR_C_ARRAY_INIT;
243 /* set default signal handlers */
244 act.sa_handler = SIG_DFL;
245 act.sa_flags = 0;
246 sigemptyset(&act.sa_mask);
247
248 for (int sig = 1; sig < NSIG; sig++)
249 sigaction(sig, &act, nullptr);
250
251 /* unblock all signals */
252 sigfillset(&set);
253 pthread_sigmask(SIG_UNBLOCK, &set, nullptr);
254
255 if (lpStartupInfo)
256 {
257 int handle_fd = 0;
258 handle_fd = winpr_Handle_getFd(lpStartupInfo->hStdOutput);
259
260 if (handle_fd != -1)
261 dup2(handle_fd, STDOUT_FILENO);
262
263 handle_fd = winpr_Handle_getFd(lpStartupInfo->hStdError);
264
265 if (handle_fd != -1)
266 dup2(handle_fd, STDERR_FILENO);
267
268 handle_fd = winpr_Handle_getFd(lpStartupInfo->hStdInput);
269
270 if (handle_fd != -1)
271 dup2(handle_fd, STDIN_FILENO);
272 }
273
274#ifdef __sun
275 closefrom(3);
276#else
277#ifdef F_MAXFD // on some BSD derivates
278 maxfd = fcntl(0, F_MAXFD);
279#else
280 {
281 const long rc = sysconf(_SC_OPEN_MAX);
282 if ((rc < INT32_MIN) || (rc > INT32_MAX))
283 goto finish;
284 maxfd = (int)rc;
285 }
286#endif
287
288 for (int fd = 3; fd < maxfd; fd++)
289 close(fd);
290
291#endif // __sun
292
293 if (token)
294 {
295 if (token->GroupId)
296 {
297 int rc = setgid((gid_t)token->GroupId);
298
299 if (rc < 0)
300 {
301 }
302 else
303 {
304 initgroups(token->Username, (gid_t)token->GroupId);
305 }
306 }
307
308 if (token->UserId)
309 {
310 int rc = setuid((uid_t)token->UserId);
311 if (rc != 0)
312 goto finish;
313 }
314 }
315
316 /* TODO: add better cwd handling and error checking */
317 if (lpCurrentDirectory && strlen(lpCurrentDirectory) > 0)
318 {
319 int rc = chdir(lpCurrentDirectory);
320 if (rc != 0)
321 goto finish;
322 }
323
324 if (execve(filename, pArgs, envp) < 0)
325 {
326 /* execve failed - end the process */
327 _exit(1);
328 }
329 }
330 else
331 {
332 /* parent process */
333 }
334
335 process = CreateProcessHandle(pid);
336
337 if (!process)
338 {
339 goto finish;
340 }
341
342 thread = CreateNoneHandle();
343
344 if (!thread)
345 {
346 ProcessHandleCloseHandle(process);
347 goto finish;
348 }
349
350 lpProcessInformation->hProcess = process;
351 lpProcessInformation->hThread = thread;
352 lpProcessInformation->dwProcessId = (DWORD)pid;
353 lpProcessInformation->dwThreadId = (DWORD)pid;
354 ret = TRUE;
355finish:
356
357 /* restore caller's original signal mask */
358 if (restoreSigMask)
359 pthread_sigmask(SIG_SETMASK, &oldSigMask, nullptr);
360
361 free(filename);
362 free((void*)pArgs);
363
364 if (lpszEnvironmentBlock)
365 FreeEnvironmentStrings(lpszEnvironmentBlock);
366
367 if (envp)
368 {
369 int i = 0;
370
371 while (envp[i])
372 {
373 free(envp[i]);
374 i++;
375 }
376
377 free((void*)envp);
378 }
379
380 return ret;
381}
382
383BOOL CreateProcessA(LPCSTR lpApplicationName, LPSTR lpCommandLine,
384 LPSECURITY_ATTRIBUTES lpProcessAttributes,
385 LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles,
386 DWORD dwCreationFlags, LPVOID lpEnvironment, LPCSTR lpCurrentDirectory,
387 LPSTARTUPINFOA lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation)
388{
389 return CreateProcessExA(nullptr, 0, lpApplicationName, lpCommandLine, lpProcessAttributes,
390 lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment,
391 lpCurrentDirectory, lpStartupInfo, lpProcessInformation);
392}
393
394BOOL CreateProcessW(WINPR_ATTR_UNUSED LPCWSTR lpApplicationName,
395 WINPR_ATTR_UNUSED LPWSTR lpCommandLine,
396 WINPR_ATTR_UNUSED LPSECURITY_ATTRIBUTES lpProcessAttributes,
397 WINPR_ATTR_UNUSED LPSECURITY_ATTRIBUTES lpThreadAttributes,
398 WINPR_ATTR_UNUSED BOOL bInheritHandles, WINPR_ATTR_UNUSED DWORD dwCreationFlags,
399 WINPR_ATTR_UNUSED LPVOID lpEnvironment,
400 WINPR_ATTR_UNUSED LPCWSTR lpCurrentDirectory,
401 WINPR_ATTR_UNUSED LPSTARTUPINFOW lpStartupInfo,
402 WINPR_ATTR_UNUSED LPPROCESS_INFORMATION lpProcessInformation)
403{
404 WINPR_ASSERT(lpStartupInfo);
405 WINPR_ASSERT(lpProcessInformation);
406
407 LPSTR lpApplicationNameA = nullptr;
408 LPSTR lpCommandLineA = nullptr;
409 LPSTR lpCurrentDirectoryA = nullptr;
410 STARTUPINFOA StartupInfoA = { .cb = sizeof(STARTUPINFOA),
411 .lpReserved = nullptr,
412 .lpDesktop = nullptr,
413 .lpTitle = nullptr,
414 .dwX = lpStartupInfo->dwX,
415 .dwY = lpStartupInfo->dwY,
416 .dwXSize = lpStartupInfo->dwXSize,
417 .dwYSize = lpStartupInfo->dwYSize,
418 .dwXCountChars = lpStartupInfo->dwXCountChars,
419 .dwYCountChars = lpStartupInfo->dwYCountChars,
420 .dwFillAttribute = lpStartupInfo->dwFillAttribute,
421 .dwFlags = lpStartupInfo->dwFlags,
422 .wShowWindow = lpStartupInfo->wShowWindow,
423 .cbReserved2 = lpStartupInfo->cbReserved2,
424 .lpReserved2 = lpStartupInfo->lpReserved2,
425 .hStdInput = lpStartupInfo->hStdInput,
426 .hStdOutput = lpStartupInfo->hStdOutput,
427 .hStdError = lpStartupInfo->hStdError };
428
429 BOOL rc = FALSE;
430 if (lpApplicationName)
431 {
432 lpApplicationNameA = ConvertWCharToUtf8Alloc(lpApplicationName, nullptr);
433 if (!lpApplicationNameA)
434 goto fail;
435 }
436 if (lpCommandLine)
437 {
438 lpCommandLineA = ConvertWCharToUtf8Alloc(lpCommandLine, nullptr);
439 if (!lpCommandLineA)
440 goto fail;
441 }
442 if (lpCurrentDirectory)
443 {
444 lpCurrentDirectoryA = ConvertWCharToUtf8Alloc(lpCurrentDirectory, nullptr);
445 if (!lpCurrentDirectoryA)
446 goto fail;
447 }
448 if (lpStartupInfo->lpReserved)
449 {
450 StartupInfoA.lpReserved = ConvertWCharToUtf8Alloc(lpStartupInfo->lpReserved, nullptr);
451 if (!StartupInfoA.lpReserved)
452 goto fail;
453 }
454 if (lpStartupInfo->lpDesktop)
455 {
456 StartupInfoA.lpDesktop = ConvertWCharToUtf8Alloc(lpStartupInfo->lpDesktop, nullptr);
457 if (!StartupInfoA.lpDesktop)
458 goto fail;
459 }
460 if (lpStartupInfo->lpTitle)
461 {
462 StartupInfoA.lpTitle = ConvertWCharToUtf8Alloc(lpStartupInfo->lpTitle, nullptr);
463 if (!StartupInfoA.lpTitle)
464 goto fail;
465 }
466
467 rc = CreateProcessA(lpApplicationNameA, lpCommandLineA, lpProcessAttributes, lpThreadAttributes,
468 bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectoryA,
469 &StartupInfoA, lpProcessInformation);
470fail:
471 free(lpApplicationNameA);
472 free(lpCommandLineA);
473 free(lpCurrentDirectoryA);
474 free(StartupInfoA.lpDesktop);
475 free(StartupInfoA.lpReserved);
476 free(StartupInfoA.lpTitle);
477 return rc;
478}
479
480BOOL CreateProcessAsUserA(HANDLE hToken, LPCSTR lpApplicationName, LPSTR lpCommandLine,
481 LPSECURITY_ATTRIBUTES lpProcessAttributes,
482 LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles,
483 DWORD dwCreationFlags, LPVOID lpEnvironment, LPCSTR lpCurrentDirectory,
484 LPSTARTUPINFOA lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation)
485{
486 return CreateProcessExA(hToken, 0, lpApplicationName, lpCommandLine, lpProcessAttributes,
487 lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment,
488 lpCurrentDirectory, lpStartupInfo, lpProcessInformation);
489}
490
491BOOL CreateProcessAsUserW(WINPR_ATTR_UNUSED HANDLE hToken,
492 WINPR_ATTR_UNUSED LPCWSTR lpApplicationName,
493 WINPR_ATTR_UNUSED LPWSTR lpCommandLine,
494 WINPR_ATTR_UNUSED LPSECURITY_ATTRIBUTES lpProcessAttributes,
495 WINPR_ATTR_UNUSED LPSECURITY_ATTRIBUTES lpThreadAttributes,
496 WINPR_ATTR_UNUSED BOOL bInheritHandles,
497 WINPR_ATTR_UNUSED DWORD dwCreationFlags,
498 WINPR_ATTR_UNUSED LPVOID lpEnvironment,
499 WINPR_ATTR_UNUSED LPCWSTR lpCurrentDirectory,
500 WINPR_ATTR_UNUSED LPSTARTUPINFOW lpStartupInfo,
501 WINPR_ATTR_UNUSED LPPROCESS_INFORMATION lpProcessInformation)
502{
503 WLog_ERR("TODO", "TODO: implement");
504 return FALSE;
505}
506
507BOOL CreateProcessWithLogonA(
508 WINPR_ATTR_UNUSED LPCSTR lpUsername, WINPR_ATTR_UNUSED LPCSTR lpDomain,
509 WINPR_ATTR_UNUSED LPCSTR lpPassword, WINPR_ATTR_UNUSED DWORD dwLogonFlags,
510 WINPR_ATTR_UNUSED LPCSTR lpApplicationName, WINPR_ATTR_UNUSED LPSTR lpCommandLine,
511 WINPR_ATTR_UNUSED DWORD dwCreationFlags, WINPR_ATTR_UNUSED LPVOID lpEnvironment,
512 WINPR_ATTR_UNUSED LPCSTR lpCurrentDirectory, WINPR_ATTR_UNUSED LPSTARTUPINFOA lpStartupInfo,
513 WINPR_ATTR_UNUSED LPPROCESS_INFORMATION lpProcessInformation)
514{
515 WLog_ERR("TODO", "TODO: implement");
516 return FALSE;
517}
518
519BOOL CreateProcessWithLogonW(
520 WINPR_ATTR_UNUSED LPCWSTR lpUsername, WINPR_ATTR_UNUSED LPCWSTR lpDomain,
521 WINPR_ATTR_UNUSED LPCWSTR lpPassword, WINPR_ATTR_UNUSED DWORD dwLogonFlags,
522 WINPR_ATTR_UNUSED LPCWSTR lpApplicationName, WINPR_ATTR_UNUSED LPWSTR lpCommandLine,
523 WINPR_ATTR_UNUSED DWORD dwCreationFlags, WINPR_ATTR_UNUSED LPVOID lpEnvironment,
524 WINPR_ATTR_UNUSED LPCWSTR lpCurrentDirectory, WINPR_ATTR_UNUSED LPSTARTUPINFOW lpStartupInfo,
525 WINPR_ATTR_UNUSED LPPROCESS_INFORMATION lpProcessInformation)
526{
527 WLog_ERR("TODO", "TODO: implement");
528 return FALSE;
529}
530
531BOOL CreateProcessWithTokenA(WINPR_ATTR_UNUSED HANDLE hToken, WINPR_ATTR_UNUSED DWORD dwLogonFlags,
532 LPCSTR lpApplicationName, LPSTR lpCommandLine, DWORD dwCreationFlags,
533 LPVOID lpEnvironment, LPCSTR lpCurrentDirectory,
534 LPSTARTUPINFOA lpStartupInfo,
535 LPPROCESS_INFORMATION lpProcessInformation)
536{
537 return CreateProcessExA(nullptr, 0, lpApplicationName, lpCommandLine, nullptr, nullptr, FALSE,
538 dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo,
539 lpProcessInformation);
540}
541
542BOOL CreateProcessWithTokenW(WINPR_ATTR_UNUSED HANDLE hToken, WINPR_ATTR_UNUSED DWORD dwLogonFlags,
543 WINPR_ATTR_UNUSED LPCWSTR lpApplicationName,
544 WINPR_ATTR_UNUSED LPWSTR lpCommandLine,
545 WINPR_ATTR_UNUSED DWORD dwCreationFlags,
546 WINPR_ATTR_UNUSED LPVOID lpEnvironment,
547 WINPR_ATTR_UNUSED LPCWSTR lpCurrentDirectory,
548 WINPR_ATTR_UNUSED LPSTARTUPINFOW lpStartupInfo,
549 WINPR_ATTR_UNUSED LPPROCESS_INFORMATION lpProcessInformation)
550{
551 WLog_ERR("TODO", "TODO: implement");
552 return FALSE;
553}
554
555VOID ExitProcess(UINT uExitCode)
556{
557 // NOLINTNEXTLINE(concurrency-mt-unsafe)
558 exit((int)uExitCode);
559}
560
561BOOL GetExitCodeProcess(HANDLE hProcess, LPDWORD lpExitCode)
562{
563 WINPR_PROCESS* process = nullptr;
564
565 if (!hProcess)
566 return FALSE;
567
568 if (!lpExitCode)
569 return FALSE;
570
571 process = (WINPR_PROCESS*)hProcess;
572 *lpExitCode = process->dwExitCode;
573 return TRUE;
574}
575
576HANDLE _GetCurrentProcess(VOID)
577{
578 WLog_ERR("TODO", "TODO: implement");
579 return nullptr;
580}
581
582DWORD GetCurrentProcessId(VOID)
583{
584 return ((DWORD)getpid());
585}
586
587BOOL TerminateProcess(HANDLE hProcess, WINPR_ATTR_UNUSED UINT uExitCode)
588{
589 WINPR_PROCESS* process = nullptr;
590 process = (WINPR_PROCESS*)hProcess;
591
592 if (!process || (process->pid <= 0))
593 return FALSE;
594
595 if (kill(process->pid, SIGTERM))
596 return FALSE;
597
598 return TRUE;
599}
600
601static BOOL ProcessHandleCloseHandle(HANDLE handle)
602{
603 WINPR_PROCESS* process = (WINPR_PROCESS*)handle;
604 WINPR_ASSERT(process);
605 if (process->fd >= 0)
606 {
607 close(process->fd);
608 process->fd = -1;
609 }
610 free(process);
611 return TRUE;
612}
613
614static BOOL ProcessHandleIsHandle(HANDLE handle)
615{
616 return WINPR_HANDLE_IS_HANDLED(handle, HANDLE_TYPE_PROCESS, FALSE);
617}
618
619static int ProcessGetFd(HANDLE handle)
620{
621 WINPR_PROCESS* process = (WINPR_PROCESS*)handle;
622
623 if (!ProcessHandleIsHandle(handle))
624 return -1;
625
626 return process->fd;
627}
628
629static DWORD ProcessCleanupHandle(HANDLE handle)
630{
631 WINPR_PROCESS* process = (WINPR_PROCESS*)handle;
632
633 WINPR_ASSERT(process);
634 if (process->fd > 0)
635 {
636 if (waitpid(process->pid, &process->status, WNOHANG) == process->pid)
637 {
638 if (WIFEXITED(process->status))
639 process->dwExitCode = (DWORD)WEXITSTATUS(process->status);
640 else if (WIFSIGNALED(process->status))
641 process->dwExitCode = (DWORD)(128 + WTERMSIG(process->status));
642 else
643 process->dwExitCode = (DWORD)process->status;
644 }
645 }
646 return WAIT_OBJECT_0;
647}
648
649static HANDLE_OPS ops = { ProcessHandleIsHandle,
650 ProcessHandleCloseHandle,
651 ProcessGetFd,
652 ProcessCleanupHandle, /* CleanupHandle */
653 nullptr,
654 nullptr,
655 nullptr,
656 nullptr,
657 nullptr,
658 nullptr,
659 nullptr,
660 nullptr,
661 nullptr,
662 nullptr,
663 nullptr,
664 nullptr,
665 nullptr,
666 nullptr,
667 nullptr,
668 nullptr,
669 nullptr };
670
671static int pidfd_open(pid_t pid)
672{
673#ifdef __linux__
674#if !defined(__NR_pidfd_open)
675#define __NR_pidfd_open 434
676#endif /* __NR_pidfd_open */
677
678#ifndef PIDFD_NONBLOCK
679#define PIDFD_NONBLOCK O_NONBLOCK
680#endif /* PIDFD_NONBLOCK */
681
682 long fd = syscall(__NR_pidfd_open, pid, PIDFD_NONBLOCK);
683 if (fd < 0 && errno == EINVAL)
684 {
685 /* possibly PIDFD_NONBLOCK is not supported, let's try to create a pidfd and set it
686 * non blocking afterward */
687 int flags = 0;
688 fd = syscall(__NR_pidfd_open, pid, 0);
689 if ((fd < 0) || (fd > INT32_MAX))
690 return -1;
691
692 flags = fcntl((int)fd, F_GETFL);
693 if ((flags < 0) || fcntl((int)fd, F_SETFL, flags | O_NONBLOCK) < 0)
694 {
695 close((int)fd);
696 fd = -1;
697 }
698 }
699 if ((fd < 0) || (fd > INT32_MAX))
700 return -1;
701 return (int)fd;
702#else
703 return -1;
704#endif
705}
706
707HANDLE CreateProcessHandle(pid_t pid)
708{
709 WINPR_PROCESS* process = nullptr;
710 process = (WINPR_PROCESS*)calloc(1, sizeof(WINPR_PROCESS));
711
712 if (!process)
713 return nullptr;
714
715 process->pid = pid;
716 process->common.Type = HANDLE_TYPE_PROCESS;
717 process->common.ops = &ops;
718 process->fd = pidfd_open(pid);
719 if (process->fd >= 0)
720 process->common.Mode = WINPR_FD_READ;
721 return (HANDLE)process;
722}
723
724#endif