/*
 * Server-side /proc support for Solaris
 *
 * Copyright (C) 2007 Alexandre Julliard
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include "config.h"

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <signal.h>
#include <stdarg.h>
#include <sys/types.h>
#include <unistd.h>

#include "ntstatus.h"
#define WIN32_NO_STATUS
#include "winternl.h"

#include "file.h"
#include "process.h"
#include "thread.h"

#ifdef USE_PROCFS

/* procfs doesn't support large files */
# undef _FILE_OFFSET_BITS
# define _FILE_OFFSET_BITS 32
#include <procfs.h>

static int open_proc_as( struct process *process, int flags )
{
    char buffer[32];
    int fd;

    if (process->unix_pid == -1)
    {
        set_error( STATUS_ACCESS_DENIED );
        return -1;
    }

    sprintf( buffer, "/proc/%u/as", process->unix_pid );
    if ((fd = open( buffer, flags )) == -1)
    {
        if (errno == ENOENT)  /* probably got killed */
        {
            process->unix_pid = -1;
            set_error( STATUS_ACCESS_DENIED );
        }
        else file_set_error();
    }
    return fd;
}

static int open_proc_lwpctl( struct thread *thread )
{
    char buffer[48];
    int fd;

    if (thread->unix_pid == -1) return -1;

    sprintf( buffer, "/proc/%u/lwp/%u/lwpctl", thread->unix_pid, thread->unix_tid );
    if ((fd = open( buffer, O_WRONLY )) == -1)
    {
        if (errno == ENOENT)  /* probably got killed */
            thread->unix_pid = thread->unix_tid = -1;
        else
            file_set_error();
    }
    return fd;
}


/* handle a SIGCHLD signal */
void sigchld_callback(void)
{
    assert( 0 );  /* should only be called when using ptrace */
}

/* initialize the process tracing mechanism */
void init_tracing_mechanism(void)
{
    /* no initialization needed */
}

/* initialize the per-process tracing mechanism */
void init_process_tracing( struct process *process )
{
    /* setup is done on-demand */
}

/* terminate the per-process tracing mechanism */
void finish_process_tracing( struct process *process )
{
}

/* send a Unix signal to a specific thread */
int send_thread_signal( struct thread *thread, int sig )
{
    int fd = open_proc_lwpctl( thread );
    long kill[2];
    ssize_t ret;

    if (fd == -1) return 0;

    kill[0] = PCKILL;
    kill[1] = sig;
    ret = write( fd, kill, sizeof(kill) );
    close( fd );
    return (ret == sizeof(kill));
}

/* read data from a process memory space */
int read_process_memory( struct process *process, client_ptr_t ptr, size_t size, char *dest )
{
    ssize_t ret;
    int fd;

    if ((off_t)ptr != ptr)
    {
        set_error( STATUS_ACCESS_DENIED );
        return 0;
    }

    if ((fd = open_proc_as( process, O_RDONLY )) == -1) return 0;

    ret = pread( fd, dest, size, (off_t)ptr );
    close( fd );
    if (ret == size) return 1;

    if (ret == -1) file_set_error();
    else set_error( STATUS_ACCESS_VIOLATION );
    return 0;
}

/* write data to a process memory space */
int write_process_memory( struct process *process, client_ptr_t ptr, size_t size, const char *src )
{
    ssize_t ret;
    int fd;

    if ((off_t)ptr != ptr)
    {
        set_error( STATUS_ACCESS_DENIED );
        return 0;
    }

    if ((fd = open_proc_as( process, O_WRONLY )) == -1) return 0;

    ret = pwrite( fd, src, size, (off_t)ptr );
    close( fd );
    if (ret == size) return 1;

    if (ret == -1) file_set_error();
    else set_error( STATUS_ACCESS_VIOLATION );
    return 0;
}

/* retrieve an LDT selector entry */
void get_selector_entry( struct thread *thread, int entry, unsigned int *base,
                         unsigned int *limit, unsigned char *flags )
{
    ssize_t ret;
    off_t pos = thread->process->ldt_copy;
    int fd;

    if (!pos)
    {
        set_error( STATUS_ACCESS_DENIED );
        return;
    }
    if ((fd = open_proc_as( thread->process, O_RDONLY )) == -1) return;

    ret = pread( fd, base, sizeof(*base), pos + entry*sizeof(int) );
    if (ret != sizeof(*base)) goto error;
    ret = pread( fd, limit, sizeof(*limit), pos + (8192 + entry)*sizeof(int) );
    if (ret != sizeof(*limit)) goto error;
    ret = pread( fd, flags, sizeof(*flags), pos + 2*8192*sizeof(int) + entry );
    if (ret != sizeof(*flags)) goto error;
    close( fd );
    return;

error:
    if (ret == -1) file_set_error();
    else set_error( STATUS_ACCESS_VIOLATION );
    close( fd );
}

/* retrieve the thread registers */
void get_thread_context( struct thread *thread, context_t *context, unsigned int flags )
{
    /* FIXME: get debug registers */
}

/* set the thread registers */
void set_thread_context( struct thread *thread, const context_t *context, unsigned int flags )
{
    /* FIXME: set debug registers */
}

#endif /* USE_PROCFS */