/* fhandler_process.cc: fhandler for /proc/ virtual filesystem Copyright 2002 Red Hat, Inc. This file is part of Cygwin. This software is a copyrighted work licensed under the terms of the Cygwin license. Please consult the file "CYGWIN_LICENSE" for details. */ #include "winsup.h" #include #include #include #include #include #include #include "cygerrno.h" #include "security.h" #include "fhandler.h" #include "sigproc.h" #include "pinfo.h" #include "path.h" #include "shared_info.h" #include "dtable.h" #include "cygheap.h" #include "ntdll.h" #include #define _COMPILING_NEWLIB #include static const int PROCESS_PPID = 2; static const int PROCESS_EXENAME = 3; static const int PROCESS_WINPID = 4; static const int PROCESS_WINEXENAME = 5; static const int PROCESS_STATUS = 6; static const int PROCESS_UID = 7; 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[] = { ".", "..", "ppid", "exename", "winpid", "winexename", "status", "uid", "gid", "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. */ int fhandler_process::exists () { const char *path = get_name (); debug_printf ("exists (%s)", path); path += proc_len + 1; while (*path != 0 && !SLASH_P (*path)) path++; if (*path == 0) return 2; for (int i = 0; process_listing[i]; i++) if (pathmatch (path + 1, process_listing[i])) return -1; return 1; } fhandler_process::fhandler_process (): fhandler_proc (FH_PROCESS) { } int fhandler_process::fstat (struct __stat64 *buf, path_conv *pc) { const char *path = get_name (); int file_type = exists (); (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) { case 0: set_errno (ENOENT); return -1; case 1: 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_uid = p->uid; buf->st_gid = p->gid; buf->st_mode |= S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; return 0; } } struct dirent * fhandler_process::readdir (DIR * dir) { if (dir->__d_position >= PROCESS_LINK_COUNT) { set_errno (ENMFILE); return NULL; } strcpy (dir->__d_dirent->d_name, process_listing[dir->__d_position++]); syscall_printf ("%p = readdir (%p) (%s)", &dir->__d_dirent, dir, dir->__d_dirent->d_name); return dir->__d_dirent; } int fhandler_process::open (path_conv *pc, int flags, mode_t mode) { int process_file_no = -1, pid; winpids pids; _pinfo *p; int res = fhandler_virtual::open (pc, flags, mode); if (!res) goto out; const char *path; path = get_name () + proc_len + 1; pid = atoi (path); while (*path != 0 && !SLASH_P (*path)) path++; if (*path == 0) { if ((flags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL)) { set_errno (EEXIST); res = 0; goto out; } else if (flags & O_WRONLY) { set_errno (EISDIR); res = 0; goto out; } else { flags |= O_DIROPEN; goto success; } } process_file_no = -1; for (int i = 0; process_listing[i]; i++) { if (path_prefix_p (process_listing[i], path + 1, strlen (process_listing[i]))) process_file_no = i; } if (process_file_no == -1) { if (flags & O_CREAT) { set_errno (EROFS); res = 0; goto out; } else { set_errno (ENOENT); res = 0; goto out; } } if (flags & O_WRONLY) { set_errno (EROFS); res = 0; goto out; } 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); res = 0; goto out; found: 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: case PROCESS_PGID: case PROCESS_SID: case PROCESS_CTTY: case PROCESS_PPID: { if (!filebuf) filebuf = (char *) cmalloc (HEAP_BUF, bufalloc = 40); int num; switch (fileid) { case PROCESS_PPID: num = saved_p->ppid; break; case PROCESS_UID: num = saved_p->uid; break; case PROCESS_PGID: num = saved_p->pgid; break; case PROCESS_SID: num = saved_p->sid; break; case PROCESS_GID: num = saved_p->gid; break; case PROCESS_CTTY: num = saved_p->ctty; break; default: // what's this here for? num = 0; break; } __small_sprintf (filebuf, "%d\n", num); filesize = strlen (filebuf); break; } case PROCESS_EXENAME: { if (!filebuf) filebuf = (char *) cmalloc (HEAP_BUF, bufalloc = MAX_PATH); if (saved_p->process_state & (PID_ZOMBIE | PID_EXITED)) strcpy (filebuf, ""); else { mount_table->conv_to_posix_path (saved_p->progname, filebuf, 1); int len = strlen (filebuf); if (len > 4) { char *s = filebuf + len - 4; if (strcasecmp (s, ".exe") == 0) *s = 0; } } filesize = strlen (filebuf); break; } case PROCESS_WINPID: { if (!filebuf) filebuf = (char *) cmalloc (HEAP_BUF, bufalloc = 40); __small_sprintf (filebuf, "%d\n", saved_p->dwProcessId); filesize = strlen (filebuf); break; } case PROCESS_WINEXENAME: { int len = strlen (saved_p->progname); if (!filebuf) filebuf = (char *) cmalloc (HEAP_BUF, bufalloc = (len + 2)); strcpy (filebuf, saved_p->progname); filebuf[len] = '\n'; filesize = len + 1; break; } case PROCESS_STATUS: { 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; } } } 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, "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 = NtQueryInformationProcess (hProcess, ProcessVmCounters, (PVOID) &vmc, sizeof vmc, NULL); if (ret == STATUS_SUCCESS) ret = NtQueryInformationProcess (hProcess, ProcessTimes, (PVOID) &put, sizeof put, NULL); if (ret == STATUS_SUCCESS) ret = NtQueryInformationProcess (hProcess, ProcessBasicInformation, (PVOID) &pbi, sizeof pbi, NULL); if (ret == STATUS_SUCCESS) ret = NtQueryInformationProcess (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 = NtQuerySystemInformation (SystemTimeOfDayInformation, (PVOID) &stodi, sizeof stodi, NULL); if (ret == STATUS_SUCCESS) ret = NtQuerySystemInformation (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 ); } 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, ""); 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 = NtQuerySystemInformation (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: 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 = NtQueryVirtualMemory (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 = NtQueryInformationProcess (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; }