/*
 * Shared Memory WineServer stuff.
 *
 * Copyright (C) 2002,2004 TransGaming Technologies
 */
#include "config.h"

#include <stdlib.h>
#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif
#ifdef HAVE_SYS_IPC_H
# include <sys/ipc.h>
#endif
#ifdef HAVE_SYS_SHM_H
# include <sys/shm.h>
#endif
#ifdef HAVE_SYS_SEM_H
# include <sys/sem.h>
#endif
#include <stdio.h>
#include <string.h>
#include <signal.h>
#ifdef HAVE_SYS_MMAN_H
# include <sys/mman.h>
#endif

#include "config.h"
#include "wine/port.h"

#include "server_memory.h"
#include "request.h"
#include "mem_defs.h"
#include "mem_globals.h"

/* command-line options */
int debug_level = 0;
int persistent_server = 0;
int wineserver_is_daemon = 1;

/* Set to true only in wineserver process */
int is_wineserver = 0; // PH: FIXME: Remove hack

/* PH: FIXME: EVIL HACK */
int is_select = 0; 

/* Data area pointer */
server_memory_area* server_memory_block = NULL;
static off_t allocated_shm_size = 0;

/* this is set by wineserver (the binary) if wine_main_preload_info has been
 * set by the preloader.
 * have to do this double thinger because this symble is exported by the so,
 * not the binary (which is where the preloader looks).
 */
static void* reserved_shm_addr = NULL;
static size_t reserved_shm_size = 0;
void set_shm_reserved(void *addr, const size_t size)
{
    reserved_shm_addr = addr;
    reserved_shm_size = size;
}


#define DEBUG_LEVEL WITH_EXTREME_DEBUG

/* PH: FIXME: Need to have a way to communicate to grow a shared memory area. Is it possible? */
#define initial_size ( 4*1024*1024 ) /* Not too big for now... */
#define shared_memory_address 0x90000000 /* FIXME DH: to go away (can almost go now) */

#define PAGESIZE getpagesize()

#define USE_CSRI_MALLOC

#if defined( HAVE_SEMOP ) && defined( HAVE_SHMGET ) && defined( HAVE_FTOK )
#  define USE_SYSV_IPC
#endif

#define CRITSECTION_SEMA
#if defined( CRITSECTION_SEMA )
#  define RECURIVE_CRITSECION_SEMA
#endif
/* #define MEMORY_PROTECTION */ /* Doesn't work right now */
/* #define HANDLE_SHM_CRASHES */ /* Doesn't work right now */


void protect_memory(void);
void unprotect_memory(void);

static pid_t signal_tid;


#if defined( HANDLE_SHM_CRASHES )
static sigset_t block_sigset;
static sigset_t orig_sigset;
static struct sigaction shm_action;
static struct sigaction shm_orig_sigsegv_action;


// PH: FIXME: Need to do some serious cleanup here. Integrate into signal_i386.c
//            or something.
typedef struct
{
    unsigned short sc_gs, __gsh;
    unsigned short sc_fs, __fsh;
    unsigned short sc_es, __esh;
    unsigned short sc_ds, __dsh;
    unsigned long sc_edi;
    unsigned long sc_esi;
    unsigned long sc_ebp;
    unsigned long sc_esp;
    unsigned long sc_ebx;
    unsigned long sc_edx;
    unsigned long sc_ecx;
    unsigned long sc_eax;
    unsigned long sc_trapno;
    unsigned long sc_err;
    unsigned long sc_eip;
    unsigned short sc_cs, __csh;
    unsigned long sc_eflags;
    unsigned long esp_at_signal;
    unsigned short sc_ss, __ssh;
    unsigned long i387;
    unsigned long oldmask;
    unsigned long cr2;
} SIGCONTEXT;


static void unexpected_shm_signal_handler(int sig)
{
    /* Sigaction is unfortunately per process, not per thread so we may have to do a manual
     * dispatch to the correct signal handler for the running thread.
     */
    if( signal_tid != wine_gettid() )
    {
        shm_orig_sigsegv_action.sa_handler( sig );
        return;
    }

    info_msg( ALWAYS, current, "Fatal Signal during tid %d shmwineserver execution!\n", wine_gettid() );

    release_shm_server_semaphore();

    raise( SIGSTOP );
    _exit(SIGSEGV);
}
#endif /* HANDLE_SHM_CRASHES */

void setup_shm_signal_handling(void)
{
#if defined( HANDLE_SHM_CRASHES )

    sigset_t sigset;

    sigemptyset( &block_sigset );
    sigaddset( &block_sigset, SIGCHLD );
    sigaddset( &block_sigset, SIGHUP );
    sigaddset( &block_sigset, SIGINT );
    sigaddset( &block_sigset, SIGQUIT );
    sigaddset( &block_sigset, SIGTERM );

    sigemptyset( &sigset );
    sigaddset( &sigset, SIGSEGV );
    shm_action.sa_mask = block_sigset;
#if defined( linux )
    shm_action.sa_flags = SA_RESTART;
#else
    shm_action.sa_flags = 0;
#endif
    shm_action.sa_handler = unexpected_shm_signal_handler;

    sigaction( SIGSEGV, &shm_action, &shm_orig_sigsegv_action );
#endif /* HANDLE_SHM_CRASHES */
}

void signal_enter_shm(void)
{
    if( is_wineserver ) return;

    signal_tid = wine_gettid();
}

void signal_leave_shm(void)
{
    if( is_wineserver ) return;

    signal_tid = 0; // PH: FIXME: What's an invalid tid?
}


static const int shared_memory_project_id = 0x54477758; /* "TGwX" in ascii. */

#if defined( USE_SYSV_IPC )

static int shm_sema_id = 0;

#if defined( _SEM_SEMUN_UNDEFINED )
union semun {
		int val;
		struct semid_ds *buf;
		ushort * array;
	};
#endif /* _SEM_SEMUN_UNDEFINED */

static void atexit_destroy_semaphore(void)
{
  if( debug_level ) fprintf( stderr, "atexit destroy of semaphore\n" );
  if( semctl( shm_sema_id, 0, IPC_RMID ) == -1 )
  {
    perror( "Unable to destroy semaphore" );
  }
}

static void create_server_semaphore( key_t key )
{
  union semun argument;
  
  argument.val = 0; /* Initially the semaphore is not available */

  shm_sema_id = semget( key, 1, IPC_CREAT | SHM_R | SHM_W );
  if( shm_sema_id == -1 )
  {
     fatal_perror( "sema create fail" );
  }
  
  /* Set the semaphore as available to start with */
  if( semctl( shm_sema_id, 0, SETVAL, argument ) < 0 )
  {
    fatal_perror( "Can't set value for new sema" );
  }
  
  atexit( atexit_destroy_semaphore );
}

static void get_server_semaphore( key_t key )
{
  shm_sema_id = semget (key, 1, SHM_R | SHM_W );
  if( shm_sema_id == -1 )
  {
     fatal_perror( "sema get fail" );
  }
}

#if defined( CRITSECTION_SEMA ) && defined( __i386__ )
/* PH: FIXME: lifted, and not well, from dlls/ntdll/critsection.c */
inline static PVOID interlocked_cmpxchg( PVOID *dest, PVOID xchg, PVOID compare )
{
    PVOID ret;
    __asm__ __volatile__( "lock; cmpxchgl %2,(%1)"
                          : "=a" (ret) : "r" (dest), "r" (xchg), "0" (compare) : "memory" );
    return ret;
}
inline static LONG interlocked_inc( PLONG dest )
{
    LONG ret;
    __asm__ __volatile__( "lock; xaddl %0,(%1)"
                          : "=r" (ret) : "r" (dest), "0" (1) : "memory" );
    return ret + 1;
}

inline static LONG interlocked_dec( PLONG dest )
{
    LONG ret;
    __asm__ __volatile__( "lock; xaddl %0,(%1)"
                          : "=r" (ret) : "r" (dest), "0" (-1) : "memory" );
    return ret - 1;
}
#elif defined( CRITSECTION_SEMA )
#error "Define functions for your platform"
#endif /* CRITSECTION_SEMA */

#if defined( RECURIVE_CRITSECION_SEMA )
static inline void initialize_recursion(void)
{
  SERVER_GLOBAL_VARIABLE( sema_owning_tid ) = wine_gettid(); /* We start locked for wineserver */
  SERVER_GLOBAL_VARIABLE( sema_recursion_count ) = 1; /* We start locked for wineserver */
}
#endif /* RECURIVE_CRITSECION_SEMA */

/* Called prior to performing anything on shared data */
void acquire_shm_server_semaphore_exclusive(void)
{
   static struct sembuf acquire_operation = { 0, -1, 0 };
   pid_t tid;

   /* Don't do anything if we're not running as a shared memory server */
   if( !SERVER_GLOBAL_VARIABLE( shared_memory_server ) )
     return;

   tid = wine_gettid();

   if( debug_level > DEBUG_LEVEL ) fprintf( stderr, "%d - Attempting to get sema\n", tid );
     
#if defined( CRITSECTION_SEMA )
   if( interlocked_inc( &SERVER_GLOBAL_VARIABLE( sema_lock_count ) ))
   {
#endif

#if defined( RECURIVE_CRITSECION_SEMA )
     if( tid == SERVER_GLOBAL_VARIABLE( sema_owning_tid ))
     {
       SERVER_GLOBAL_VARIABLE( sema_recursion_count )++;
       return;
     }
     else
#endif
     {
       if( debug_level > DEBUG_LEVEL ) fprintf( stderr, "%d - Taking slow path\n", tid );

       while( semop( shm_sema_id, &acquire_operation, 1 ) == -1 )
       {
         if( errno == EINTR ) continue;

         fatal_perror( "sema acquire fail" );
       }
     }
  
#if defined( CRITSECTION_SEMA ) 
   }
#endif

   SERVER_GLOBAL_VARIABLE( sema_owning_tid ) = tid;
   SERVER_GLOBAL_VARIABLE( sema_recursion_count ) = 1;

   if( debug_level > DEBUG_LEVEL ) fprintf( stderr, "%d - Got sema\n", tid );
   
   signal_enter_shm();
   unprotect_memory();
}

/* Call to release */
void release_shm_server_semaphore(void)
{
   static struct sembuf release_operation = { 0, 1, 0 };

   /* Don't do anything if we're not running as a shared memory server */
   if( !SERVER_GLOBAL_VARIABLE( shared_memory_server ) )
     return;

   protect_memory();
   signal_leave_shm();

#if defined( RECURIVE_CRITSECION_SEMA )
   if( --SERVER_GLOBAL_VARIABLE( sema_recursion_count ) )
   {
     interlocked_dec( &SERVER_GLOBAL_VARIABLE( sema_lock_count ) );
   }
   else
   {
     SERVER_GLOBAL_VARIABLE( sema_owning_tid ) = 0;
#endif
#if defined( CRITSECTION_SEMA )
     if( interlocked_dec( &SERVER_GLOBAL_VARIABLE( sema_lock_count ) ) >= 0 )
     {
#endif

       while( semop( shm_sema_id, &release_operation, 1 ) == -1 )
       {
         if( errno == EINTR ) continue;

         fatal_perror( "sema release fail" );
       }
   
#if defined( CRITSECTION_SEMA )
     }
#endif

#if defined( RECURIVE_CRITSECION_SEMA )
   }
#endif /* RECURIVE_CRITSECION_SEMA */

   if( debug_level > DEBUG_LEVEL ) fprintf( stderr, "%d - Released sema\n", wine_gettid() );
}



static int shmid_server;

static void atexit_destroy_shared_memory(void)
{
  if( debug_level ) fprintf( stderr, "atexit destroy of shared_memory\n" );
  if( shmctl( shmid_server, IPC_RMID, 0 ) == -1 )
  {
    perror( "Failed to destory shared memory\n" );
  }
}

#endif /* defined( USE_SYSV_IPC ) */


void* regular_memory_alloc( const char* pathname, int reg_size )
{
  /* Just use malloc since things aren't up and running yet */
  return malloc( initial_size + reg_size ); 
}

/* Anything which needs doing prior to using shm_mem_malloc */
static void setup_shm_malloc(void)
{
#if DEBUG
  _malloc_statsfile = stderr;
#endif /* DEBUG */
}

/* Setup shared memory for the master owner (wineserver process in this case) */
void* create_server_shared_memory_area( const char* pathname, int reg_size )
{
  void *shm_addr, *shm_get_addr;
  key_t shmkey;
  size_t shm_size = initial_size + reg_size;

  /* If we've already established the segment for this process, there's not need to 
   * repeat.
   */
  if( server_memory_block )
    return server_memory_block;

  /* figure out the address we should use */
  shm_get_addr = (void*)shared_memory_address;
  if (reserved_shm_size)
  {
      shm_get_addr = reserved_shm_addr;
      /* now unmmap it, cause we are going to use it for shm */
      if (debug_level > 2)
          fprintf(stderr, "using reserved memory, munmap(%p, 0x%lx)\n",
                  shm_get_addr, (unsigned long)reserved_shm_size);
      munmap(shm_get_addr, reserved_shm_size);
  }
  if (reserved_shm_size && reserved_shm_size < shm_size)
  {
      fprintf(stderr, "sorry, but the preloader didn't reserve enough memory. "
              "preloader reserved 0x%08lx, we want 0x%08lx\n",
              (unsigned long)reserved_shm_size, (unsigned long)shm_size);
      return NULL;
  }

#if defined( USE_SYSV_IPC )    
  shmkey = ftok( pathname, shared_memory_project_id );
  if( shmkey == -1 )
  {
    perror( "can't create key_t for shm" );
    return NULL;
  }

  if( debug_level > 2 ) fprintf( stderr, "Key is 0x%x\n", shmkey );

  /* NOTE: It's alright if it already exists from an unnatural death previously since
   *       we're just going to reiintialize it...
   */  
  while( shm_size > 0 )
  {
    shmid_server = shmget( shmkey, shm_size, IPC_CREAT | SHM_R | SHM_W);
    if( shmid_server != -1 ) break;
    shm_size -= 2*PAGESIZE;
  }

  if( shm_size <= 0 )
  {
     fprintf( stderr, "Unable to allocate any shared memory. Is SYSV shm supported/broken on your system?\n" );
     return NULL;
  }
  else if( shm_size < initial_size + reg_size )
  {
     if( shm_size < (reg_size + 0.5*initial_size) )
     {
       /* Probably not enough memory so don't use shm */
       fprintf( stderr, "A less than optimial amount of shared memory was available (%lu vs %d).\n"
                        "Perhaps your registry is too large?\n", (unsigned long)shm_size,
                        initial_size + reg_size );
       shmctl( shmid_server, IPC_RMID, 0 );
       return NULL;
     }

     if( debug_level )
       fprintf( stderr, "Using shm with less than ideal shm(%lu vs %d).\n",
                           (unsigned long)shm_size, initial_size + reg_size );
  }

  if( debug_level > 1 )
     fprintf( stderr, "Allocated %lu with min_size %d and initial size %d\n",
             (unsigned long)shm_size, reg_size, initial_size );

  /* Get the segment */
  shm_addr = shmat( shmid_server, (void*)shm_get_addr/* FIXME dh: configureable */, 0 );
  if( shm_addr == (void*)-1 )
  {
    perror( "can't map memory? (server)" );
    return NULL;
  }

  /* Setup for this process */
  server_memory_block = shm_addr;
  allocated_shm_size  = shm_size;

  /* Other processes cannot attach if we delete immediately. Mark for destruction on exit. */
  atexit( atexit_destroy_shared_memory );
#endif /* defined( USE_SYSV_IPC ) */

  /* Initialize the memory */
  memset( server_memory_block, 0xff, allocated_shm_size );

  create_server_semaphore( shmkey );

  setup_shm_malloc();

  return shm_addr;
}

void* get_server_shared_memory_area( const char* pathname, size_t shm_size, void *shm_get_addr,
                                     size_t shm_res_size, void *shm_res_addr )
{
  int shmid;
  void* shm_addr;
  key_t shmkey;

  /* If we've already established the segment for this process, there's not need to 
   * repeat.
   */
  if( server_memory_block )
    return server_memory_block;

  /* PH: FIXME: Should probably make sure we destroy everything first if this is the wineserver starting. */

  shmkey = ftok( pathname, shared_memory_project_id );
  if( shmkey == -1 )
  {
    perror( "can't create key_t for shm" );
    return NULL;
  }

  if( debug_level > 2 )  fprintf( stderr, "Key is 0x%x\n", shmkey );

  shmid = shmget( shmkey, shm_size, SHM_R | SHM_W );
  if( shmid == -1 )
  {
    perror( "unable to create/get shared memory segment" );
    return NULL;
  }

  /* if the preloader has reserved memory memory for us, free it here
   * just before the attach.*/
  if (shm_res_size && (shm_res_addr != shm_res_addr || shm_res_size < shm_size))
  {
    fprintf(stderr,
            "the memory reserved for the shm server by the preloader does not match "
            "the memory that the server is using. will use the servers addr, but it may "
            "fail to allocate\n");
  }
  if (shm_size)
  {
      if (debug_level > 2)
          fprintf(stderr, "unmapping reserved area of %p (0x%lx)\n", shm_res_addr,
                  (unsigned long)shm_res_size);
      munmap(shm_res_addr, shm_res_size);
  }

  /* Get the segment */
  shm_addr = shmat( shmid, (void*)shm_get_addr, 0 );
  if( shm_addr == (void*)-1 )
  {
    perror( "can't map memory (client)?" );
    return NULL;
  }

  /* Setup for this process */
  server_memory_block = shm_addr;
  allocated_shm_size  = shm_size;

  /* Mark the segment to be destroyed when not used anymore */
  /* PH: FIXME: I think that this needs to turn into an atexit( shmdt... */
  //shmctl( shmid, IPC_RMID, 0 );

  get_server_semaphore( shmkey );

  setup_shm_malloc();

  setup_shm_signal_handling();

  return shm_addr;
}




struct dynamic_memory_management
{
  size_t size;  /* Size of the dynamic memory area (constant) */
  void*  start; /* Start of the dynamic memory area (constant) */
  void*  free;  /* Ptr to the not yet used area */
};

#define DYN_MEM_AREA( smb )  ((struct dynamic_memory_management*)((smb)+1))

extern size_t shm_allocator_size(void);
extern void shm_setup_memory( void* ptr );

void init_memory_block( int shm )
{
  memset( server_memory_block, 0, sizeof( *server_memory_block ) );
  
  DYN_MEM_AREA(server_memory_block)->size = allocated_shm_size - sizeof( server_memory_block ) - sizeof(struct dynamic_memory_management) - shm_allocator_size();

  /* Make this page aligned for some malloc speed and to give a buffer before user data */
  DYN_MEM_AREA(server_memory_block)->free = (void*)((char*)server_memory_block + ((sizeof(*server_memory_block) + sizeof(struct dynamic_memory_management) + shm_allocator_size() + PAGESIZE-1) & ~(PAGESIZE-1)));
  DYN_MEM_AREA(server_memory_block)->start = DYN_MEM_AREA(server_memory_block)->free;
  
  /* Setup memory for the memory allocator variables */
  shm_setup_memory( (void*)((char*)server_memory_block + (sizeof(*server_memory_block) + sizeof(struct dynamic_memory_management)) ));

  SERVER_GLOBAL_VARIABLE( first_thread ) = NULL;
  SERVER_GLOBAL_VARIABLE( threadids ) = NULL;
  SERVER_GLOBAL_VARIABLE( first_free_threadid ) = NULL;
  SERVER_GLOBAL_VARIABLE( last_free_threadid ) = NULL;
#if 0
  /* FIXME MOOF NOTE: Currently the threads and processes won't
     share the same IDs. ie. they come from the same pool. */
  SERVER_GLOBAL_VARIABLE( processids ) = NULL;
  SERVER_GLOBAL_VARIABLE( first_free_processid ) = NULL;
  SERVER_GLOBAL_VARIABLE( last_free_processid ) = NULL;
#endif
  SERVER_GLOBAL_VARIABLE( server_start_ticks ) = 0;
  SERVER_GLOBAL_VARIABLE( shared_memory_server ) = shm;
  SERVER_GLOBAL_VARIABLE( shared_memory_server_size ) = allocated_shm_size;
  SERVER_GLOBAL_VARIABLE( shared_memory_server_addr ) = server_memory_block;
  SERVER_GLOBAL_VARIABLE( serverdir ) = NULL;
  SERVER_GLOBAL_VARIABLE( sema_lock_count ) = 0; /* We start locked for wineserver */
#if defined( RECURIVE_CRITSECION_SEMA )
  SERVER_GLOBAL_VARIABLE( sema_owning_tid ) = wine_gettid(); /* We start locked for wineserver */
  SERVER_GLOBAL_VARIABLE( sema_recursion_count ) = 1; /* We start locked for wineserver */
#endif

  /* NOTE: Can now use mem_alloc and other memory wrapping functions */

  STATIC_INITIALIZER_FUNCTION_CALL( atom );
  STATIC_INITIALIZER_FUNCTION_CALL( cdrom_eject );
  STATIC_INITIALIZER_FUNCTION_CALL( console );
  STATIC_INITIALIZER_FUNCTION_CALL( distributed );
  STATIC_INITIALIZER_FUNCTION_CALL( file );
  STATIC_INITIALIZER_FUNCTION_CALL( handle );
  STATIC_INITIALIZER_FUNCTION_CALL( mapping );
  STATIC_INITIALIZER_FUNCTION_CALL( object );
  STATIC_INITIALIZER_FUNCTION_CALL( process );
  STATIC_INITIALIZER_FUNCTION_CALL( registry );
  STATIC_INITIALIZER_FUNCTION_CALL( rpc_epmap );
  STATIC_INITIALIZER_FUNCTION_CALL( select );
  STATIC_INITIALIZER_FUNCTION_CALL( thread );
  STATIC_INITIALIZER_FUNCTION_CALL( user );
  STATIC_INITIALIZER_FUNCTION_CALL( window );
}


#if defined( USE_CSRI_MALLOC )

static void* simple_allocator(size_t size)
{
  void* new_area = DYN_MEM_AREA(server_memory_block)->free;
  
  if( (char*)DYN_MEM_AREA(server_memory_block)->free + size <= (char*)server_memory_block + DYN_MEM_AREA(server_memory_block)->size )
  {
    DYN_MEM_AREA(server_memory_block)->free = (char*)DYN_MEM_AREA(server_memory_block)->free + size;
  }
  else
  {
    /* Not enough memory available for the request */
    fatal_error( "not enough memory for allocation of %d bytes\n"
                 "start=%p present=%p total=0x%x\n",
		               size,
              		 DYN_MEM_AREA(server_memory_block)->start,
              		 DYN_MEM_AREA(server_memory_block)->free,
              		 DYN_MEM_AREA(server_memory_block)->size );
    new_area = (void*)-1;
  }
  
  return new_area;
}

/* For hooking into the CSRI memory allocator */
void* (*_malloc_memfunc)(size_t) = simple_allocator;

#endif /* USE_CSRI_MALLOC */

#define CALLBACK_TAG( name ) get_##name##_callback
static const get_callback_function callback_lookup_table[ last_callback_tag_value - first_callback_tag_value + 1 ] =
{
  CALLBACK_TAG( queue_timer_timeout ),
  CALLBACK_TAG( timer_timer_timeout ),
  CALLBACK_TAG( queue_result_timeout ),
  CALLBACK_TAG( async ),
  CALLBACK_TAG( periodic_save ),
  CALLBACK_TAG( cdrom_eject ),
  CALLBACK_TAG( thread_thread_timeout ),
};
#undef CALLBACK_TAG

/* Virutalized multiprocess callbacks */
timeout_callback get_callback( timeout_callback cb )
{
  if( (long)cb < first_callback_tag_value || (long)cb > last_callback_tag_value )
  {
    fprintf( stderr, "Ah. Unimplemented callback %p\n", cb );
    *(int*)0 = 0x89;
  }

  return callback_lookup_table[ (long)cb - (long)first_callback_tag_value ]();
}


/* The order must be correct or obviously the mapping doesn't hold up */
#define OPS_TAG( name ) get_this_process_##name
static const ops_function ops_lookup_table[ last_ops_tag_value - first_ops_tag_value + 1 ] =
{
  OPS_TAG( atom_table_ops ),
  OPS_TAG( change_ops ),
  OPS_TAG( console_input_ops ),
  OPS_TAG( console_input_events_ops ),
  OPS_TAG( screen_buffer_ops ),
  OPS_TAG( debug_event_ops ),
  OPS_TAG( debug_ctx_ops ),
  OPS_TAG( device_ops ),
  OPS_TAG( event_ops ),
  OPS_TAG( file_ops  ),
  NULL,
  OPS_TAG( handle_table_ops ),
  OPS_TAG( mapping_ops ),
  OPS_TAG( mutex_ops ),
  OPS_TAG( named_pipe_ops ),
  OPS_TAG( pipe_user_ops ),
  OPS_TAG( process_ops ),
  OPS_TAG( startup_info_ops ),
  OPS_TAG( msg_queue_ops ),
  OPS_TAG( key_ops ),
  OPS_TAG( master_socket_ops ),
  OPS_TAG( semaphore_ops ),
  OPS_TAG( serial_ops ),
  OPS_TAG( snapshot_ops ),
  OPS_TAG( sock_ops ),
  OPS_TAG( thread_ops ),
  OPS_TAG( timer_ops ),
  OPS_TAG( fd_server_ops ),
  OPS_TAG( distributed_commands_ops ),
  OPS_TAG( token_ops )
};

#undef OPS_TAG

/* Virtualized multiprocess object operations vtable */
const struct object_ops* get_ops( const struct object_ops* ops )
{
  if( (long)ops < first_ops_tag_value || (long)ops > last_ops_tag_value )
  {
    fprintf( stderr, "Ah. Unimplemented op %p\n", ops );
    *(int*)0 = 0x89;
  }

  return ops_lookup_table[ (long)ops - (long)first_ops_tag_value ]();
}


/* Entry point for freeing and allocating shared memory. Required if 1 side of library or other needs to allocate 
 * free and the other does the opposite.
 */
void* alloc_server_memory( size_t size )
{
  void* ptr;
  acquire_shm_server_semaphore_exclusive();
    ptr = mem_alloc( size );
  release_shm_server_semaphore();
  return ptr;
}

void free_server_memory( void* ptr )
{
  acquire_shm_server_semaphore_exclusive();
    mem_free( ptr );
  release_shm_server_semaphore();
  
}

void protect_memory(void)
{
#if defined( MEMORY_PROTECTION )
  int err;

  /* PH: FIXME: Kludge because the wineserver poll is broken */
  if( is_wineserver ) return;

  /* Cheat. We need access to the first bit of memory as that's where the semaphore stuff is */
  err = mprotect( (void*)server_memory_block+PAGESIZE, allocated_shm_size-PAGESIZE, PROT_NONE );

  if( err == -1 )
  {
    fprintf( stderr, "Protect failed: %d, %s\n", errno, strerror( errno ) );
  }
#endif
}

void unprotect_memory(void)
{
#if defined( MEMORY_PROTECTION )
  int err;

  /* PH: FIXME: Kludge because the wineserver poll is broken */
  if( is_wineserver ) return;

  /* Cheat. We need access to the first bit of memory as that's where the semaphore stuff is */
  err = mprotect( (void*)server_memory_block+PAGESIZE, allocated_shm_size-PAGESIZE, PROT_READ | PROT_WRITE );

  if( err == -1 )
  {
    fprintf( stderr, "Protect failed: %d, %s\n", errno, strerror( errno ) );
  }
#endif
}

void set_debug_level( int debuglevel )
{
  if( debuglevel || debug_level ) fprintf( stderr, "Setting the debug level to %d\n", debuglevel );
  debug_level = debuglevel;
}

/* Return the protocol version that is used for communications */
int shared_memory_protocol_version( void )
{
  return 1;
}

