From c0271c1de905f464117f403a354195ce9c8747ee Mon Sep 17 00:00:00 2001 From: Sean Williams Date: Fri, 15 Apr 2016 13:38:25 -0700 Subject: [PATCH] Support process creation time on windows --- process.go | 7 +++++ process_darwin.go | 6 ++++ process_unix.go | 7 +++++ process_windows.go | 69 ++++++++++++++++++++++++++++++++++++++-------- 4 files changed, 78 insertions(+), 11 deletions(-) diff --git a/process.go b/process.go index 2b5e8ed..35ffafe 100644 --- a/process.go +++ b/process.go @@ -7,6 +7,10 @@ // are interested. package ps +import ( + "time" +) + // Process is the generic interface that is implemented on every platform // and provides common operations for processes. type Process interface { @@ -19,6 +23,9 @@ type Process interface { // Executable name running this process. This is not a path to the // executable. Executable() string + + // Time at which the process was created. + CreationTime() time.Time } // Processes returns all processes. diff --git a/process_darwin.go b/process_darwin.go index 2b633ba..2150467 100644 --- a/process_darwin.go +++ b/process_darwin.go @@ -7,6 +7,7 @@ import "C" import ( "sync" + "time" ) // This lock is what verifies that C calling back into Go is only @@ -18,6 +19,7 @@ type DarwinProcess struct { pid int ppid int binary string + ctime time.Time } func (p *DarwinProcess) Pid() int { @@ -32,6 +34,10 @@ func (p *DarwinProcess) Executable() string { return p.binary } +func (p *DarwinProcess) CreationTime() time.Time { + return p.ctime +} + //export go_darwin_append_proc func go_darwin_append_proc(pid C.pid_t, ppid C.pid_t, comm *C.char) { proc := &DarwinProcess{ diff --git a/process_unix.go b/process_unix.go index 578215a..b4d81c8 100644 --- a/process_unix.go +++ b/process_unix.go @@ -9,6 +9,7 @@ import ( "os" "strconv" "strings" + "time" ) // UnixProcess is an implementation of Process that contains Unix-specific @@ -21,6 +22,8 @@ type UnixProcess struct { sid int binary string + + ctime time.Time } func (p *UnixProcess) Pid() int { @@ -35,6 +38,10 @@ func (p *UnixProcess) Executable() string { return p.binary } +func (p *UnixProcess) CreationTime() time.Time { + return p.ctime +} + // Refresh reloads all the data associated with this process. func (p *UnixProcess) Refresh() error { statPath := fmt.Sprintf("/proc/%d/stat", p.pid) diff --git a/process_windows.go b/process_windows.go index f151974..eec2f7f 100644 --- a/process_windows.go +++ b/process_windows.go @@ -5,6 +5,7 @@ package ps import ( "fmt" "syscall" + "time" "unsafe" ) @@ -13,6 +14,9 @@ var ( modKernel32 = syscall.NewLazyDLL("kernel32.dll") procCloseHandle = modKernel32.NewProc("CloseHandle") procCreateToolhelp32Snapshot = modKernel32.NewProc("CreateToolhelp32Snapshot") + procGetProcessTimes = modKernel32.NewProc("GetProcessTimes") + procFileTimeToSystemTime = modKernel32.NewProc("FileTimeToSystemTime") + procOpenProcess = modKernel32.NewProc("OpenProcess") procProcess32First = modKernel32.NewProc("Process32FirstW") procProcess32Next = modKernel32.NewProc("Process32NextW") ) @@ -21,6 +25,7 @@ var ( const ( ERROR_NO_MORE_FILES = 0x12 MAX_PATH = 260 + PROCESS_ALL_ACCESS = 0x1F0FFF ) // PROCESSENTRY32 is the Windows API structure that contains a process's @@ -38,11 +43,22 @@ type PROCESSENTRY32 struct { ExeFile [MAX_PATH]uint16 } +type HANDLE uintptr + +type FILETIME struct { + LowDateTime uint32 + HighDateTime uint32 +} +type SYSTEMTIME struct { + year, month, dow, day, hour, min, sec, msec uint16 +} + // WindowsProcess is an implementation of Process for Windows. type WindowsProcess struct { - pid int - ppid int - exe string + pid int + ppid int + exe string + ctime time.Time } func (p *WindowsProcess) Pid() int { @@ -57,7 +73,11 @@ func (p *WindowsProcess) Executable() string { return p.exe } -func newWindowsProcess(e *PROCESSENTRY32) *WindowsProcess { +func (p *WindowsProcess) CreationTime() time.Time { + return p.ctime +} + +func newWindowsProcess(e *PROCESSENTRY32, ctime time.Time) *WindowsProcess { // Find when the string ends for decoding end := 0 for { @@ -68,9 +88,10 @@ func newWindowsProcess(e *PROCESSENTRY32) *WindowsProcess { } return &WindowsProcess{ - pid: int(e.ProcessID), - ppid: int(e.ParentProcessID), - exe: syscall.UTF16ToString(e.ExeFile[:end]), + pid: int(e.ProcessID), + ppid: int(e.ParentProcessID), + exe: syscall.UTF16ToString(e.ExeFile[:end]), + ctime: ctime, } } @@ -98,7 +119,12 @@ func processes() ([]Process, error) { } defer procCloseHandle.Call(handle) - var entry PROCESSENTRY32 + var ( + entry PROCESSENTRY32 + ctime, etime, ktime, utime FILETIME + // real creation time + rCtime = SYSTEMTIME{0,0,0,0,0,0,0,0} + ) entry.Size = uint32(unsafe.Sizeof(entry)) ret, _, _ := procProcess32First.Call(handle, uintptr(unsafe.Pointer(&entry))) if ret == 0 { @@ -107,12 +133,33 @@ func processes() ([]Process, error) { results := make([]Process, 0, 50) for { - results = append(results, newWindowsProcess(&entry)) - - ret, _, _ := procProcess32Next.Call(handle, uintptr(unsafe.Pointer(&entry))) + ret, _, _ = procProcess32Next.Call(handle, uintptr(unsafe.Pointer(&entry))) + // All done iterating over processes if ret == 0 { break } + + // Try to open process to capture more process information like ctime + pHandle, _, _ := procOpenProcess.Call(PROCESS_ALL_ACCESS, uintptr(0), uintptr(entry.ProcessID)) + if pHandle != 0 { + ret, _, _ = procGetProcessTimes.Call(uintptr(unsafe.Pointer(pHandle)), + uintptr(unsafe.Pointer(&ctime)), + uintptr(unsafe.Pointer(&etime)), + uintptr(unsafe.Pointer(&ktime)), + uintptr(unsafe.Pointer(&utime))) + if ret != 0 { + ret, _, _ = procFileTimeToSystemTime.Call(uintptr(unsafe.Pointer(&ctime)), uintptr(unsafe.Pointer(&rCtime))) + } + } else { + rCtime = SYSTEMTIME{0,0,0,0,0,0,0,0} + } + ctime := time.Date(int(rCtime.year), time.Month(rCtime.month), int(rCtime.day), + int(rCtime.hour), int(rCtime.min), int(rCtime.sec), 0, &time.Location{}) + + results = append(results, newWindowsProcess(&entry, ctime)) + + //fmt.Printf("process age over? %v\n", time.Since(pDate) > time.Duration(1 * time.Hour)) + } return results, nil