/*
 * Server-side pipe management
 *
 * Copyright (C) 1998 Alexandre Julliard
 * Copyright (C) 2001 Mike McCormack
 *
 * TODO:
 *   improve error handling
 */

#include "config.h"

#include <assert.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
#endif
#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif
#ifdef HAVE_SYS_SOCKET_H
# include <sys/socket.h>
#endif
#include <time.h>
#include <unistd.h>

#include "winbase.h"

#include "handle.h"
#include "thread.h"
#include "request.h"

enum pipe_state
{
    ps_none,
    ps_idle_server,
    ps_wait_open,
    ps_wait_connect,
    ps_connected_server,
    ps_connected_client,
    ps_disconnected
};

struct named_pipe;

struct pipe_user
{
    struct object       obj;
    enum pipe_state     state;
    struct pipe_user   *other;
    struct named_pipe  *pipe;
    struct pipe_user   *next;
    struct pipe_user   *prev;
    struct thread      *thread;
    void               *func;
    void               *overlapped;
};

struct named_pipe
{
    struct object       obj;         /* object header */
    unsigned int        pipemode;
    unsigned int        maxinstances;
    unsigned int        outsize;
    unsigned int        insize;
    unsigned int        timeout;
    struct pipe_user   *users;
};

static void named_pipe_dump( struct object *obj, int verbose );
static void named_pipe_destroy( struct object *obj);

static const struct object_ops named_pipe_ops =
{
    sizeof(struct named_pipe),    /* size */
    named_pipe_dump,              /* dump */
    no_add_queue,                 /* add_queue */
    NULL,                         /* remove_queue */
    NULL,                         /* signaled */
    NULL,                         /* satisfied */
    NULL,                         /* get_poll_events */
    NULL,                         /* 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 */
    named_pipe_destroy            /* destroy */
};
const struct object_ops* get_this_process_named_pipe_ops(void) { return &named_pipe_ops; }

static void pipe_user_dump( struct object *obj, int verbose );
static void pipe_user_destroy( struct object *obj);
static int pipe_user_get_fd( struct object *obj );
#if defined( CONTEXTUAL_FD )
static context_fd pipe_user_get_ctx_fd( struct object *obj );
#endif
static int pipe_user_get_info( struct object *obj, struct get_file_info_reply *reply, int *flags );

static const struct object_ops pipe_user_ops =
{
    sizeof(struct pipe_user),     /* size */
    pipe_user_dump,               /* dump */
    default_poll_add_queue,       /* add_queue */
    default_poll_remove_queue,    /* remove_queue */
    default_poll_signaled,        /* signaled */
    no_satisfied,                 /* satisfied */
    NULL,                         /* get_poll_events */
    default_poll_event,           /* poll_event */
    pipe_user_get_fd,             /* get_fd */
#if defined( CONTEXTUAL_FD )
    pipe_user_get_ctx_fd,         /* get_ctx_fd */
#endif
    no_flush,                     /* flush */
    pipe_user_get_info,           /* get_file_info */
    NULL,                         /* queue_async */
    pipe_user_destroy             /* destroy */
};
const struct object_ops* get_this_process_pipe_user_ops(void) { return &pipe_user_ops; }

static void named_pipe_dump( struct object *obj, int verbose )
{
    struct named_pipe *pipe = (struct named_pipe *)obj;
    assert( obj->ops == named_pipe_ops_tag );
    fprintf( stderr, "named pipe %p\n" ,pipe);
}

static void pipe_user_dump( struct object *obj, int verbose )
{
    struct pipe_user *user = (struct pipe_user *)obj;
    assert( obj->ops == pipe_user_ops_tag );
    fprintf( stderr, "named pipe user %p (state %d)\n", user, user->state );
}

static void named_pipe_destroy( struct object *obj)
{
    struct named_pipe *pipe = (struct named_pipe *)obj;
    assert( !pipe->users );
}

static void notify_waiter( struct pipe_user *user, unsigned int status)
{
    if(user->thread && user->func && user->overlapped)
    {
        /* queue a system APC, to notify a waiting thread */
        thread_queue_apc(user->thread,NULL,user->func,
            APC_ASYNC,1,2,user->overlapped,status);
    }
    if (user->thread) release_object(user->thread);
    user->thread = NULL;
    user->func = NULL;
    user->overlapped=NULL;
}

static void pipe_user_destroy( struct object *obj)
{
    struct pipe_user *user = (struct pipe_user *)obj;

    assert( obj->ops == pipe_user_ops_tag );

    if(user->overlapped)
        notify_waiter(user,STATUS_HANDLES_CLOSED);

    if(user->other)
    {
        user->other->other=NULL;
        user->other = NULL;
    }

    /* remove user from pipe's user list */
    if (user->next) user->next->prev = user->prev;
    if (user->prev) user->prev->next = user->next;
    else user->pipe->users = user->next;
    if (user->thread) release_object(user->thread);
    release_object(user->pipe);
}

static int pipe_user_get_fd( struct object *obj )
{
    struct pipe_user *user = (struct pipe_user *)obj;
    int fd;

    assert( obj->ops == pipe_user_ops_tag );

    if (!user->other) {
        /* any remaining data in the pipe won't be retrievable,
         * but I suppose that's the way it is in windows */
        set_error(STATUS_PIPE_DISCONNECTED);
        return -1;
    }
 
    fd = get_object_fd( &user->obj );
    if (fd == -1) set_error(STATUS_PIPE_DISCONNECTED);
    return fd;
}

#if defined( CONTEXTUAL_FD )
static context_fd pipe_user_get_ctx_fd( struct object *obj )
{
    struct pipe_user *user = (struct pipe_user *)obj;
    context_fd fd;

    assert( obj->ops == pipe_user_ops_tag );

    if (!user->other) {
        /* any remaining data in the pipe won't be retrievable,
         * but I suppose that's the way it is in windows */
        set_error(STATUS_PIPE_DISCONNECTED);
        return INVALID_CTX_FD;
    }
 
    fd = user->obj.fd;
    if (fd == INVALID_CTX_FD) set_error(STATUS_PIPE_DISCONNECTED);
    return fd;
}
#endif

static int pipe_user_get_info( struct object *obj, struct get_file_info_reply *reply, int *flags )
{
    if (reply)
    {
        reply->type        = FILE_TYPE_PIPE;
        reply->attr        = 0;
        reply->access_time = 0;
        reply->write_time  = 0;
        reply->size_high   = 0;
        reply->size_low    = 0;
        reply->links       = 0;
        reply->index_high  = 0;
        reply->index_low   = 0;
        reply->serial      = 0;
    }
    *flags = 0;
    return FD_TYPE_DEFAULT;
}

static struct named_pipe *create_named_pipe( const WCHAR *name, size_t len )
{
    struct named_pipe *pipe;

    if ((pipe = create_named_object( named_pipe_ops_tag, name, len )))
    {
        if (get_error() != STATUS_OBJECT_NAME_COLLISION)
        {
            /* initialize it if it didn't already exist */
            pipe->users = 0;
        }
    }
    return pipe;
}

static struct pipe_user *get_pipe_user_obj( struct process *process, handle_t handle,
                                            unsigned int access )
{
    return (struct pipe_user *)get_handle_obj( process, handle, access, pipe_user_ops_tag );
}

static struct pipe_user *create_pipe_user( struct named_pipe *pipe, int fd )
{
    struct pipe_user *user;

    user = alloc_object( pipe_user_ops_tag, fd );
    if(!user)
        return NULL;

    user->pipe = pipe;
    user->state = ps_none;
    user->other = NULL;
    user->thread = NULL;
    user->func = NULL;
    user->overlapped = NULL;

    /* add to list of pipe users */
    if ((user->next = pipe->users)) user->next->prev = user;
    user->prev = NULL;
    pipe->users = user;

    grab_object(pipe);

    return user;
}

static struct pipe_user *find_partner(struct named_pipe *pipe, enum pipe_state state)
{
    struct pipe_user *x;

    for(x = pipe->users; x; x=x->next)
    {
        if(x->state==state)
        break;
    }

    if(!x)
        return NULL;

    return (struct pipe_user *)grab_object( x );
}

DECL_HANDLER(create_named_pipe)
{
    struct named_pipe *pipe;
    struct pipe_user *user;

    reply->handle = 0;
    pipe = create_named_pipe( get_req_data(), get_req_data_size() );
    if(!pipe)
        return;

    if (get_error() != STATUS_OBJECT_NAME_COLLISION)
    {
        pipe->insize = req->insize;
        pipe->outsize = req->outsize;
        pipe->maxinstances = req->maxinstances;
        pipe->timeout = req->timeout;
        pipe->pipemode = req->pipemode;
    }

    user = create_pipe_user (pipe, -1);

    if(user)
    {
        user->state = ps_idle_server;
        reply->handle = alloc_handle( current->process, user, GENERIC_READ|GENERIC_WRITE, 0 );
        release_object( user );
    }

    release_object( pipe );
}

DECL_HANDLER(open_named_pipe)
{
    struct named_pipe *pipe;

    reply->handle = 0;
    pipe = create_named_pipe( get_req_data(), get_req_data_size() );
    if(!pipe)
        return;

    if (get_error() == STATUS_OBJECT_NAME_COLLISION)
    {
        struct pipe_user *partner;

        if ((partner = find_partner(pipe, ps_wait_open)) ||
                (partner = find_partner(pipe, ps_idle_server)))
        {
            int fds[2];

            if(!socketpair(PF_UNIX, SOCK_STREAM, 0, fds))
            {
                struct pipe_user *user;

                if( (user = create_pipe_user (pipe, fds[1])) )
                {
                    set_object_fd( &partner->obj, fds[0] );
                    grab_context_fd( partner->obj.fd, NULL );
                    notify_waiter(partner,STATUS_SUCCESS);
                    partner->state = ps_connected_server;
                    partner->other = user;
                    user->state = ps_connected_client;
                    user->other = partner;
                    reply->handle = alloc_handle( current->process, user, req->access, 0 );
                    release_object(user);
                }
                else
                {
                    close(fds[0]);
                }
            }
            release_object( partner );
        }
        else
        {
            set_error(STATUS_PIPE_NOT_AVAILABLE);
        }
    }
    else
    {
        set_error(STATUS_NO_SUCH_FILE);
    }

    release_object(pipe);
}

DECL_HANDLER(connect_named_pipe)
{
    struct pipe_user *user, *partner;

    user = get_pipe_user_obj(current->process, req->handle, 0);
    if(!user)
        return;

    if( user->state != ps_idle_server )
    {
        set_error(STATUS_PORT_ALREADY_SET);
    }
    else
    {
        user->state = ps_wait_open;
        user->thread = (struct thread *)grab_object(current);
        user->func = req->func;
        user->overlapped = req->overlapped;

        /* notify all waiters that a pipe just became available */
        while( (partner = find_partner(user->pipe,ps_wait_connect)) )
        {
            notify_waiter(partner,STATUS_SUCCESS);
            release_object(partner);
            release_object(partner);
        }
    }

    release_object(user);
}

DECL_HANDLER(wait_named_pipe)
{
    struct named_pipe *pipe;

    pipe = create_named_pipe( get_req_data(), get_req_data_size() );
    if( pipe )
    {
        /* only wait if the pipe already exists */
        if(get_error() == STATUS_OBJECT_NAME_COLLISION)
        {
            struct pipe_user *partner;

            set_error(STATUS_SUCCESS);
            if( (partner = find_partner(pipe,ps_wait_open)) )
            {
                /* this should use notify_waiter,
                   but no pipe_user object exists now... */
                thread_queue_apc(current,NULL,req->func,
                    APC_ASYNC,1,2,req->overlapped,STATUS_SUCCESS);
                release_object(partner);
            }
            else
            {
                struct pipe_user *user;

                if( (user = create_pipe_user (pipe, -1)) )
                {
                    user->state = ps_wait_connect;
                    user->thread = (struct thread *)grab_object(current);
                    user->func = req->func;
                    user->overlapped = req->overlapped;
                    /* don't release it */
                }
            }
        }
        else
        {
            set_error(STATUS_PIPE_NOT_AVAILABLE);
        }
        release_object(pipe);
    }
}

DECL_HANDLER(disconnect_named_pipe)
{
    struct pipe_user *user;

    user = get_pipe_user_obj(current->process, req->handle, 0);
    if(!user)
        return;
    if( user->state == ps_connected_server )
    {
        if (user->other) {
            user->other->other = NULL;
            user->other = NULL;
        }

        release_context_fd( user->obj.fd, NULL, NULL );
        set_object_fd( &user->obj, -1 );
        user->state = ps_idle_server;
    }
    release_object(user);
}

DECL_HANDLER(get_named_pipe_info)
{
    struct pipe_user *user;

    user = get_pipe_user_obj(current->process, req->handle, 0);
    if(!user)
        return;

    reply->flags        = user->pipe->pipemode;
    reply->maxinstances = user->pipe->maxinstances;
    reply->insize       = user->pipe->insize;
    reply->outsize      = user->pipe->outsize;

    release_object(user);
}

