diff options
Diffstat (limited to 'winsup/cygwin/fhandler_process.cc')
-rw-r--r-- | winsup/cygwin/fhandler_process.cc | 522 |
1 files changed, 488 insertions, 34 deletions
diff --git a/winsup/cygwin/fhandler_process.cc b/winsup/cygwin/fhandler_process.cc index 7f68d4a..f374d4d 100644 --- a/winsup/cygwin/fhandler_process.cc +++ b/winsup/cygwin/fhandler_process.cc @@ -14,6 +14,7 @@ details. */ #include <unistd.h> #include <stdlib.h> #include <sys/cygwin.h> +#include <ntdef.h> #include "cygerrno.h" #include "security.h" #include "fhandler.h" @@ -23,6 +24,7 @@ details. */ #include "shared_info.h" #include "dtable.h" #include "cygheap.h" +#include "ntdll.h" #include <assert.h> #define _COMPILING_NEWLIB @@ -38,6 +40,8 @@ static const int PROCESS_GID = 8; static const int PROCESS_PGID = 9; static const int PROCESS_SID = 10; static const int PROCESS_CTTY = 11; +static const int PROCESS_STAT = 12; +static const int PROCESS_STATM = 13; static const char *process_listing[] = { ".", @@ -52,11 +56,20 @@ static const char *process_listing[] = { "pgid", "sid", "ctty", + "stat", + "statm", NULL }; static const int PROCESS_LINK_COUNT = (sizeof(process_listing) / sizeof(const char *)) - 1; +static off_t format_process_stat (_pinfo *p, char *destbuf, size_t maxsize); +static off_t format_process_status (_pinfo *p, char *destbuf, size_t maxsize); +static off_t format_process_statm (_pinfo *p, char *destbuf, size_t maxsize); +static int get_process_state (DWORD dwProcessId); +static bool get_mem_values(DWORD dwProcessId, unsigned long *vmsize, unsigned long *vmrss, unsigned long *vmtext, + unsigned long *vmdata, unsigned long *vmlib, unsigned long *vmshare); + /* Returns 0 if path doesn't exist, >0 if path is a directory, * <0 if path is a file. */ @@ -84,8 +97,26 @@ fhandler_process::fhandler_process (): int fhandler_process::fstat (struct __stat64 *buf, path_conv *pc) { - int file_type = exists (get_name ()); + const char *path = get_name (); + int file_type = exists (path); (void) fhandler_base::fstat (buf, pc); + path += proc_len + 1; + int pid = atoi (path); + winpids pids; + _pinfo *p; + for (unsigned i = 0; i < pids.npids; i++) + { + p = pids[i]; + + if (!proc_exists (p)) + continue; + + if (p->pid == pid) + goto found; + } + set_errno(ENOENT); + return -1; +found: buf->st_mode &= ~_IFMT & NO_W; switch (file_type) @@ -97,12 +128,18 @@ fhandler_process::fstat (struct __stat64 *buf, path_conv *pc) buf->st_mode |= S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH; return 0; case 2: + buf->st_ctime = buf->st_mtime = p->start_time; + buf->st_atime = time(NULL); + buf->st_uid = p->uid; + buf->st_gid = p->gid; buf->st_mode |= S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH; buf->st_nlink = PROCESS_LINK_COUNT; return 0; default: case -1: - buf->st_mode |= S_IFREG; + buf->st_uid = p->uid; + buf->st_gid = p->gid; + buf->st_mode |= S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; return 0; } } @@ -198,7 +235,36 @@ fhandler_process::open (path_conv *pc, int flags, mode_t mode) res = 0; goto out; found: - switch (process_file_no) + fileid = process_file_no; + saved_pid = pid; + saved_p = p; + fill_filebuf (); + + if (flags & O_APPEND) + position = filesize; + else + position = 0; + +success: + res = 1; + set_open_status (); + set_flags (flags); +out: + syscall_printf ("%d = fhandler_proc::open (%p, %d)", res, flags, mode); + return res; +} + +void +fhandler_process::fill_filebuf () +{ + // has this process gone away? + if (!proc_exists (saved_p) || saved_p->pid != saved_pid) + { + if (filebuf) + cfree(filebuf); + filesize = 0; bufalloc = (size_t) -1; + } + switch (fileid) { case PROCESS_UID: case PROCESS_GID: @@ -207,25 +273,31 @@ found: case PROCESS_CTTY: case PROCESS_PPID: { + if (!filebuf) filebuf = (char *) cmalloc (HEAP_BUF, bufalloc = 40); int num; - switch (process_file_no) + switch (fileid) { case PROCESS_PPID: - num = p->ppid; + num = saved_p->ppid; break; case PROCESS_UID: - num = p->uid; + num = saved_p->uid; break; case PROCESS_PGID: - num = p->pgid; + num = saved_p->pgid; break; case PROCESS_SID: - num = p->sid; + num = saved_p->sid; + break; + case PROCESS_GID: + num = saved_p->gid; break; - default: case PROCESS_CTTY: - num = p->ctty; + num = saved_p->ctty; + break; + default: // what's this here for? + num = 0; break; } __small_sprintf (filebuf, "%d\n", num); @@ -234,12 +306,13 @@ found: } case PROCESS_EXENAME: { + if (!filebuf) filebuf = (char *) cmalloc (HEAP_BUF, bufalloc = MAX_PATH); - if (p->process_state & (PID_ZOMBIE | PID_EXITED)) + if (saved_p->process_state & (PID_ZOMBIE | PID_EXITED)) strcpy (filebuf, "<defunct>"); else { - mount_table->conv_to_posix_path (p->progname, filebuf, 1); + mount_table->conv_to_posix_path (saved_p->progname, filebuf, 1); int len = strlen (filebuf); if (len > 4) { @@ -253,47 +326,428 @@ found: } case PROCESS_WINPID: { + if (!filebuf) filebuf = (char *) cmalloc (HEAP_BUF, bufalloc = 40); - __small_sprintf (filebuf, "%d\n", p->dwProcessId); + __small_sprintf (filebuf, "%d\n", saved_p->dwProcessId); filesize = strlen (filebuf); break; } case PROCESS_WINEXENAME: { - int len = strlen (p->progname); + int len = strlen (saved_p->progname); + if (!filebuf) filebuf = (char *) cmalloc (HEAP_BUF, bufalloc = (len + 2)); - strcpy (filebuf, p->progname); + strcpy (filebuf, saved_p->progname); filebuf[len] = '\n'; filesize = len + 1; break; } case PROCESS_STATUS: { - filebuf = (char *) cmalloc (HEAP_BUF, bufalloc = 3); - filebuf[0] = ' '; - filebuf[1] = '\n'; - filebuf[2] = 0; - if (p->process_state & PID_STOPPED) - filebuf[0] = 'S'; - else if (p->process_state & PID_TTYIN) - filebuf[0] = 'I'; - else if (p->process_state & PID_TTYOU) - filebuf[0] = 'O'; - filesize = 2; + if (!filebuf) + filebuf = (char *) cmalloc (HEAP_BUF, bufalloc = 2048); + filesize = format_process_status (saved_p, filebuf, bufalloc); + break; + } + case PROCESS_STAT: + { + if (!filebuf) + filebuf = (char *) cmalloc (HEAP_BUF, bufalloc = 2048); + filesize = format_process_stat (saved_p, filebuf, bufalloc); break; } + case PROCESS_STATM: + { + if (!filebuf) + filebuf = (char *) cmalloc (HEAP_BUF, bufalloc = 2048); + filesize = format_process_statm (saved_p, filebuf, bufalloc); + break; + } } +} - if (flags & O_APPEND) - position = filesize; +static +off_t +format_process_stat (_pinfo *p, char *destbuf, size_t maxsize) +{ + char cmd[MAX_PATH]; + int state = 'R'; + unsigned long fault_count = 0UL, + utime = 0UL, stime = 0UL, + start_time = 0UL, + vmsize = 0UL, vmrss = 0UL, vmmaxrss = 0UL; + int priority = 0; + if (p->process_state & (PID_ZOMBIE | PID_EXITED)) + strcpy (cmd, "<defunct"); else - position = 0; + { + strcpy(cmd, p->progname); + char *last_slash = strrchr (cmd, '\\'); + if (last_slash != NULL) + strcpy (cmd, last_slash + 1); + int len = strlen (cmd); + if (len > 4) + { + char *s = cmd + len - 4; + if (strcasecmp (s, ".exe") == 0) + *s = 0; + } + } + /* + * Note: under Windows, a _process_ is always running - it's only _threads_ + * that get suspended. Therefore the default state is R (runnable). + */ + if (p->process_state & PID_ZOMBIE) + state = 'Z'; + else if (p->process_state & PID_STOPPED) + state = 'T'; + else if (wincap.is_winnt ()) + state = get_process_state (p->dwProcessId); + if (wincap.is_winnt ()) + { + NTSTATUS ret; + HANDLE hProcess; + VM_COUNTERS vmc; + KERNEL_USER_TIMES put; + PROCESS_BASIC_INFORMATION pbi; + QUOTA_LIMITS ql; + SYSTEM_TIME_OF_DAY_INFORMATION stodi; + SYSTEM_PROCESSOR_TIMES spt; + hProcess = OpenProcess (PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, + FALSE, p->dwProcessId); + if (hProcess != NULL) + { + ret = ZwQueryInformationProcess (hProcess, + ProcessVmCounters, + (PVOID) &vmc, + sizeof vmc, NULL); + if (ret == STATUS_SUCCESS) + ret = ZwQueryInformationProcess (hProcess, + ProcessTimes, + (PVOID) &put, + sizeof put, NULL); + if (ret == STATUS_SUCCESS) + ret = ZwQueryInformationProcess (hProcess, + ProcessBasicInformation, + (PVOID) &pbi, + sizeof pbi, NULL); + if (ret == STATUS_SUCCESS) + ret = ZwQueryInformationProcess (hProcess, + ProcessQuotaLimits, + (PVOID) &ql, + sizeof ql, NULL); + CloseHandle (hProcess); + } + else + { + DWORD error = GetLastError (); + __seterrno_from_win_error (error); + debug_printf("OpenProcess: ret = %d", + error); + return 0; + } + if (ret == STATUS_SUCCESS) + ret = ZwQuerySystemInformation (SystemTimeOfDayInformation, + (PVOID) &stodi, + sizeof stodi, NULL); + if (ret == STATUS_SUCCESS) + ret = ZwQuerySystemInformation (SystemProcessorTimes, + (PVOID) &spt, + sizeof spt, NULL); + if (ret != STATUS_SUCCESS) + { + __seterrno_from_win_error (RtlNtStatusToDosError (ret)); + debug_printf("NtQueryInformationProcess: ret = %d, " + "Dos(ret) = %d", + ret, RtlNtStatusToDosError (ret)); + return 0; + } + fault_count = vmc.PageFaultCount; + utime = put.UserTime.QuadPart / 100000ULL; + stime = put.KernelTime.QuadPart / 100000ULL; + if (stodi.CurrentTime.QuadPart > put.CreateTime.QuadPart) + start_time = (spt.InterruptTime.QuadPart + spt.KernelTime.QuadPart + + spt.IdleTime.QuadPart + spt.UserTime.QuadPart + + spt.DpcTime.QuadPart - stodi.CurrentTime.QuadPart + + put.CreateTime.QuadPart) / 100000ULL; + else + /* + * sometimes stodi.CurrentTime is a bit behind + * Note: some older versions of procps are broken and can't cope + * with process start times > time(NULL). + */ + start_time = (spt.InterruptTime.QuadPart + spt.KernelTime.QuadPart + + spt.IdleTime.QuadPart + spt.UserTime.QuadPart + + spt.DpcTime.QuadPart) / 100000ULL; + priority = pbi.BasePriority; + unsigned page_size = getpagesize(); + vmsize = vmc.VirtualSize; + vmrss = vmc.WorkingSetSize / page_size; + vmmaxrss = ql.MaximumWorkingSetSize / page_size; + } + else + { + start_time = (GetTickCount() / 1000 - time(NULL) + p->start_time) * 100; + } + return __small_sprintf (destbuf, "%d (%s) %c " + "%d %d %d %d %d " + "%lu %lu %lu %lu %lu %lu %lu " + "%ld %ld %ld %ld %ld %ld " + "%lu %lu " + "%ld " + "%lu", + p->pid, cmd, + state, + p->ppid, p->pgid, p->sid, p->ctty, -1, + 0, fault_count, fault_count, 0, 0, utime, stime, + utime, stime, priority, 0, 0, 0, + start_time, vmsize, + vmrss, vmmaxrss + ); +} -success: - res = 1; - set_open_status (); - set_flags (flags); +static +off_t +format_process_status (_pinfo *p, char *destbuf, size_t maxsize) +{ + char cmd[MAX_PATH]; + int state = 'R'; + const char *state_str = "unknown"; + unsigned long vmsize = 0UL, vmrss = 0UL, vmdata = 0UL, vmlib = 0UL, vmtext = 0UL, + vmshare = 0UL; + if (p->process_state & (PID_ZOMBIE | PID_EXITED)) + strcpy (cmd, "<defunct>"); + else + { + strcpy(cmd, p->progname); + char *last_slash = strrchr (cmd, '\\'); + if (last_slash != NULL) + strcpy (cmd, last_slash + 1); + int len = strlen (cmd); + if (len > 4) + { + char *s = cmd + len - 4; + if (strcasecmp (s, ".exe") == 0) + *s = 0; + } + } + /* + * Note: under Windows, a _process_ is always running - it's only _threads_ + * that get suspended. Therefore the default state is R (runnable). + */ + if (p->process_state & PID_ZOMBIE) + state = 'Z'; + else if (p->process_state & PID_STOPPED) + state = 'T'; + else if (wincap.is_winnt ()) + state = get_process_state (p->dwProcessId); + switch (state) + { + case 'O': + state_str = "running"; + break; + case 'D': + case 'S': + state_str = "sleeping"; + break; + case 'R': + state_str = "runnable"; + break; + case 'Z': + state_str = "zombie"; + break; + case 'T': + state_str = "stopped"; + break; + } + if (wincap.is_winnt ()) + { + if (!get_mem_values (p->dwProcessId, &vmsize, &vmrss, &vmtext, &vmdata, &vmlib, &vmshare)) + return 0; + unsigned page_size = getpagesize(); + vmsize *= page_size; vmrss *= page_size; vmdata *= page_size; + vmtext *= page_size; vmlib *= page_size; + } + return __small_sprintf (destbuf, "Name: %s\n" + "State: %c (%s)\n" + "Tgid: %d\n" + "Pid: %d\n" + "PPid: %d\n" + "Uid: %d %d %d %d\n" + "Gid: %d %d %d %d\n" + "VmSize: %8d kB\n" + "VmLck: %8d kB\n" + "VmRSS: %8d kB\n" + "VmData: %8d kB\n" + "VmStk: %8d kB\n" + "VmExe: %8d kB\n" + "VmLib: %8d kB\n" + "SigPnd: %016x\n" + "SigBlk: %016x\n" + "SigIgn: %016x\n", + cmd, + state, state_str, + p->pgid, + p->pid, + p->ppid, + p->uid, cygheap->user.real_uid, cygheap->user.real_uid, p->uid, + p->gid, cygheap->user.real_gid, cygheap->user.real_gid, p->gid, + vmsize >> 10, 0, vmrss >> 10, vmdata >> 10, 0, vmtext >> 10, vmlib >> 10, + 0, 0, p->getsigmask () + ); +} + +static +off_t +format_process_statm (_pinfo *p, char *destbuf, size_t maxsize) +{ + unsigned long vmsize = 0UL, vmrss = 0UL, vmtext = 0UL, vmdata = 0UL, vmlib = 0UL, + vmshare = 0UL; + if (wincap.is_winnt ()) + { + if (!get_mem_values (p->dwProcessId, &vmsize, &vmrss, &vmtext, &vmdata, &vmlib, &vmshare)) + return 0; + } + return __small_sprintf (destbuf, "%ld %ld %ld %ld %ld %ld %ld", + vmsize, vmrss, vmshare, vmtext, vmlib, vmdata, 0 + ); +} + +static +int +get_process_state (DWORD dwProcessId) +{ + /* + * This isn't really heavy magic - just go through the processes' + * threads one by one and return a value accordingly + * Errors are silently ignored. + */ + NTSTATUS ret; + SYSTEM_PROCESSES *sp; + ULONG n = 0x1000; + PULONG p = new ULONG[n]; + int state =' '; + while (STATUS_INFO_LENGTH_MISMATCH == + (ret = ZwQuerySystemInformation (SystemProcessesAndThreadsInformation, + (PVOID) p, + n * sizeof *p, NULL))) + delete [] p, p = new ULONG[n *= 2]; + if (ret != STATUS_SUCCESS) + { + debug_printf("NtQuerySystemInformation: ret = %d, " + "Dos(ret) = %d", + ret, RtlNtStatusToDosError (ret)); + goto out; + } + state = 'Z'; + sp = (SYSTEM_PROCESSES *) p; + for (;;) + { + if (sp->ProcessId == dwProcessId) + { + SYSTEM_THREADS *st; + if (wincap.has_process_io_counters ()) + /* + * Windows 2000 and XP have an extra member in SYSTEM_PROCESSES + * which means the offset of the first SYSTEM_THREADS entry is + * different on these operating systems compared to NT 4. + */ + st = &sp->Threads[0]; + else + /* + * 136 is the offset of the first SYSTEM_THREADS entry on + * Windows NT 4. + */ + st = (SYSTEM_THREADS *) ((char *) sp + 136); + state = 'S'; + for (unsigned i = 0; i < sp->ThreadCount; i++) + { + if (st->State == StateRunning || + st->State == StateReady) + { + state = 'R'; + goto out; + } + st++; + } + break; + } + if (!sp->NextEntryDelta) + break; + sp = (SYSTEM_PROCESSES *) ((char *) sp + sp->NextEntryDelta); + } out: - syscall_printf ("%d = fhandler_proc::open (%p, %d)", res, flags, mode); + delete [] p; + return state; +} + +static +bool +get_mem_values(DWORD dwProcessId, unsigned long *vmsize, unsigned long *vmrss, unsigned long *vmtext, + unsigned long *vmdata, unsigned long *vmlib, unsigned long *vmshare) +{ + bool res = true; + NTSTATUS ret; + HANDLE hProcess; + VM_COUNTERS vmc; + MEMORY_WORKING_SET_LIST *mwsl; + ULONG n = 0x1000, length; + PULONG p = new ULONG[n]; + unsigned page_size = getpagesize(); + hProcess = OpenProcess (PROCESS_QUERY_INFORMATION, + FALSE, dwProcessId); + if (hProcess == NULL) + { + DWORD error = GetLastError(); + __seterrno_from_win_error (error); + debug_printf("OpenProcess: ret = %d", + error); + return false; + } + while ((ret = ZwQueryVirtualMemory (hProcess, 0, + MemoryWorkingSetList, + (PVOID) p, + n * sizeof *p, &length)), + (ret == STATUS_SUCCESS || ret == STATUS_INFO_LENGTH_MISMATCH) && + length >= n * sizeof *p) + delete [] p, p = new ULONG[n *= 2]; + if (ret != STATUS_SUCCESS) + { + debug_printf("NtQueryVirtualMemory: ret = %d, " + "Dos(ret) = %d", + ret, RtlNtStatusToDosError (ret)); + res = false; + goto out; + } + mwsl = (MEMORY_WORKING_SET_LIST *) p; + for (unsigned long i = 0; i < mwsl->NumberOfPages; i++) + { + ++*vmrss; + unsigned flags = mwsl->WorkingSetList[i] & 0x0FFF; + if (flags & (WSLE_PAGE_EXECUTE | WSLE_PAGE_SHAREABLE) == (WSLE_PAGE_EXECUTE | WSLE_PAGE_SHAREABLE)) + ++*vmlib; + else if (flags & WSLE_PAGE_SHAREABLE) + ++*vmshare; + else if (flags & WSLE_PAGE_EXECUTE) + ++*vmtext; + else + ++*vmdata; + } + ret = ZwQueryInformationProcess (hProcess, + ProcessVmCounters, + (PVOID) &vmc, + sizeof vmc, NULL); + if (ret != STATUS_SUCCESS) + { + debug_printf("NtQueryInformationProcess: ret = %d, " + "Dos(ret) = %d", + ret, RtlNtStatusToDosError (ret)); + res = false; + goto out; + } + *vmsize = vmc.VirtualSize / page_size; +out: + delete [] p; + CloseHandle (hProcess); return res; } |