/* 
 * File Descriptor Translation Server
 *
 * Copyright (C) 2004 TransGaming Technologies.
 *
 */

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

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>

#ifdef HAVE_SYS_SOCKET_H
# include <sys/socket.h>
#endif
#include <sys/uio.h>
#include <sys/un.h>
#include <unistd.h>
#include <errno.h>
#include <sched.h>
#include <signal.h>
#include <fcntl.h>

#include "object.h"
#include "request.h"

#define SOCKETNAME "fd-socket"        /* name of the socket file */

#if 0
#if !defined( USE_PTHREADS )
#define USE_PTHREADS
#include <pthread.h>
#endif
#endif

extern struct context_entry* server_context_fd_list;
extern unsigned int server_context_fd_list_size;

struct fd_server
{
    struct object       obj;         /* object header */
};

static void fd_server_dump( struct object *obj, int verbose );
static void fd_server_poll_event( struct object *obj, int event );
static void fd_server_destroy( struct object *obj );

static const struct object_ops fd_server_ops =
{
    sizeof(struct fd_server),      /* size */
    fd_server_dump,                /* dump */
    no_add_queue,                  /* add_queue */
    NULL,                          /* remove_queue */
    NULL,                          /* signaled */
    NULL,                          /* satisfied */
    NULL,                          /* get_poll_events */
    fd_server_poll_event,          /* poll_event */
    no_get_fd,                     /* get_fd */
#if defined( CONTEXTUAL_FD )
    no_get_ctx_fd,                 /* get_ctx_fd */
#endif
    no_flush,                      /* flush */
    no_get_file_info,              /* get_file_info */
    NULL,                          /* queue_async */
    fd_server_destroy              /* destroy */
};
const struct object_ops* get_this_process_fd_server_ops(void) { return &fd_server_ops; }

static struct fd_server* fd_server_socket;  /* the fd server socket object */
static void create_fd_server( int fd );

static int fd_server_socket_fd = -1; /* Per process */

static struct pollfd* poll_list = NULL;
static const struct pollfd init_poll_list = {-1,0,0};
static unsigned int poll_users = 0;
static unsigned int max_poll_users = 0;

/* wineserver -> fd server communication. 1 is for wineserver talk. 0 is for fd server listen */
static int fd_server_pipe[2] = { -1, -1 };




/* PH: FIXME: Alot of this is copied from master socket stuff. */

static void fd_server_dump( struct object *obj, int verbose )
{
    struct fd_server *sock = (struct fd_server *)obj;
    assert( obj->ops == fd_server_ops_tag );
    fprintf( stderr, "FD Server socket fd=%d\n", get_object_fd_for_server( &sock->obj ) );
}

static void fd_server_poll_event( struct object *obj, int event )
{
    struct fd_server *sock = (struct fd_server *)obj;
    assert( obj->ops == fd_server_ops_tag );

    assert( sock == fd_server_socket );  /* there is only one fd server socket */

    if (event & (POLLERR | POLLHUP))
    {
        /* this is not supposed to happen */
        fprintf( stderr, "wineserver: Error on fd server socket\n" );
        release_object( obj );
    }
    else if (event & POLLIN)
    {
        struct sockaddr_un dummy;
        int len = sizeof(dummy);
        int client = accept( get_object_fd_for_server( &fd_server_socket->obj ),
                              (struct sockaddr *) &dummy, &len );
        if (client == -1) return;
        fcntl( client, F_SETFL, O_NONBLOCK );

        create_fd_server( client );
    }
}

/* remove the socket upon exit */
static void socket_cleanup(void)
{
    static int do_it_once;
    if (!do_it_once++) unlink( SOCKETNAME );
}

static void fd_server_destroy( struct object *obj )
{
    assert( obj->ops == fd_server_ops_tag );

    socket_cleanup();

    /* Close our end of the pipe to the fd server */
    close( fd_server_pipe[1] );
    fd_server_pipe[0] = -1;
}

void open_fd_server_socket(void)
{
    struct sockaddr_un addr;
    int fd, slen;

    if ((fd = socket( AF_UNIX, SOCK_STREAM, 0 )) == -1) fatal_perror( "fd server socket" );
    addr.sun_family = AF_UNIX;
    strcpy( addr.sun_path, SOCKETNAME );
    slen = sizeof(addr) - sizeof(addr.sun_path) + strlen(addr.sun_path) + 1;
#ifdef HAVE_SOCKADDR_SUN_LEN
    addr.sun_len = slen;
#endif
    if (bind( fd, (struct sockaddr *)&addr, slen ) == -1)
    {
       /* If the socket already exists then the master_socket should have bailed us out by now */
       fatal_perror( "fd server bind" );
    }
    if( atexit( socket_cleanup ) ) fatal_perror( "fd server atexit" );

    chmod( SOCKETNAME, 0600 );  /* make sure no other user can connect */
    if (listen( fd, 5 ) == -1) fatal_perror( "listen" );

    if (!(fd_server_socket = alloc_object( fd_server_ops_tag, fd )))
        fatal_error( "out of memory\n" );
    set_select_events( &fd_server_socket->obj, POLLIN, NULL );
}

void close_fd_server_socket(void)
{
    release_object( fd_server_socket );
}

/* receive a file descriptor on the fd server socket */
int receive_server_fd( int socket_fd )
{
    int data;
    int fd, ret;
    struct iovec myiovec;
    struct msghdr msghdr;

#ifndef HAVE_MSGHDR_ACCRIGHTS
struct cmsg_fd
{
    int len;   /* sizeof structure */
    int level; /* SOL_SOCKET */
    int type;  /* SCM_RIGHTS */
    int fd;    /* fd to pass */
};
static struct cmsg_fd cmsg = { sizeof(cmsg), SOL_SOCKET, SCM_RIGHTS, -1 };
#endif  /* HAVE_MSGHDR_ACCRIGHTS */

#ifdef HAVE_MSGHDR_ACCRIGHTS
    msghdr.msg_accrightslen = sizeof(fd);
    msghdr.msg_accrights = (void *)&fd;
#else  /* HAVE_MSGHDR_ACCRIGHTS */
    msghdr.msg_control    = &cmsg;
    msghdr.msg_controllen = sizeof(cmsg);
    cmsg.fd = -1;
#endif  /* HAVE_MSGHDR_ACCRIGHTS */

    msghdr.msg_name    = NULL;
    msghdr.msg_namelen = 0;
    msghdr.msg_iov     = &myiovec;
    msghdr.msg_iovlen  = 1;

    myiovec.iov_base = &data;
    myiovec.iov_len  = sizeof(data);

    ret = recvmsg( socket_fd, &msghdr, 0 );
#ifndef HAVE_MSGHDR_ACCRIGHTS
    fd = cmsg.fd;
#endif

    if (ret == sizeof(data))
    {
        info_msg( WITH_DEBUG, current, "*fd* %d <- (fd server)%d\n", fd, socket_fd );
        return fd;
    }


    if (ret >= 0)
    {
        if (ret > 0)
            info_msg( ALWAYS, current, "Protocol error: partial recvmsg %d for fd\n", ret );
    }
    else
    {
        if (errno != EWOULDBLOCK && errno != EAGAIN)
        {
            info_msg( ALWAYS, current, "Protocol error: " );
            perror( "recvmsg" );
        }
    }

    return -1;
}

#define FD_TRANSLATION_REQUEST 0

struct fds_translate_fd_msg_body
{
   context_fd ctx_fd; 
};

struct fds_msg
{
   int type;
   union
   {
       struct fds_translate_fd_msg_body tfd;
   } u;
};

/* Send an fd to the fd server to be translated and return the translated result */
int translate_server_fd( context_fd ctx_fd )
{
    int ret;
    struct fds_msg msg;
    int ret_fd;

    info_msg( WITH_DEBUG, current, "translate *fd* %d -> fd server(%d)\n", ctx_fd, fd_server_socket_fd );

    msg.type = FD_TRANSLATION_REQUEST;
    msg.u.tfd.ctx_fd = ctx_fd;

    ret = write( fd_server_socket_fd, &msg, sizeof( msg ) );
    if( ret != sizeof( msg ) ) fatal_perror( "client: translate write" );

    info_msg( WITH_DEBUG, current, "translate waiting for response\n" );

    ret_fd = receive_server_fd( fd_server_socket_fd );

    /* Since this file descriptor exists in the shm client address space, we need to 
     * make sure it's closed if a fork is performed.
     */
    if( ret_fd != -1 )
        fcntl( ret_fd, F_SETFD, 1 );

    return ret_fd;
}

/* send an fd to a client */
static int translate_client_fd( int send_to_fd, int fd )
{
    int ret;
    struct iovec myiovec;
    struct msghdr msghdr;

#ifndef HAVE_MSGHDR_ACCRIGHTS
struct cmsg_fd
{
    int len;   /* sizeof structure */
    int level; /* SOL_SOCKET */
    int type;  /* SCM_RIGHTS */
    int fd;    /* fd to pass */
};
    struct cmsg_fd cmsg = { sizeof(cmsg), SOL_SOCKET, SCM_RIGHTS, -1 };
#endif  /* HAVE_MSGHDR_ACCRIGHTS */

    /* If we don't have a valid fd to send... don't bother translating it */
    if( fd == -1 ) cmsg.type = 0;

    if (debug_level)
        fprintf( stderr, "fd server: *fd* %d -> %d\n", fd, send_to_fd );

    if( fd != -1 )
    {
#ifdef HAVE_MSGHDR_ACCRIGHTS
        msghdr.msg_accrightslen = sizeof(fd);
        msghdr.msg_accrights = (void *)&fd;
#else  /* HAVE_MSGHDR_ACCRIGHTS */
        msghdr.msg_control    = &cmsg;
        msghdr.msg_controllen = sizeof(cmsg);

        cmsg.fd = fd;
#endif  /* HAVE_MSGHDR_ACCRIGHTS */
    }
    else
    {
#ifdef HAVE_MSGHDR_ACCRIGHTS
        msghdr.msg_accrightslen = 0;
        msghdr.msg_accrights = 0;
#else  /* HAVE_MSGHDR_ACCRIGHTS */
        msghdr.msg_control    = NULL;
        msghdr.msg_controllen = 0;
#endif  /* HAVE_MSGHDR_ACCRIGHTS */    
    }

    msghdr.msg_name    = NULL;
    msghdr.msg_namelen = 0;
    msghdr.msg_iov     = &myiovec;
    msghdr.msg_iovlen  = 1;

    myiovec.iov_base = (void *)&fd;
    myiovec.iov_len  = sizeof(fd);

    ret = sendmsg( send_to_fd, &msghdr, 0 );

    if (ret == sizeof(fd)) return 0;

    if (ret >= 0)
    {
        if (ret > 0)
            fprintf( stderr, "fd server: Protocol error: fd server: partial sendmsg %d\n", ret );
    }
    else
    {
        fprintf( stderr, "fd server: Protocol error: %d -> (%d/%s) ", send_to_fd, ret, strerror( errno ) );
        perror( "sendmsg" );
    }
    return -1;
}



static void service_fd_translation_request( int poll_entry )
{
    if( poll_list[poll_entry].revents & (POLLERR|POLLHUP) )
    {
        if( debug_level ) fprintf( stderr, "fd server: Closing fd server[%d] %d\n",
                                              poll_entry, poll_list[poll_entry].fd  );
        close( poll_list[poll_entry].fd );
        poll_list[poll_entry].fd = -1;
        poll_list[poll_entry].events = 0;
        poll_list[poll_entry].revents = 0;
    }
    else 
    {
        struct fds_msg msg;
        int ret;

        if( debug_level ) fprintf( stderr, "fd server: Listening to %d\n", poll_list[poll_entry].fd );
        ret = read( poll_list[poll_entry].fd, &msg, sizeof( msg ) );

        /* PH: FIXME: Check errno etc */
        if( ret != sizeof( msg ) ) fatal_perror( "fd server: msg read" );

        switch( msg.type )
        {
            case FD_TRANSLATION_REQUEST:
            {
                context_fd ctx_fd = msg.u.tfd.ctx_fd;

                if( debug_level) fprintf( stderr, "fd server: got *context fd* %d\n", ctx_fd );

                translate_client_fd( poll_list[poll_entry].fd,
                                     get_context_untranslated_fd( ctx_fd, NULL ) );
                break;
            }
            default:
                fprintf( stderr, "fd server: Unknown msg type %d\n", msg.type );
                break;
        }

        poll_list[poll_entry].revents &= ~POLLIN;
    }
}

static void get_new_fdserver_listener(void)
{
    for(;;)
    {
       int fd;
       int ret = read( poll_list[0].fd, &fd, sizeof(fd) );

       if( ret == sizeof( fd ) )
       {
         int i;

         /* Can we find an empty slot first? */
         for( i = 0; i < max_poll_users; i++ )
         {
            if( (poll_list[i].fd == -1) && (poll_list[i].events == 0) && (poll_list[i].revents == 0) ) break;
         }

         if( i == max_poll_users )
         {
            int j;

            if( debug_level > 2 ) fprintf( stderr, "fd_server: increasing poll_list from %d max_poll_users\n", max_poll_users );

            /* Expand out the array */
            max_poll_users += 20;
            poll_list = realloc( poll_list, sizeof( *poll_list ) * max_poll_users );

            for( j = poll_users; j < max_poll_users; j++ )
            {
              memcpy( poll_list+j, &init_poll_list, sizeof(init_poll_list) );              
            }
         }

         /* Add the fd into its poll list */
         if( debug_level ) fprintf( stderr, "fd server adding %d to fd server poll list\n", fd );
         poll_list[i].fd = fd;
         poll_list[i].events = POLLIN;
         poll_list[i].revents = 0;

         /* Are we reusing an existing slot? */
         if( i+1 > poll_users ) poll_users = i+1;

         poll_list[0].revents &= ~POLLIN;
 
         break;
       }

       if( ret == -1 && errno == EINTR ) continue;

       fprintf( stderr, "fd server: New listener: ret = %d error = %s\n", ret, strerror( errno ) );
       fatal_perror( "fd server: read for start signal" );
    }
}

static void fd_server_signal_init( sigset_t* sigset )
{
    /* block the signals we use (PH: FIXME: Copied from select) */
    sigemptyset( sigset );
    sigaddset( sigset, SIGCHLD );
    sigaddset( sigset, SIGHUP );
    sigaddset( sigset, SIGINT );
    sigaddset( sigset, SIGQUIT );
    sigaddset( sigset, SIGTERM );
    sigaddset( sigset, SIGUSR2 );
    sigprocmask( SIG_BLOCK, sigset, NULL );
}

static int fd_server_thread( void* arg )
{
    sigset_t sigset;

    if( debug_level > 1 ) fprintf( stderr, "fd server: thread created as pid %d\n", wine_gettid() );

    fd_server_signal_init( &sigset );
    get_new_fdserver_listener();

    while( 1 )
    {
        int i, ret;

        if( debug_level > 2 ) fprintf( stderr, "fd server: starting poll\n" );

        sigprocmask( SIG_UNBLOCK, &sigset, NULL );

        ret = poll( poll_list, poll_users, -1 );

        sigprocmask( SIG_BLOCK, &sigset, NULL );

        if( debug_level > 2 ) 
            fprintf( stderr, "fd server: poll returned %d(%s)\n", 
                                ret, (ret != -1 ) ? "" : strerror(errno) );
        if( ret == -1 )
        {
            if( errno == EINTR ) continue;
            fatal_perror( "fd server: poll" );
        }

        /* Was it the pipe to the wineserver? */
        if( poll_list[0].revents )
        {
            /* Do we have something else to listen to. */
            if( poll_list[0].revents & (POLLHUP|POLLERR) )
            {
                /* The wineserver must have exited */
                if( debug_level )
                    fprintf( stderr, "fd server: exiting on revent %d\n", poll_list[0].revents );
                break;
            }

            else if( poll_list[0].revents & POLLIN )
            {
                get_new_fdserver_listener();
            }
 
            ret--;
        }

        /* Must have been a request for translation */
        for( i = 1; ret > 0 && i <= poll_users; i++ )
        {
            if( poll_list[i].revents )
            {
                service_fd_translation_request( i );
                ret--;
            }
        } 

        if( ret > 0 ) fprintf( stderr, "fd server:Couldn't find poll user! ret still = %d\n", ret );
    }

    if( debug_level ) fprintf( stderr, "fd server: thread stopping\n" );

    socket_cleanup();
    close( fd_server_pipe[1] );

    return 0;
}

#if defined( USE_PTHREADS )
static void* fd_server_pthread( void* arg )
{
   return (void*)fd_server_thread( arg );
}
#endif

static void create_fd_server( int fd )
{
    int ret;

    /* Spawn the thread if required */
    if( fd_server_pipe[1] == -1 )
    {
        size_t stack_size = 64*1024;
        int i;

        if( debug_level > 1 ) fprintf( stderr, "wineserver: Creating fd server thread\n" );

        if( pipe( fd_server_pipe ) == -1 ) fatal_perror( "wineserver: fd server pipe" );

        /* Start with 20 items as a typical size */
        max_poll_users = 20;
        poll_list = malloc( sizeof( *poll_list ) * max_poll_users );

        for( i = 0; i < max_poll_users; i++ )
        {
            memcpy( poll_list+i, &init_poll_list, sizeof(init_poll_list) );
        }

        poll_list[poll_users].fd = fd_server_pipe[0];
        poll_list[poll_users].events = POLLIN;
        poll_list[poll_users].revents = 0;
        poll_users++;

#if defined( USE_PTHREADS )
        {
            pthread_t thread;
            pthread_attr_t attr;

            pthread_attr_init( &attr );

            pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_DETACHED );

            pthread_attr_setstacksize( &attr, stack_size );

            pthread_create( &thread, &attr, fd_server_pthread, NULL );

            pthread_attr_destroy( &attr );
        }
#else
       {
           void* stack = malloc( stack_size );
           stack = (char*)stack + stack_size; /* Needs to point to *top* of stack */

           /* PH: FIXME: Hmmm. */
           if (clone( fd_server_thread, stack,
                      CLONE_FILES | CLONE_VM | SIGCHLD,
                      NULL ) < 0)
               fatal_perror( "wineserver: clone" );
       }
#endif
       if( debug_level ) fprintf( stderr, "wineserver: Done creating fd server thread\n" );
    }

    /* Send the new fd */
    ret = write( fd_server_pipe[1], &fd, sizeof( fd ) );
    if( ret != sizeof( fd ) ) fatal_perror( "wineserver: fd server pipe write" );
}


void set_context_variables( int socket_fd  )
{
  fd_server_socket_fd = socket_fd;
}


