Skip to content

Commit 0ce33db

Browse files
authored
Merge pull request #1166 from easyops-cn/kestrel/process
feat(process): implement the 'OpenFilesWithContext' function of the windows system
2 parents 214bc8d + 5832fdf commit 0ce33db

File tree

6 files changed

+298
-2
lines changed

6 files changed

+298
-2
lines changed

internal/common/common_windows.go

+71
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"context"
77
"fmt"
88
"path/filepath"
9+
"reflect"
910
"strings"
1011
"syscall"
1112
"unsafe"
@@ -48,11 +49,18 @@ const (
4849
PDH_INVALID_DATA = 0xc0000bc6
4950
PDH_INVALID_HANDLE = 0xC0000bbc
5051
PDH_NO_DATA = 0x800007d5
52+
53+
STATUS_BUFFER_OVERFLOW = 0x80000005
54+
STATUS_BUFFER_TOO_SMALL = 0xC0000023
55+
STATUS_INFO_LENGTH_MISMATCH = 0xC0000004
5156
)
5257

5358
const (
5459
ProcessBasicInformation = 0
5560
ProcessWow64Information = 26
61+
ProcessQueryInformation = windows.PROCESS_DUP_HANDLE | windows.PROCESS_QUERY_INFORMATION
62+
63+
SystemExtendedHandleInformationClass = 64
5664
)
5765

5866
var (
@@ -227,3 +235,66 @@ func ConvertDOSPath(p string) string {
227235
}
228236
return p
229237
}
238+
239+
type NtStatus uint32
240+
241+
func (s NtStatus) Error() error {
242+
if s == 0 {
243+
return nil
244+
}
245+
return fmt.Errorf("NtStatus 0x%08x", uint32(s))
246+
}
247+
248+
func (s NtStatus) IsError() bool {
249+
return s>>30 == 3
250+
}
251+
252+
type SystemExtendedHandleTableEntryInformation struct {
253+
Object uintptr
254+
UniqueProcessId uintptr
255+
HandleValue uintptr
256+
GrantedAccess uint32
257+
CreatorBackTraceIndex uint16
258+
ObjectTypeIndex uint16
259+
HandleAttributes uint32
260+
Reserved uint32
261+
}
262+
263+
type SystemExtendedHandleInformation struct {
264+
NumberOfHandles uintptr
265+
Reserved uintptr
266+
Handles [1]SystemExtendedHandleTableEntryInformation
267+
}
268+
269+
// CallWithExpandingBuffer https://github.com/hillu/go-ntdll
270+
func CallWithExpandingBuffer(fn func() NtStatus, buf *[]byte, resultLength *uint32) NtStatus {
271+
for {
272+
if st := fn(); st == STATUS_BUFFER_OVERFLOW || st == STATUS_BUFFER_TOO_SMALL || st == STATUS_INFO_LENGTH_MISMATCH {
273+
if int(*resultLength) <= cap(*buf) {
274+
(*reflect.SliceHeader)(unsafe.Pointer(buf)).Len = int(*resultLength)
275+
} else {
276+
*buf = make([]byte, int(*resultLength))
277+
}
278+
continue
279+
} else {
280+
if !st.IsError() {
281+
*buf = (*buf)[:int(*resultLength)]
282+
}
283+
return st
284+
}
285+
}
286+
}
287+
288+
func NtQuerySystemInformation(
289+
SystemInformationClass uint32,
290+
SystemInformation *byte,
291+
SystemInformationLength uint32,
292+
ReturnLength *uint32,
293+
) NtStatus {
294+
r0, _, _ := ProcNtQuerySystemInformation.Call(
295+
uintptr(SystemInformationClass),
296+
uintptr(unsafe.Pointer(SystemInformation)),
297+
uintptr(SystemInformationLength),
298+
uintptr(unsafe.Pointer(ReturnLength)))
299+
return NtStatus(r0)
300+
}

process/process_test.go

+3
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,9 @@ func Test_CPUTimes(t *testing.T) {
657657
}
658658

659659
func Test_OpenFiles(t *testing.T) {
660+
fp, err := os.Open("process_test.go")
661+
defer fp.Close()
662+
660663
pid := os.Getpid()
661664
p, err := NewProcess(int32(pid))
662665
skipIfNotImplementedErr(t, err)

process/process_windows.go

+75-1
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ import (
99
"fmt"
1010
"io"
1111
"os"
12+
"reflect"
1213
"strings"
1314
"syscall"
15+
"unicode/utf16"
1416
"unsafe"
1517

1618
"github.com/shirou/gopsutil/cpu"
@@ -667,7 +669,79 @@ func (p *Process) ChildrenWithContext(ctx context.Context) ([]*Process, error) {
667669
}
668670

669671
func (p *Process) OpenFilesWithContext(ctx context.Context) ([]OpenFilesStat, error) {
670-
return nil, common.ErrNotImplementedError
672+
files := make([]OpenFilesStat, 0)
673+
fileExists := make(map[string]bool)
674+
675+
process, err := windows.OpenProcess(common.ProcessQueryInformation, false, uint32(p.Pid))
676+
if err != nil {
677+
return nil, err
678+
}
679+
680+
buffer := make([]byte, 1024)
681+
var size uint32
682+
683+
st := common.CallWithExpandingBuffer(
684+
func() common.NtStatus {
685+
return common.NtQuerySystemInformation(
686+
common.SystemExtendedHandleInformationClass,
687+
&buffer[0],
688+
uint32(len(buffer)),
689+
&size,
690+
)
691+
},
692+
&buffer,
693+
&size,
694+
)
695+
if st.IsError() {
696+
return nil, st.Error()
697+
}
698+
699+
handlesList := (*common.SystemExtendedHandleInformation)(unsafe.Pointer(&buffer[0]))
700+
handles := make([]common.SystemExtendedHandleTableEntryInformation, int(handlesList.NumberOfHandles))
701+
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&handles))
702+
hdr.Data = uintptr(unsafe.Pointer(&handlesList.Handles[0]))
703+
704+
currentProcess, err := windows.GetCurrentProcess()
705+
if err != nil {
706+
return nil, err
707+
}
708+
709+
for _, handle := range handles {
710+
var file uintptr
711+
if int32(handle.UniqueProcessId) != p.Pid {
712+
continue
713+
}
714+
if windows.DuplicateHandle(process, windows.Handle(handle.HandleValue), currentProcess, (*windows.Handle)(&file),
715+
0, true, windows.DUPLICATE_SAME_ACCESS) != nil {
716+
continue
717+
}
718+
fileType, _ := windows.GetFileType(windows.Handle(file))
719+
if fileType != windows.FILE_TYPE_DISK {
720+
continue
721+
}
722+
723+
var buf [syscall.MAX_LONG_PATH]uint16
724+
n, err := windows.GetFinalPathNameByHandle(windows.Handle(file), &buf[0], syscall.MAX_LONG_PATH, 0)
725+
if err != nil {
726+
continue
727+
}
728+
729+
fileName := string(utf16.Decode(buf[:n]))
730+
fileInfo, _ := os.Stat(fileName)
731+
if fileInfo.IsDir() {
732+
continue
733+
}
734+
735+
if _, exists := fileExists[fileName]; !exists {
736+
files = append(files, OpenFilesStat{
737+
Path: fileName,
738+
Fd: uint64(file),
739+
})
740+
fileExists[fileName] = true
741+
}
742+
}
743+
744+
return files, nil
671745
}
672746

673747
func (p *Process) ConnectionsWithContext(ctx context.Context) ([]net.ConnectionStat, error) {

v3/internal/common/common_windows.go

+71
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"context"
77
"fmt"
88
"path/filepath"
9+
"reflect"
910
"strings"
1011
"syscall"
1112
"unsafe"
@@ -48,11 +49,18 @@ const (
4849
PDH_INVALID_DATA = 0xc0000bc6
4950
PDH_INVALID_HANDLE = 0xC0000bbc
5051
PDH_NO_DATA = 0x800007d5
52+
53+
STATUS_BUFFER_OVERFLOW = 0x80000005
54+
STATUS_BUFFER_TOO_SMALL = 0xC0000023
55+
STATUS_INFO_LENGTH_MISMATCH = 0xC0000004
5156
)
5257

5358
const (
5459
ProcessBasicInformation = 0
5560
ProcessWow64Information = 26
61+
ProcessQueryInformation = windows.PROCESS_DUP_HANDLE | windows.PROCESS_QUERY_INFORMATION
62+
63+
SystemExtendedHandleInformationClass = 64
5664
)
5765

5866
var (
@@ -227,3 +235,66 @@ func ConvertDOSPath(p string) string {
227235
}
228236
return p
229237
}
238+
239+
type NtStatus uint32
240+
241+
func (s NtStatus) Error() error {
242+
if s == 0 {
243+
return nil
244+
}
245+
return fmt.Errorf("NtStatus 0x%08x", uint32(s))
246+
}
247+
248+
func (s NtStatus) IsError() bool {
249+
return s>>30 == 3
250+
}
251+
252+
type SystemExtendedHandleTableEntryInformation struct {
253+
Object uintptr
254+
UniqueProcessId uintptr
255+
HandleValue uintptr
256+
GrantedAccess uint32
257+
CreatorBackTraceIndex uint16
258+
ObjectTypeIndex uint16
259+
HandleAttributes uint32
260+
Reserved uint32
261+
}
262+
263+
type SystemExtendedHandleInformation struct {
264+
NumberOfHandles uintptr
265+
Reserved uintptr
266+
Handles [1]SystemExtendedHandleTableEntryInformation
267+
}
268+
269+
// CallWithExpandingBuffer https://github.com/hillu/go-ntdll
270+
func CallWithExpandingBuffer(fn func() NtStatus, buf *[]byte, resultLength *uint32) NtStatus {
271+
for {
272+
if st := fn(); st == STATUS_BUFFER_OVERFLOW || st == STATUS_BUFFER_TOO_SMALL || st == STATUS_INFO_LENGTH_MISMATCH {
273+
if int(*resultLength) <= cap(*buf) {
274+
(*reflect.SliceHeader)(unsafe.Pointer(buf)).Len = int(*resultLength)
275+
} else {
276+
*buf = make([]byte, int(*resultLength))
277+
}
278+
continue
279+
} else {
280+
if !st.IsError() {
281+
*buf = (*buf)[:int(*resultLength)]
282+
}
283+
return st
284+
}
285+
}
286+
}
287+
288+
func NtQuerySystemInformation(
289+
SystemInformationClass uint32,
290+
SystemInformation *byte,
291+
SystemInformationLength uint32,
292+
ReturnLength *uint32,
293+
) NtStatus {
294+
r0, _, _ := ProcNtQuerySystemInformation.Call(
295+
uintptr(SystemInformationClass),
296+
uintptr(unsafe.Pointer(SystemInformation)),
297+
uintptr(SystemInformationLength),
298+
uintptr(unsafe.Pointer(ReturnLength)))
299+
return NtStatus(r0)
300+
}

v3/process/process_test.go

+3
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,9 @@ func Test_CPUTimes(t *testing.T) {
659659
}
660660

661661
func Test_OpenFiles(t *testing.T) {
662+
fp, err := os.Open("process_test.go")
663+
defer fp.Close()
664+
662665
pid := os.Getpid()
663666
p, err := NewProcess(int32(pid))
664667
skipIfNotImplementedErr(t, err)

v3/process/process_windows.go

+75-1
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ import (
99
"fmt"
1010
"io"
1111
"os"
12+
"reflect"
1213
"strings"
1314
"syscall"
15+
"unicode/utf16"
1416
"unsafe"
1517

1618
"github.com/shirou/gopsutil/v3/cpu"
@@ -654,7 +656,79 @@ func (p *Process) ChildrenWithContext(ctx context.Context) ([]*Process, error) {
654656
}
655657

656658
func (p *Process) OpenFilesWithContext(ctx context.Context) ([]OpenFilesStat, error) {
657-
return nil, common.ErrNotImplementedError
659+
files := make([]OpenFilesStat, 0)
660+
fileExists := make(map[string]bool)
661+
662+
process, err := windows.OpenProcess(common.ProcessQueryInformation, false, uint32(p.Pid))
663+
if err != nil {
664+
return nil, err
665+
}
666+
667+
buffer := make([]byte, 1024)
668+
var size uint32
669+
670+
st := common.CallWithExpandingBuffer(
671+
func() common.NtStatus {
672+
return common.NtQuerySystemInformation(
673+
common.SystemExtendedHandleInformationClass,
674+
&buffer[0],
675+
uint32(len(buffer)),
676+
&size,
677+
)
678+
},
679+
&buffer,
680+
&size,
681+
)
682+
if st.IsError() {
683+
return nil, st.Error()
684+
}
685+
686+
handlesList := (*common.SystemExtendedHandleInformation)(unsafe.Pointer(&buffer[0]))
687+
handles := make([]common.SystemExtendedHandleTableEntryInformation, int(handlesList.NumberOfHandles))
688+
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&handles))
689+
hdr.Data = uintptr(unsafe.Pointer(&handlesList.Handles[0]))
690+
691+
currentProcess, err := windows.GetCurrentProcess()
692+
if err != nil {
693+
return nil, err
694+
}
695+
696+
for _, handle := range handles {
697+
var file uintptr
698+
if int32(handle.UniqueProcessId) != p.Pid {
699+
continue
700+
}
701+
if windows.DuplicateHandle(process, windows.Handle(handle.HandleValue), currentProcess, (*windows.Handle)(&file),
702+
0, true, windows.DUPLICATE_SAME_ACCESS) != nil {
703+
continue
704+
}
705+
fileType, _ := windows.GetFileType(windows.Handle(file))
706+
if fileType != windows.FILE_TYPE_DISK {
707+
continue
708+
}
709+
710+
var buf [syscall.MAX_LONG_PATH]uint16
711+
n, err := windows.GetFinalPathNameByHandle(windows.Handle(file), &buf[0], syscall.MAX_LONG_PATH, 0)
712+
if err != nil {
713+
continue
714+
}
715+
716+
fileName := string(utf16.Decode(buf[:n]))
717+
fileInfo, _ := os.Stat(fileName)
718+
if fileInfo.IsDir() {
719+
continue
720+
}
721+
722+
if _, exists := fileExists[fileName]; !exists {
723+
files = append(files, OpenFilesStat{
724+
Path: fileName,
725+
Fd: uint64(file),
726+
})
727+
fileExists[fileName] = true
728+
}
729+
}
730+
731+
return files, nil
658732
}
659733

660734
func (p *Process) ConnectionsWithContext(ctx context.Context) ([]net.ConnectionStat, error) {

0 commit comments

Comments
 (0)