From 19a79f422818859dd5a052ddc8e270ffbfef6e71 Mon Sep 17 00:00:00 2001
From: Alex Henrie <alexhenrie24@gmail.com>
Date: Sun, 7 May 2017 22:32:43 -0600
Subject: [PATCH] ntdll: Implement ProcessVmCounters for Linux for other
 processes.

Signed-off-by: Alex Henrie <alexhenrie24@gmail.com>
Signed-off-by: Alexandre Julliard <julliard@winehq.org>
---
 dlls/ntdll/process.c           | 20 +++++++++++++++---
 dlls/ntdll/tests/info.c        | 22 ++++++++++++++++++++
 include/wine/server_protocol.h | 23 +++++++++++++++++++-
 server/process.c               | 38 ++++++++++++++++++++++++++++++++++
 server/protocol.def            | 13 ++++++++++++
 server/request.h               | 11 ++++++++++
 server/trace.c                 | 18 ++++++++++++++++
 7 files changed, 141 insertions(+), 4 deletions(-)

diff --git a/dlls/ntdll/process.c b/dlls/ntdll/process.c
index 952225688cb..8d660300aaa 100644
--- a/dlls/ntdll/process.c
+++ b/dlls/ntdll/process.c
@@ -294,15 +294,29 @@ NTSTATUS WINAPI NtQueryInformationProcess(
             {
                 if (!ProcessInformation)
                     ret = STATUS_ACCESS_VIOLATION;
-                else if (!ProcessHandle)
-                    ret = STATUS_INVALID_HANDLE;
                 else
                 {
                     memset(&pvmi, 0 , sizeof(VM_COUNTERS));
                     if (ProcessHandle == GetCurrentProcess())
                         fill_VM_COUNTERS(&pvmi);
                     else
-                        FIXME("Need wineserver call to get VM counters for another process\n");
+                    {
+                        SERVER_START_REQ(get_process_vm_counters)
+                        {
+                            req->handle = wine_server_obj_handle( ProcessHandle );
+                            if (!(ret = wine_server_call( req )))
+                            {
+                                pvmi.PeakVirtualSize = reply->peak_virtual_size;
+                                pvmi.VirtualSize = reply->virtual_size;
+                                pvmi.PeakWorkingSetSize = reply->peak_working_set_size;
+                                pvmi.WorkingSetSize = reply->working_set_size;
+                                pvmi.PagefileUsage = reply->pagefile_usage;
+                                pvmi.PeakPagefileUsage = reply->peak_pagefile_usage;
+                            }
+                        }
+                        SERVER_END_REQ;
+                        if (ret) break;
+                    }
 
                     len = ProcessInformationLength;
                     if (len != FIELD_OFFSET(VM_COUNTERS,PrivatePageCount)) len = sizeof(VM_COUNTERS);
diff --git a/dlls/ntdll/tests/info.c b/dlls/ntdll/tests/info.c
index 35e25afd0eb..f8759fe9bd1 100644
--- a/dlls/ntdll/tests/info.c
+++ b/dlls/ntdll/tests/info.c
@@ -1052,6 +1052,7 @@ static void test_query_process_vm(void)
     ULONG ReturnLength;
     VM_COUNTERS pvi;
     ULONG old_size = FIELD_OFFSET(VM_COUNTERS,PrivatePageCount);
+    HANDLE process;
 
     status = pNtQueryInformationProcess(NULL, ProcessVmCounters, NULL, sizeof(pvi), NULL);
     ok( status == STATUS_ACCESS_VIOLATION || status == STATUS_INVALID_HANDLE,
@@ -1079,6 +1080,27 @@ static void test_query_process_vm(void)
     /* Check if we have some return values */
     trace("WorkingSetSize : %ld\n", pvi.WorkingSetSize);
     ok( pvi.WorkingSetSize > 0, "Expected a WorkingSetSize > 0\n");
+
+    process = OpenProcess(PROCESS_VM_READ, FALSE, GetCurrentProcessId());
+    status = pNtQueryInformationProcess(process, ProcessVmCounters, &pvi, sizeof(pvi), NULL);
+    ok( status == STATUS_ACCESS_DENIED, "Expected STATUS_ACCESS_DENIED, got %08x\n", status);
+    CloseHandle(process);
+
+    process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, GetCurrentProcessId());
+    status = pNtQueryInformationProcess(process, ProcessVmCounters, &pvi, sizeof(pvi), NULL);
+    ok( status == STATUS_SUCCESS || broken(!process) /* XP */, "Expected STATUS_SUCCESS, got %08x\n", status);
+    CloseHandle(process);
+
+    memset(&pvi, 0, sizeof(pvi));
+    process = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, GetCurrentProcessId());
+    status = pNtQueryInformationProcess(process, ProcessVmCounters, &pvi, sizeof(pvi), NULL);
+    ok( status == STATUS_SUCCESS, "Expected STATUS_SUCCESS, got %08x\n", status);
+
+    /* Check if we have some return values */
+    trace("WorkingSetSize : %ld\n", pvi.WorkingSetSize);
+    ok( pvi.WorkingSetSize > 0, "Expected a WorkingSetSize > 0\n");
+
+    CloseHandle(process);
 }
 
 static void test_query_process_io(void)
diff --git a/include/wine/server_protocol.h b/include/wine/server_protocol.h
index af792c6f87d..cee0f57c639 100644
--- a/include/wine/server_protocol.h
+++ b/include/wine/server_protocol.h
@@ -885,6 +885,24 @@ struct get_process_info_reply
 
 
 
+struct get_process_vm_counters_request
+{
+    struct request_header __header;
+    obj_handle_t handle;
+};
+struct get_process_vm_counters_reply
+{
+    struct reply_header __header;
+    mem_size_t peak_virtual_size;
+    mem_size_t virtual_size;
+    mem_size_t peak_working_set_size;
+    mem_size_t working_set_size;
+    mem_size_t pagefile_usage;
+    mem_size_t peak_pagefile_usage;
+};
+
+
+
 struct set_process_info_request
 {
     struct request_header __header;
@@ -5541,6 +5559,7 @@ enum request
     REQ_terminate_process,
     REQ_terminate_thread,
     REQ_get_process_info,
+    REQ_get_process_vm_counters,
     REQ_set_process_info,
     REQ_get_thread_info,
     REQ_get_thread_times,
@@ -5831,6 +5850,7 @@ union generic_request
     struct terminate_process_request terminate_process_request;
     struct terminate_thread_request terminate_thread_request;
     struct get_process_info_request get_process_info_request;
+    struct get_process_vm_counters_request get_process_vm_counters_request;
     struct set_process_info_request set_process_info_request;
     struct get_thread_info_request get_thread_info_request;
     struct get_thread_times_request get_thread_times_request;
@@ -6119,6 +6139,7 @@ union generic_reply
     struct terminate_process_reply terminate_process_reply;
     struct terminate_thread_reply terminate_thread_reply;
     struct get_process_info_reply get_process_info_reply;
+    struct get_process_vm_counters_reply get_process_vm_counters_reply;
     struct set_process_info_reply set_process_info_reply;
     struct get_thread_info_reply get_thread_info_reply;
     struct get_thread_times_reply get_thread_times_reply;
@@ -6395,6 +6416,6 @@ union generic_reply
     struct terminate_job_reply terminate_job_reply;
 };
 
-#define SERVER_PROTOCOL_VERSION 530
+#define SERVER_PROTOCOL_VERSION 531
 
 #endif /* __WINE_WINE_SERVER_PROTOCOL_H */
diff --git a/server/process.c b/server/process.c
index e9e2f21de2b..3e40c9eeafd 100644
--- a/server/process.c
+++ b/server/process.c
@@ -1371,6 +1371,44 @@ DECL_HANDLER(get_process_info)
     }
 }
 
+/* retrieve information about a process memory usage */
+DECL_HANDLER(get_process_vm_counters)
+{
+    struct process *process = get_process_from_handle( req->handle, PROCESS_QUERY_LIMITED_INFORMATION );
+
+    if (!process) return;
+#ifdef linux
+    if (process->unix_pid != -1)
+    {
+        FILE *f;
+        char proc_path[32], line[256];
+        unsigned long value;
+
+        sprintf( proc_path, "/proc/%u/status", process->unix_pid );
+        if ((f = fopen( proc_path, "r" )))
+        {
+            while (fgets( line, sizeof(line), f ))
+            {
+                if (sscanf( line, "VmPeak: %lu", &value ))
+                    reply->peak_virtual_size = (mem_size_t)value * 1024;
+                else if (sscanf( line, "VmSize: %lu", &value ))
+                    reply->virtual_size = (mem_size_t)value * 1024;
+                else if (sscanf( line, "VmHWM: %lu", &value ))
+                    reply->peak_working_set_size = (mem_size_t)value * 1024;
+                else if (sscanf( line, "VmRSS: %lu", &value ))
+                    reply->working_set_size = (mem_size_t)value * 1024;
+                else if (sscanf( line, "VmSwap: %lu", &value ))
+                    reply->peak_pagefile_usage = reply->pagefile_usage = (mem_size_t)value * 1024;
+            }
+            fclose( f );
+        }
+        else set_error( STATUS_ACCESS_DENIED );
+    }
+    else set_error( STATUS_ACCESS_DENIED );
+#endif
+    release_object( process );
+}
+
 static void set_process_affinity( struct process *process, affinity_t affinity )
 {
     struct thread *thread;
diff --git a/server/protocol.def b/server/protocol.def
index e9de52214bb..0a9d258fa63 100644
--- a/server/protocol.def
+++ b/server/protocol.def
@@ -845,6 +845,19 @@ struct rawinput_device
 @END
 
 
+/* Retrieve information about a process memory usage */
+@REQ(get_process_vm_counters)
+    obj_handle_t handle;                        /* process handle */
+@REPLY
+    mem_size_t peak_virtual_size;               /* peak virtual memory in bytes */
+    mem_size_t virtual_size;                    /* virtual memory in bytes */
+    mem_size_t peak_working_set_size;           /* peak real memory in bytes */
+    mem_size_t working_set_size;                /* real memory in bytes */
+    mem_size_t pagefile_usage;                  /* swapped-out memory in bytes */
+    mem_size_t peak_pagefile_usage;             /* peak swapped-out memory in bytes */
+@END
+
+
 /* Set a process information */
 @REQ(set_process_info)
     obj_handle_t handle;       /* process handle */
diff --git a/server/request.h b/server/request.h
index d9e27b8c5e9..8b9d9b61f53 100644
--- a/server/request.h
+++ b/server/request.h
@@ -121,6 +121,7 @@ DECL_HANDLER(init_thread);
 DECL_HANDLER(terminate_process);
 DECL_HANDLER(terminate_thread);
 DECL_HANDLER(get_process_info);
+DECL_HANDLER(get_process_vm_counters);
 DECL_HANDLER(set_process_info);
 DECL_HANDLER(get_thread_info);
 DECL_HANDLER(get_thread_times);
@@ -410,6 +411,7 @@ static const req_handler req_handlers[REQ_NB_REQUESTS] =
     (req_handler)req_terminate_process,
     (req_handler)req_terminate_thread,
     (req_handler)req_get_process_info,
+    (req_handler)req_get_process_vm_counters,
     (req_handler)req_set_process_info,
     (req_handler)req_get_thread_info,
     (req_handler)req_get_thread_times,
@@ -796,6 +798,15 @@ C_ASSERT( FIELD_OFFSET(struct get_process_info_reply, cpu) == 56 );
 C_ASSERT( FIELD_OFFSET(struct get_process_info_reply, debugger_present) == 60 );
 C_ASSERT( FIELD_OFFSET(struct get_process_info_reply, debug_children) == 62 );
 C_ASSERT( sizeof(struct get_process_info_reply) == 64 );
+C_ASSERT( FIELD_OFFSET(struct get_process_vm_counters_request, handle) == 12 );
+C_ASSERT( sizeof(struct get_process_vm_counters_request) == 16 );
+C_ASSERT( FIELD_OFFSET(struct get_process_vm_counters_reply, peak_virtual_size) == 8 );
+C_ASSERT( FIELD_OFFSET(struct get_process_vm_counters_reply, virtual_size) == 16 );
+C_ASSERT( FIELD_OFFSET(struct get_process_vm_counters_reply, peak_working_set_size) == 24 );
+C_ASSERT( FIELD_OFFSET(struct get_process_vm_counters_reply, working_set_size) == 32 );
+C_ASSERT( FIELD_OFFSET(struct get_process_vm_counters_reply, pagefile_usage) == 40 );
+C_ASSERT( FIELD_OFFSET(struct get_process_vm_counters_reply, peak_pagefile_usage) == 48 );
+C_ASSERT( sizeof(struct get_process_vm_counters_reply) == 56 );
 C_ASSERT( FIELD_OFFSET(struct set_process_info_request, handle) == 12 );
 C_ASSERT( FIELD_OFFSET(struct set_process_info_request, mask) == 16 );
 C_ASSERT( FIELD_OFFSET(struct set_process_info_request, priority) == 20 );
diff --git a/server/trace.c b/server/trace.c
index d830e4f9c18..7b33043cdf7 100644
--- a/server/trace.c
+++ b/server/trace.c
@@ -1346,6 +1346,21 @@ static void dump_get_process_info_reply( const struct get_process_info_reply *re
     fprintf( stderr, ", debug_children=%d", req->debug_children );
 }
 
+static void dump_get_process_vm_counters_request( const struct get_process_vm_counters_request *req )
+{
+    fprintf( stderr, " handle=%04x", req->handle );
+}
+
+static void dump_get_process_vm_counters_reply( const struct get_process_vm_counters_reply *req )
+{
+    dump_uint64( " peak_virtual_size=", &req->peak_virtual_size );
+    dump_uint64( ", virtual_size=", &req->virtual_size );
+    dump_uint64( ", peak_working_set_size=", &req->peak_working_set_size );
+    dump_uint64( ", working_set_size=", &req->working_set_size );
+    dump_uint64( ", pagefile_usage=", &req->pagefile_usage );
+    dump_uint64( ", peak_pagefile_usage=", &req->peak_pagefile_usage );
+}
+
 static void dump_set_process_info_request( const struct set_process_info_request *req )
 {
     fprintf( stderr, " handle=%04x", req->handle );
@@ -4457,6 +4472,7 @@ static const dump_func req_dumpers[REQ_NB_REQUESTS] = {
     (dump_func)dump_terminate_process_request,
     (dump_func)dump_terminate_thread_request,
     (dump_func)dump_get_process_info_request,
+    (dump_func)dump_get_process_vm_counters_request,
     (dump_func)dump_set_process_info_request,
     (dump_func)dump_get_thread_info_request,
     (dump_func)dump_get_thread_times_request,
@@ -4743,6 +4759,7 @@ static const dump_func reply_dumpers[REQ_NB_REQUESTS] = {
     (dump_func)dump_terminate_process_reply,
     (dump_func)dump_terminate_thread_reply,
     (dump_func)dump_get_process_info_reply,
+    (dump_func)dump_get_process_vm_counters_reply,
     NULL,
     (dump_func)dump_get_thread_info_reply,
     (dump_func)dump_get_thread_times_reply,
@@ -5029,6 +5046,7 @@ static const char * const req_names[REQ_NB_REQUESTS] = {
     "terminate_process",
     "terminate_thread",
     "get_process_info",
+    "get_process_vm_counters",
     "set_process_info",
     "get_thread_info",
     "get_thread_times",
-- 
GitLab