From cc8a09773c259881d21c67f3b99b07208bee2cbe Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Michael=20M=C3=BCller?= <michael@fds-team.de>
Date: Thu, 19 Mar 2015 01:22:34 +0100
Subject: server: Implement support for global and local shared memory blocks
 based on memfd.

---
 dlls/ntdll/ntdll_misc.h |  1 +
 dlls/ntdll/server.c     | 64 ++++++++++++++++++++++++++++++++++++++++++
 dlls/ntdll/thread.c     |  4 +++
 include/wine/server.h   | 11 ++++++++
 include/winternl.h      |  2 +-
 server/fd.c             | 27 ++++++++++++++++++
 server/file.h           |  8 ++++++
 server/main.c           |  1 +
 server/mapping.c        | 74 +++++++++++++++++++++++++++++++++++++++++++++++++
 server/protocol.def     | 15 ++++++++++
 server/thread.c         |  7 +++++
 server/thread.h         |  2 ++
 12 files changed, 215 insertions(+), 1 deletion(-)

diff --git a/dlls/ntdll/ntdll_misc.h b/dlls/ntdll/ntdll_misc.h
index 539944e..493246c 100644
--- a/dlls/ntdll/ntdll_misc.h
+++ b/dlls/ntdll/ntdll_misc.h
@@ -99,6 +99,7 @@ extern int server_get_unix_fd( HANDLE handle, unsigned int access, int *unix_fd,
 extern int server_pipe( int fd[2] ) DECLSPEC_HIDDEN;
 extern NTSTATUS alloc_object_attributes( const OBJECT_ATTRIBUTES *attr, struct object_attributes **ret,
                                          data_size_t *ret_len ) DECLSPEC_HIDDEN;
+extern void *server_get_shared_memory( HANDLE thread ) DECLSPEC_HIDDEN;
 
 /* module handling */
 extern LIST_ENTRY tls_links DECLSPEC_HIDDEN;
diff --git a/dlls/ntdll/server.c b/dlls/ntdll/server.c
index 356d631..a91e478 100644
--- a/dlls/ntdll/server.c
+++ b/dlls/ntdll/server.c
@@ -972,6 +972,66 @@ done:
 
 
 /***********************************************************************
+ *           server_get_shared_memory_fd
+ *
+ * Receive a file descriptor to a server shared memory block.
+ */
+static int server_get_shared_memory_fd( HANDLE thread, int *unix_fd )
+{
+    obj_handle_t dummy;
+    sigset_t sigset;
+    int ret;
+
+    server_enter_uninterrupted_section( &fd_cache_section, &sigset );
+
+    SERVER_START_REQ( get_shared_memory )
+    {
+        req->tid = HandleToULong(thread);
+        if (!(ret = wine_server_call( req )))
+        {
+            *unix_fd = receive_fd( &dummy );
+            if (*unix_fd == -1) ret = STATUS_NOT_SUPPORTED;
+        }
+    }
+    SERVER_END_REQ;
+
+    server_leave_uninterrupted_section( &fd_cache_section, &sigset );
+    return ret;
+}
+
+
+/***********************************************************************
+ *           server_get_shared_memory
+ *
+ * Get address of a shared memory block.
+ */
+void *server_get_shared_memory( HANDLE thread )
+{
+    static shmglobal_t *shmglobal = (void *)-1;
+    void *mem = NULL;
+    int fd = -1;
+
+    /* The global memory block is only requested once. No locking is
+     * required because this function is called very early during the
+     * process initialization for the first time. */
+    if (!thread && shmglobal != (void *)-1)
+        return shmglobal;
+
+    if (!server_get_shared_memory_fd( thread, &fd ))
+    {
+        SIZE_T size = thread ? sizeof(shmlocal_t) : sizeof(shmglobal_t);
+        virtual_map_shared_memory( fd, &mem, 0, &size, PAGE_READONLY );
+        close( fd );
+    }
+
+    if (!thread)
+        shmglobal = mem;
+
+    return mem;
+}
+
+
+/***********************************************************************
  *           wine_server_fd_to_handle   (NTDLL.@)
  *
  * Allocate a file handle for a Unix file descriptor.
@@ -1503,6 +1563,10 @@ size_t server_init_thread( void *entry_point )
     }
     SERVER_END_REQ;
 
+    /* initialize thread shared memory pointers */
+    NtCurrentTeb()->Reserved5[0] = server_get_shared_memory( 0 );
+    NtCurrentTeb()->Reserved5[1] = server_get_shared_memory( NtCurrentTeb()->ClientId.UniqueThread );
+
     is_wow64 = !is_win64 && (server_cpus & ((1 << CPU_x86_64) | (1 << CPU_ARM64))) != 0;
     ntdll_get_thread_data()->wow64_redir = is_wow64;
 
diff --git a/dlls/ntdll/thread.c b/dlls/ntdll/thread.c
index 9f8e909..c91b27a 100644
--- a/dlls/ntdll/thread.c
+++ b/dlls/ntdll/thread.c
@@ -376,6 +376,7 @@ void terminate_thread( int status )
 void exit_thread( int status )
 {
     static void *prev_teb;
+    shmlocal_t *shmlocal;
     sigset_t sigset;
     TEB *teb;
 
@@ -399,6 +400,9 @@ void exit_thread( int status )
     LdrShutdownThread();
     RtlFreeThreadActivationContextStack();
 
+    shmlocal = interlocked_xchg_ptr( &NtCurrentTeb()->Reserved5[1], NULL );
+    if (shmlocal) NtUnmapViewOfSection( NtCurrentProcess(), shmlocal );
+
     pthread_sigmask( SIG_BLOCK, &server_block_set, NULL );
 
     if ((teb = interlocked_xchg_ptr( &prev_teb, NtCurrentTeb() )))
diff --git a/include/wine/server.h b/include/wine/server.h
index d573d1f..695389a 100644
--- a/include/wine/server.h
+++ b/include/wine/server.h
@@ -120,6 +120,17 @@ static inline void *wine_server_get_ptr( client_ptr_t ptr )
     return (void *)(ULONG_PTR)ptr;
 }
 
+/* returns a pointer to the wineserver global shared memory block */
+static inline shmglobal_t *wine_get_shmglobal(void)
+{
+    return (shmglobal_t *)NtCurrentTeb()->Reserved5[0];
+}
+
+/* returns a pointer to the wineserver local shared memory block */
+static inline shmlocal_t *wine_get_shmlocal(void)
+{
+    return (shmlocal_t *)NtCurrentTeb()->Reserved5[1];
+}
 
 /* macros for server requests */
 
diff --git a/include/winternl.h b/include/winternl.h
index 3494c00..b733f78 100644
--- a/include/winternl.h
+++ b/include/winternl.h
@@ -366,7 +366,7 @@ typedef struct _TEB
     PVOID                        Spare4;                            /* f7c/1750 */
     PVOID                        ReservedForOle;                    /* f80/1758 */
     ULONG                        WaitingOnLoaderLock;               /* f84/1760 */
-    PVOID                        Reserved5[3];                      /* f88/1768 */
+    PVOID                        Reserved5[3];                      /* f88/1768 used for wineserver shared memory */
     PVOID                       *TlsExpansionSlots;                 /* f94/1780 */
     ULONG                        ImpersonationLocale;               /* f98/1788 */
     ULONG                        IsImpersonating;                   /* f9c/178c */
diff --git a/server/fd.c b/server/fd.c
index e3fe292..00731d1 100644
--- a/server/fd.c
+++ b/server/fd.c
@@ -2481,6 +2481,33 @@ DECL_HANDLER(write)
     }
 }
 
+/* get file descriptor to shared memory block */
+DECL_HANDLER(get_shared_memory)
+{
+    if (req->tid)
+    {
+        struct thread *thread = get_thread_from_id( req->tid );
+        if (thread)
+        {
+            if (thread->shm_fd != -1 || allocate_shared_memory( &thread->shm_fd,
+                (void **)&thread->shm, sizeof(*thread->shm) ))
+            {
+                send_client_fd( current->process, thread->shm_fd, 0 );
+            }
+            else
+                set_error( STATUS_NOT_SUPPORTED );
+            release_object( thread );
+        }
+    }
+    else
+    {
+        if (shmglobal_fd != -1)
+            send_client_fd( current->process, shmglobal_fd, 0 );
+        else
+            set_error( STATUS_NOT_SUPPORTED );
+    }
+}
+
 /* perform an ioctl on a file */
 DECL_HANDLER(ioctl)
 {
diff --git a/server/file.h b/server/file.h
index b59de1c..8a9f6b9 100644
--- a/server/file.h
+++ b/server/file.h
@@ -146,6 +146,14 @@ extern void create_mailslot_device( struct directory *root, const struct unicode
 extern struct device *create_unix_device( struct directory *root, const struct unicode_str *name,
                                           const char *unix_path );
 
+/* shared memory functions */
+
+extern int allocate_shared_memory( int *fd, void **memory, size_t size );
+extern void release_shared_memory( int fd, void *memory, size_t size );
+extern void init_shared_memory( void );
+extern shmglobal_t *shmglobal;
+extern int          shmglobal_fd;
+
 /* change notification functions */
 
 extern void do_change_notify( int unix_fd );
diff --git a/server/main.c b/server/main.c
index 7aed338..f984bfc 100644
--- a/server/main.c
+++ b/server/main.c
@@ -145,6 +145,7 @@ int main( int argc, char *argv[] )
     init_signals();
     init_directories();
     init_registry();
+    init_shared_memory();
     main_loop();
     return 0;
 }
diff --git a/server/mapping.c b/server/mapping.c
index 16e7c1c..c180ae5 100644
--- a/server/mapping.c
+++ b/server/mapping.c
@@ -29,8 +29,32 @@
 #ifdef HAVE_SYS_MMAN_H
 # include <sys/mman.h>
 #endif
+#ifdef HAVE_SYS_SYSCALL_H
+# include <sys/syscall.h>
+#endif
 #include <unistd.h>
 
+#if defined(__linux__) && (defined(__i386__) || defined(__x86_64__))
+
+/* __NR_memfd_create might not yet be available when buildservers use an old kernel */
+#ifndef __NR_memfd_create
+#ifdef __x86_64__
+#define __NR_memfd_create 319
+#else
+#define __NR_memfd_create 356
+#endif
+#endif
+
+/* the following declarations are only available in linux/fcntl.h, but not fcntl.h */
+#define F_LINUX_SPECIFIC_BASE   1024
+#define F_ADD_SEALS             (F_LINUX_SPECIFIC_BASE + 9)
+#define MFD_ALLOW_SEALING       0x0002U
+#define F_SEAL_SEAL             0x0001
+#define F_SEAL_SHRINK           0x0002
+#define F_SEAL_GROW             0x0004
+
+#endif
+
 #include "ntstatus.h"
 #define WIN32_NO_STATUS
 #include "windef.h"
@@ -114,6 +138,10 @@ static struct list shared_list = LIST_INIT(shared_list);
 
 static size_t page_mask;
 
+/* global shared memory */
+shmglobal_t *shmglobal;
+int          shmglobal_fd;
+
 #define ROUND_SIZE(size)  (((size) + page_mask) & ~page_mask)
 
 
@@ -158,6 +186,52 @@ static int check_current_dir_for_exec(void)
     return (ret != MAP_FAILED);
 }
 
+/* allocates a block of shared memory */
+int allocate_shared_memory( int *fd, void **memory, size_t size )
+{
+#if defined(__linux__) && (defined(__i386__) || defined(__x86_64__))
+    void *shm_mem;
+    int shm_fd;
+
+    shm_fd = syscall( __NR_memfd_create, "wineserver_shm", MFD_ALLOW_SEALING );
+    if (shm_fd == -1) goto err;
+    if (grow_file( shm_fd, size ))
+    {
+        if (fcntl( shm_fd, F_ADD_SEALS, F_SEAL_SEAL | F_SEAL_SHRINK | F_SEAL_GROW ) >= 0)
+        {
+            shm_mem = mmap( 0, size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0 );
+            if (shm_mem != MAP_FAILED)
+            {
+                memset( shm_mem, 0, size );
+                *fd     = shm_fd;
+                *memory = shm_mem;
+                return 1;
+            }
+        }
+    }
+    close( shm_fd );
+err:
+#endif
+    *memory = NULL;
+    *fd = -1;
+    return 0;
+}
+
+/* releases a block of shared memory */
+void release_shared_memory( int fd, void *memory, size_t size )
+{
+#if defined(__linux__) && (defined(__i386__) || defined(__x86_64__))
+    if (memory) munmap( memory, size );
+    if (fd != -1) close( fd );
+#endif
+}
+
+/* intialize shared memory management */
+void init_shared_memory( void )
+{
+    allocate_shared_memory( &shmglobal_fd, (void **)&shmglobal, sizeof(*shmglobal) );
+}
+
 /* create a temp file for anonymous mappings */
 static int create_temp_file( file_pos_t size )
 {
diff --git a/server/protocol.def b/server/protocol.def
index ea5bd61..8ce9973 100644
--- a/server/protocol.def
+++ b/server/protocol.def
@@ -69,6 +69,15 @@ struct request_max_size
 #define FIRST_USER_HANDLE 0x0020  /* first possible value for low word of user handle */
 #define LAST_USER_HANDLE  0xffef  /* last possible value for low word of user handle */
 
+typedef struct
+{
+    int dummy;
+} shmglobal_t;
+
+typedef struct
+{
+    int dummy;
+} shmlocal_t;
 
 /* debug event data */
 typedef union
@@ -1204,6 +1213,12 @@ enum server_fd_type
 };
 
 
+/* Get file descriptor for shared memory */
+@REQ(get_shared_memory)
+    thread_id_t tid;            /* thread id or 0 */
+@END
+
+
 /* Flush a file buffers */
 @REQ(flush)
     int            blocking;    /* whether it's a blocking flush */
diff --git a/server/thread.c b/server/thread.c
index 251be1c..198c371 100644
--- a/server/thread.c
+++ b/server/thread.c
@@ -196,6 +196,8 @@ static inline void init_thread_structure( struct thread *thread )
     thread->desktop_users   = 0;
     thread->token           = NULL;
     thread->exit_poll       = NULL;
+    thread->shm_fd          = -1;
+    thread->shm             = NULL;
 
     thread->creation_time = current_time;
     thread->exit_time     = 0;
@@ -297,6 +299,8 @@ static void cleanup_thread( struct thread *thread )
             thread->inflight[i].client = thread->inflight[i].server = -1;
         }
     }
+    release_shared_memory( thread->shm_fd, thread->shm, sizeof(*thread->shm) );
+
     thread->req_data = NULL;
     thread->reply_data = NULL;
     thread->request_fd = NULL;
@@ -305,6 +309,9 @@ static void cleanup_thread( struct thread *thread )
     thread->context = NULL;
     thread->suspend_context = NULL;
     thread->desktop = 0;
+    thread->shm_fd = -1;
+    thread->shm = NULL;
+
 }
 
 /* destroy a thread when its refcount is 0 */
diff --git a/server/thread.h b/server/thread.h
index ac9af24..59ef250 100644
--- a/server/thread.h
+++ b/server/thread.h
@@ -89,6 +89,8 @@ struct thread
     timeout_t              exit_time;     /* Thread exit time */
     struct token          *token;         /* security token associated with this thread */
     struct timeout_user   *exit_poll;     /* poll if the thread/process has exited already */
+    int                    shm_fd;        /* file descriptor for thread local shared memory */
+    shmlocal_t            *shm;           /* thread local shared memory pointer */
 };
 
 struct thread_snapshot
-- 
2.6.4

