mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-07-16 07:39:56 +00:00
Fix some win32 definitions
You can now use psapi.dll and pdh.dll. Some TODOs for Windows have been cleared out. We might have a working load average for the platform that should help GNU Make work well.
This commit is contained in:
parent
e2e0b042c1
commit
c23b6ecc31
162 changed files with 847 additions and 153 deletions
|
@ -44,6 +44,8 @@ LIBC_CALLS_A_DIRECTDEPS = \
|
|||
LIBC_NT_IPHLPAPI \
|
||||
LIBC_NT_KERNEL32 \
|
||||
LIBC_NT_NTDLL \
|
||||
LIBC_NT_PDH \
|
||||
LIBC_NT_PSAPI \
|
||||
LIBC_NT_POWERPROF \
|
||||
LIBC_NT_WS2_32 \
|
||||
LIBC_STR \
|
||||
|
|
|
@ -31,7 +31,6 @@ int getloadavg(double *a, int n) {
|
|||
struct sysinfo si;
|
||||
if (!n) return 0;
|
||||
if (n < 0) return einval();
|
||||
if (IsWindows()) return enosys(); /* TODO(jart) */
|
||||
if (sysinfo(&si) == -1) return -1;
|
||||
if (n > 3) n = 3;
|
||||
for (i = 0; i < n; i++) {
|
||||
|
|
|
@ -21,7 +21,9 @@
|
|||
#include "libc/calls/struct/rusage.h"
|
||||
#include "libc/fmt/conv.h"
|
||||
#include "libc/nt/accounting.h"
|
||||
#include "libc/nt/process.h"
|
||||
#include "libc/nt/runtime.h"
|
||||
#include "libc/nt/struct/processmemorycounters.h"
|
||||
#include "libc/nt/thread.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "libc/sysv/consts/rusage.h"
|
||||
|
@ -32,6 +34,7 @@ textwindows int sys_getrusage_nt(int who, struct rusage *usage) {
|
|||
struct NtFileTime ExitFileTime;
|
||||
struct NtFileTime KernelFileTime;
|
||||
struct NtFileTime UserFileTime;
|
||||
struct NtProcessMemoryCountersEx memcount;
|
||||
if (!usage) return efault();
|
||||
if (who == 99) return enosys(); /* @see libc/sysv/consts.sh */
|
||||
bzero(usage, sizeof(*usage));
|
||||
|
@ -40,8 +43,14 @@ textwindows int sys_getrusage_nt(int who, struct rusage *usage) {
|
|||
&CreationFileTime, &ExitFileTime, &KernelFileTime, &UserFileTime)) {
|
||||
usage->ru_utime = FileTimeToTimeVal(UserFileTime);
|
||||
usage->ru_stime = FileTimeToTimeVal(KernelFileTime);
|
||||
return 0;
|
||||
} else {
|
||||
return __winerr();
|
||||
}
|
||||
if (GetProcessMemoryInfo(GetCurrentProcess(), &memcount, sizeof(memcount))) {
|
||||
usage->ru_maxrss = memcount.PeakWorkingSetSize;
|
||||
usage->ru_majflt = memcount.PageFaultCount;
|
||||
} else {
|
||||
return __winerr();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -29,8 +29,7 @@
|
|||
* @vforksafe
|
||||
*/
|
||||
bool isexecutable(const char *path) {
|
||||
/* execve() depends on this */
|
||||
struct stat st;
|
||||
struct stat st; /* execve() depends on this */
|
||||
if (fstatat(AT_FDCWD, path, &st, 0)) return 0;
|
||||
return !!(st.st_mode & 0111);
|
||||
return !S_ISDIR(st.st_mode) && !!(st.st_mode & 0111);
|
||||
}
|
||||
|
|
|
@ -23,24 +23,34 @@
|
|||
#include "libc/nt/console.h"
|
||||
#include "libc/nt/enum/ctrlevent.h"
|
||||
#include "libc/nt/enum/processaccess.h"
|
||||
#include "libc/nt/errors.h"
|
||||
#include "libc/nt/process.h"
|
||||
#include "libc/nt/runtime.h"
|
||||
#include "libc/sysv/errfuns.h"
|
||||
|
||||
textwindows int sys_kill_nt(int pid, int sig) {
|
||||
bool ok;
|
||||
int event;
|
||||
int64_t handle;
|
||||
int event, ntpid;
|
||||
if (pid) {
|
||||
pid = ABS(pid);
|
||||
if ((event = GetConsoleCtrlEvent(sig)) != -1) {
|
||||
ok = !!GenerateConsoleCtrlEvent(
|
||||
event, __isfdkind(pid, kFdProcess) ? GetProcessId(g_fds.p[pid].handle)
|
||||
: pid);
|
||||
/* kill(pid, SIGINT|SIGHUP|SIGQUIT) */
|
||||
if (__isfdkind(pid, kFdProcess)) {
|
||||
ntpid = GetProcessId(g_fds.p[pid].handle);
|
||||
} else if (!__isfdopen(pid)) {
|
||||
/* XXX: this is sloppy (see fork-nt.c) */
|
||||
ntpid = pid;
|
||||
} else {
|
||||
return esrch();
|
||||
}
|
||||
ok = !!GenerateConsoleCtrlEvent(event, ntpid);
|
||||
} else if (__isfdkind(pid, kFdProcess)) {
|
||||
ok = !!TerminateProcess(g_fds.p[pid].handle, 128 + sig);
|
||||
} else if ((handle = OpenProcess(kNtProcessAllAccess, false, pid))) {
|
||||
if (!ok && GetLastError() == kNtErrorAccessDenied) ok = true;
|
||||
} else if ((handle = OpenProcess(kNtProcessTerminate, false, pid))) {
|
||||
ok = !!TerminateProcess(handle, 128 + sig);
|
||||
if (!ok && GetLastError() == kNtErrorAccessDenied) ok = true;
|
||||
CloseHandle(handle);
|
||||
} else {
|
||||
ok = false;
|
||||
|
|
95
libc/calls/loadavg-nt.c
Normal file
95
libc/calls/loadavg-nt.c
Normal file
|
@ -0,0 +1,95 @@
|
|||
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2022 Justine Alexandra Roberts Tunney │
|
||||
│ │
|
||||
│ Permission to use, copy, modify, and/or distribute this software for │
|
||||
│ any purpose with or without fee is hereby granted, provided that the │
|
||||
│ above copyright notice and this permission notice appear in all copies. │
|
||||
│ │
|
||||
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
||||
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
||||
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
||||
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
||||
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
||||
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
||||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/calls/loadavg.internal.h"
|
||||
#include "libc/calls/strace.internal.h"
|
||||
#include "libc/dce.h"
|
||||
#include "libc/errno.h"
|
||||
#include "libc/nexgen32e/nt2sysv.h"
|
||||
#include "libc/nt/enum/accessmask.h"
|
||||
#include "libc/nt/enum/pdh.h"
|
||||
#include "libc/nt/enum/securityimpersonationlevel.h"
|
||||
#include "libc/nt/enum/wt.h"
|
||||
#include "libc/nt/errors.h"
|
||||
#include "libc/nt/events.h"
|
||||
#include "libc/nt/files.h"
|
||||
#include "libc/nt/pdh.h"
|
||||
#include "libc/nt/privilege.h"
|
||||
#include "libc/nt/runtime.h"
|
||||
#include "libc/nt/struct/luid.h"
|
||||
#include "libc/nt/struct/pdhfmtcountervalue.h"
|
||||
#include "libc/nt/struct/tokenprivileges.h"
|
||||
#include "libc/nt/synchronization.h"
|
||||
#include "libc/str/str.h"
|
||||
|
||||
/**
|
||||
* @fileoverview sysinfo() on the new technology
|
||||
* @kudos Giampaolo Rodola for teaching how to do load average
|
||||
*/
|
||||
|
||||
#define LOAD_SAMPLING_INTERVAL 1 // in seconds
|
||||
|
||||
// https://github.com/torvalds/linux/blob/345671ea0f9258f410eb057b9ced9cefbbe5dc78/include/linux/sched/loadavg.h#L20-L23
|
||||
#define LOAD1F .9200444146293232478931553241
|
||||
#define LOAD5F .9834714538216174894737477501
|
||||
#define LOAD15F .9944598480048967508795473394
|
||||
|
||||
double __ntloadavg[3];
|
||||
|
||||
static void LoadavgNtPoll(int64_t hCounter, bool32 timedOut) {
|
||||
struct NtPdhFmtCountervalue c;
|
||||
if (!PdhGetFormattedCounterValue(hCounter, kNtPdhFmtDouble, 0, &c)) {
|
||||
__ntloadavg[0] = __ntloadavg[0] * LOAD1F + c.doubleValue * (1 - LOAD1F);
|
||||
__ntloadavg[1] = __ntloadavg[1] * LOAD5F + c.doubleValue * (1 - LOAD5F);
|
||||
__ntloadavg[2] = __ntloadavg[2] * LOAD15F + c.doubleValue * (1 - LOAD15F);
|
||||
} else {
|
||||
STRACE("PdhGetFormattedCounterValue(%ld) failed", hCounter);
|
||||
}
|
||||
}
|
||||
|
||||
static textstartup void LoadavgNtInit(void) {
|
||||
int64_t hQuery, hCounter, hEvent, hWaiter;
|
||||
if (!IsWindows()) return;
|
||||
STRACE("LoadavgNtInit()");
|
||||
if (PdhOpenQuery(0, 0, &hQuery)) {
|
||||
STRACE("PdhOpenQuery failed");
|
||||
return;
|
||||
}
|
||||
if (PdhAddEnglishCounter(hQuery, u"\\System\\Processor Queue Length", 0,
|
||||
&hCounter)) {
|
||||
STRACE("PdhAddEnglishCounter() failed");
|
||||
return;
|
||||
}
|
||||
if (!(hEvent = CreateEvent(0, 0, 0, u"LoadUpdateEvent"))) {
|
||||
STRACE("CreateEvent() failed");
|
||||
return;
|
||||
}
|
||||
if (PdhCollectQueryDataEx(hQuery, LOAD_SAMPLING_INTERVAL, hEvent)) {
|
||||
STRACE("PdhCollectQueryDataEx() failed");
|
||||
return;
|
||||
}
|
||||
if (!RegisterWaitForSingleObject(
|
||||
&hWaiter, hEvent, (void *)NT2SYSV(LoadavgNtPoll),
|
||||
(void *)(intptr_t)hCounter, -1, kNtWtExecutedefault)) {
|
||||
STRACE("RegisterWaitForSingleObject() failed");
|
||||
return;
|
||||
}
|
||||
LoadavgNtPoll(hCounter, 0);
|
||||
}
|
||||
|
||||
const void *const LoadavgNtCtor[] initarray = {LoadavgNtInit};
|
10
libc/calls/loadavg.internal.h
Normal file
10
libc/calls/loadavg.internal.h
Normal file
|
@ -0,0 +1,10 @@
|
|||
#ifndef COSMOPOLITAN_LIBC_CALLS_LOADAVG_INTERNAL_H_
|
||||
#define COSMOPOLITAN_LIBC_CALLS_LOADAVG_INTERNAL_H_
|
||||
#if !(__ASSEMBLER__ + __LINKER__ + 0)
|
||||
COSMOPOLITAN_C_START_
|
||||
|
||||
extern double __ntloadavg[3];
|
||||
|
||||
COSMOPOLITAN_C_END_
|
||||
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
|
||||
#endif /* COSMOPOLITAN_LIBC_CALLS_LOADAVG_INTERNAL_H_ */
|
92
libc/calls/sedebug.c
Normal file
92
libc/calls/sedebug.c
Normal file
|
@ -0,0 +1,92 @@
|
|||
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2022 Justine Alexandra Roberts Tunney │
|
||||
│ │
|
||||
│ Permission to use, copy, modify, and/or distribute this software for │
|
||||
│ any purpose with or without fee is hereby granted, provided that the │
|
||||
│ above copyright notice and this permission notice appear in all copies. │
|
||||
│ │
|
||||
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
||||
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
||||
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
||||
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
||||
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
||||
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
||||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/calls/calls.h"
|
||||
#include "libc/calls/strace.internal.h"
|
||||
#include "libc/nt/enum/accessmask.h"
|
||||
#include "libc/nt/enum/securityimpersonationlevel.h"
|
||||
#include "libc/nt/errors.h"
|
||||
#include "libc/nt/files.h"
|
||||
#include "libc/nt/privilege.h"
|
||||
#include "libc/nt/runtime.h"
|
||||
#include "libc/nt/struct/luid.h"
|
||||
#include "libc/nt/struct/tokenprivileges.h"
|
||||
|
||||
static bool32 SetPrivilegeNt(int64_t hToken, const char16_t *lpwPrivilege,
|
||||
bool32 bEnable) {
|
||||
struct NtLuid luid;
|
||||
uint32_t cbPrevious;
|
||||
struct NtTokenPrivileges tp, tpPrevious;
|
||||
cbPrevious = sizeof(struct NtTokenPrivileges);
|
||||
if (!LookupPrivilegeValue(0, lpwPrivilege, &luid)) {
|
||||
STRACE("LookupPrivilegeValue() failed");
|
||||
return false;
|
||||
}
|
||||
tp.PrivilegeCount = 1;
|
||||
tp.Privileges[0].Luid = luid;
|
||||
tp.Privileges[0].Attributes = 0;
|
||||
if (!AdjustTokenPrivileges(hToken, false, &tp, sizeof(tp), &tpPrevious,
|
||||
&cbPrevious)) {
|
||||
STRACE("AdjustTokenPrivileges() failed");
|
||||
return false;
|
||||
}
|
||||
tpPrevious.PrivilegeCount = 1;
|
||||
tpPrevious.Privileges[0].Luid = luid;
|
||||
if (bEnable) {
|
||||
tpPrevious.Privileges[0].Attributes |= kNtSePrivilegeEnabled;
|
||||
} else {
|
||||
tpPrevious.Privileges[0].Attributes ^=
|
||||
kNtSePrivilegeEnabled & tpPrevious.Privileges[0].Attributes;
|
||||
}
|
||||
if (!AdjustTokenPrivileges(hToken, false, &tpPrevious, cbPrevious, 0, 0)) {
|
||||
STRACE("AdjustTokenPrivileges() failed");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static int64_t GetCurrentProcessSecurityToken(void) {
|
||||
int64_t hToken;
|
||||
if (OpenProcessToken(GetCurrentProcess(),
|
||||
kNtTokenAdjustPrivileges | kNtTokenQuery, &hToken)) {
|
||||
return hToken;
|
||||
} else if (GetLastError() == kNtErrorNoToken) {
|
||||
if (ImpersonateSelf(kNtSecurityImpersonation)) {
|
||||
if (OpenProcessToken(GetCurrentProcess(),
|
||||
kNtTokenAdjustPrivileges | kNtTokenQuery, &hToken)) {
|
||||
return hToken;
|
||||
} else {
|
||||
STRACE("OpenProcessToken() failed");
|
||||
}
|
||||
} else {
|
||||
STRACE("ImpersonateSelf() failed");
|
||||
}
|
||||
} else {
|
||||
STRACE("OpenProcessToken() failed");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool32 ElevateSeDebugPrivilege(void) {
|
||||
int64_t hToken;
|
||||
if (!(hToken = GetCurrentProcessSecurityToken())) return false;
|
||||
SetPrivilegeNt(hToken, u"SeDebugPrivilege", true);
|
||||
RevertToSelf();
|
||||
CloseHandle(hToken);
|
||||
return true;
|
||||
}
|
|
@ -17,6 +17,7 @@
|
|||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/calls/internal.h"
|
||||
#include "libc/calls/loadavg.internal.h"
|
||||
#include "libc/calls/struct/sysinfo.h"
|
||||
#include "libc/nt/accounting.h"
|
||||
#include "libc/nt/struct/memorystatusex.h"
|
||||
|
@ -32,6 +33,9 @@ textwindows int sys_sysinfo_nt(struct sysinfo *info) {
|
|||
info->totalram = memstat.ullTotalPhys;
|
||||
info->freeram = memstat.ullAvailPhys;
|
||||
info->procs = sysinfo.dwNumberOfProcessors;
|
||||
info->loads[0] = __ntloadavg[0] * 65536;
|
||||
info->loads[1] = __ntloadavg[1] * 65536;
|
||||
info->loads[2] = __ntloadavg[2] * 65536;
|
||||
info->mem_unit = 1;
|
||||
return 0;
|
||||
} else {
|
||||
|
|
|
@ -47,8 +47,10 @@ int sysinfo(struct sysinfo *info) {
|
|||
} else {
|
||||
rc = sys_sysinfo_nt(info);
|
||||
}
|
||||
info->procs = MAX(1, info->procs);
|
||||
info->mem_unit = MAX(1, info->mem_unit);
|
||||
info->totalram = MAX((8 * 1024 * 1024) / info->mem_unit, info->totalram);
|
||||
if (rc != -1) {
|
||||
info->procs = MAX(1, info->procs);
|
||||
info->mem_unit = MAX(1, info->mem_unit);
|
||||
info->totalram = MAX((8 * 1024 * 1024) / info->mem_unit, info->totalram);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
|
|
@ -24,26 +24,47 @@
|
|||
#include "libc/fmt/conv.h"
|
||||
#include "libc/macros.internal.h"
|
||||
#include "libc/nt/accounting.h"
|
||||
#include "libc/nt/enum/accessmask.h"
|
||||
#include "libc/nt/enum/processaccess.h"
|
||||
#include "libc/nt/enum/status.h"
|
||||
#include "libc/nt/enum/wait.h"
|
||||
#include "libc/nt/process.h"
|
||||
#include "libc/nt/runtime.h"
|
||||
#include "libc/nt/struct/filetime.h"
|
||||
#include "libc/nt/struct/processmemorycounters.h"
|
||||
#include "libc/nt/synchronization.h"
|
||||
#include "libc/runtime/runtime.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "libc/sysv/consts/o.h"
|
||||
#include "libc/sysv/consts/w.h"
|
||||
#include "libc/sysv/errfuns.h"
|
||||
|
||||
textwindows int sys_wait4_nt(int pid, int *opt_out_wstatus, int options,
|
||||
struct rusage *opt_out_rusage) {
|
||||
int pids[64];
|
||||
int64_t handle;
|
||||
int64_t handles[64];
|
||||
uint32_t dwExitCode;
|
||||
uint32_t i, count, timeout;
|
||||
struct NtProcessMemoryCountersEx memcount;
|
||||
struct NtFileTime createfiletime, exitfiletime, kernelfiletime, userfiletime;
|
||||
if (pid != -1) {
|
||||
if (!__isfdkind(pid, kFdProcess)) {
|
||||
return echild();
|
||||
/* XXX: this is sloppy (see fork-nt.c) */
|
||||
if (!__isfdopen(pid) &&
|
||||
(handle = OpenProcess(kNtSynchronize | kNtProcessQueryInformation,
|
||||
true, pid))) {
|
||||
if ((pid = __reservefd()) != -1) {
|
||||
g_fds.p[pid].kind = kFdProcess;
|
||||
g_fds.p[pid].handle = handle;
|
||||
g_fds.p[pid].flags = O_CLOEXEC;
|
||||
} else {
|
||||
CloseHandle(handle);
|
||||
return echild();
|
||||
}
|
||||
} else {
|
||||
return echild();
|
||||
}
|
||||
}
|
||||
handles[0] = g_fds.p[pid].handle;
|
||||
pids[0] = pid;
|
||||
|
@ -74,7 +95,6 @@ textwindows int sys_wait4_nt(int pid, int *opt_out_wstatus, int options,
|
|||
STRACE("%s failed %u", "WaitForMultipleObjects", GetLastError());
|
||||
return __winerr();
|
||||
}
|
||||
assert(__isfdkind(pids[i], kFdProcess));
|
||||
if (!GetExitCodeProcess(handles[i], &dwExitCode)) {
|
||||
STRACE("%s failed %u", "GetExitCodeProcess", GetLastError());
|
||||
return __winerr();
|
||||
|
@ -85,8 +105,14 @@ textwindows int sys_wait4_nt(int pid, int *opt_out_wstatus, int options,
|
|||
}
|
||||
if (opt_out_rusage) {
|
||||
bzero(opt_out_rusage, sizeof(*opt_out_rusage));
|
||||
if (GetProcessTimes(g_fds.p[pids[i]].handle, &createfiletime,
|
||||
&exitfiletime, &kernelfiletime, &userfiletime)) {
|
||||
if (GetProcessMemoryInfo(handles[i], &memcount, sizeof(memcount))) {
|
||||
opt_out_rusage->ru_maxrss = memcount.PeakWorkingSetSize;
|
||||
opt_out_rusage->ru_majflt = memcount.PageFaultCount;
|
||||
} else {
|
||||
STRACE("%s failed %u", "GetProcessMemoryInfo", GetLastError());
|
||||
}
|
||||
if (GetProcessTimes(handles[i], &createfiletime, &exitfiletime,
|
||||
&kernelfiletime, &userfiletime)) {
|
||||
opt_out_rusage->ru_utime =
|
||||
WindowsDurationToTimeVal(ReadFileTime(userfiletime));
|
||||
opt_out_rusage->ru_stime =
|
||||
|
@ -95,8 +121,8 @@ textwindows int sys_wait4_nt(int pid, int *opt_out_wstatus, int options,
|
|||
STRACE("%s failed %u", "GetProcessTimes", GetLastError());
|
||||
}
|
||||
}
|
||||
CloseHandle(g_fds.p[pids[i]].handle);
|
||||
g_fds.p[pids[i]].kind = kFdEmpty;
|
||||
CloseHandle(handles[i]);
|
||||
__releasefd(pids[i]);
|
||||
return pids[i];
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue