/*
 * Copyright (c) 2003-2006  Motoyuki Kasahara
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the project nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <sys/types.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#ifdef HAVE_NET_IF_H
#include <net/if.h>
#endif

#include "build-pre.h"
#include "eb.h"
#include "build-post.h"
#include "dummyin6.h"

#if !defined(HAVE_GETADDRINFO) || !defined(HAVE_GETNAMEINFO)
#include "getaddrinfo.h"
#endif

#include "ebnet.h"

#ifndef IF_NAMESIZE
#ifdef IFNAMSIZ
#define IF_NAMESIZE             IFNAMSIZ
#else
#define IF_NAMESIZE             16
#endif
#endif

#ifndef AI_NUMERICHOST
#define AI_NUMERICHOST		0
#endif
#ifndef NI_WITHSCOPEID
#define NI_WITHSCOPEID		0
#endif

#ifdef WINSOCK
#define close closesocket
#endif

/*
 * Maximum length (including NUL) of a TCP/UDP port number string.
 */
#define IN_PORTSTRLEN		6

/*
 * Socket entry pool.
 */
typedef struct EBNet_Socket_Entry_Struct EBNet_Socket_Entry;
struct EBNet_Socket_Entry_Struct {
    /* IPv6 or IPv4 Address of peer */
    char address[INET6_ADDRSTRLEN + IF_NAMESIZE];

    /* destination port */
    in_port_t port;

    /* socket file */
    int file;

    /* reference count of the entry */
    int reference_count;

    /* object ID of the entry */
    int reference_id;

    /* lost synchronization flag */
    int lost_sync;

    /* next and previous entries. */
    EBNet_Socket_Entry *next;
    EBNet_Socket_Entry *back;

    /* book name */
    char book_name[EBNET_MAX_BOOK_NAME_LENGTH + 1];

    /* file path */
    char file_path[EB_MAX_RELATIVE_PATH_LENGTH + 1];

    /* current file pointer */
    off_t offset;

    /* file size */
    size_t file_size;
};

static EBNet_Socket_Entry *ebnet_socket_entries;
static EBNet_Socket_Entry *ebnet_socket_entry_cache;

/*
 * Host name information cache.
 */
typedef struct {
    /* hostname or IP address */
    char *host;

    /* IPv6 address of `host' */
    char ipv6_address[INET6_ADDRSTRLEN + IF_NAMESIZE];

    /* IPv4 address of `host' */
    char ipv4_address[INET_ADDRSTRLEN];
} EBNet_Host_Info;

static EBNet_Host_Info ebnet_host_info_cache;


/*
 * Hello and Bye hooks.
 *
 * The multiplex module invokes the hello hook each time it establish
 * a new connection with a server, and invokes the bye hook each time
 * it closes a last socket connected with a server.
 */
static int (*hello_hook)(int file);
static int (*bye_hook)(int file);

/*
 * Unexported functions.
 */
static void ebnet_get_addresses(const char *host, char *ipv6_address,
    char *ipv4_address);
static EBNet_Socket_Entry *ebnet_find_multiplex_entry(const char *ipv6_address,
    const char *ipv4_address, int port, int family);
static int ebnet_create_new_connection(const char *address, int port);
static void ebnet_add_socket_entry(EBNet_Socket_Entry *new_entry);
static void ebnet_delete_socket_entry(EBNet_Socket_Entry *target_entry);
static EBNet_Socket_Entry *ebnet_find_socket_entry(int file);


/*
 * Initialize the multiplex module.
 */
void
ebnet_initialize_multiplex(void)
{
    ebnet_socket_entries = NULL;
    ebnet_socket_entry_cache = NULL;

    ebnet_host_info_cache.host = NULL;
    ebnet_host_info_cache.ipv6_address[0] = '\0';
    ebnet_host_info_cache.ipv4_address[0] = '\0';

    hello_hook = NULL;
    bye_hook = NULL;
}


/*
 * Finalize the multiplex module.
 */
void
ebnet_finalize(void)
{
    while (ebnet_socket_entries != NULL) {
	close(ebnet_socket_entries->file);
	ebnet_delete_socket_entry(ebnet_socket_entries);
    }

    ebnet_socket_entries = NULL;
    ebnet_socket_entry_cache = NULL;

    if (ebnet_host_info_cache.host != NULL)
	free(ebnet_host_info_cache.host);
}


/*
 * Set hello hook.
 */
void
ebnet_set_hello_hook(int (*hook)(int file))
{
    hello_hook = hook;
}


/*
 * Set bye hook.
 */
void
ebnet_set_bye_hook(int (*hook)(int file))
{
    bye_hook = hook;
}


/*
 * Create a socket connected with a server.
 *
 * `host' is a host name or an IP address of the server.  `port' is
 * destination port number of the TCP connection.  `family' is protocol
 * family: PF_INET, PF_INET6 or PF_UNSPEC.
 *
 * Upon success, file descriptor of the socket is returned.
 * Otherwise -1 is returned.
 *
 * If there has been a socket entry in `ebnet_socket_entries' which is
 * connected with the server, this function simply duplicates the socket.
 */
int
ebnet_connect_socket(const char *host, int port, int family)
{
    char ipv6_address[INET6_ADDRSTRLEN + IF_NAMESIZE];
    char ipv4_address[INET_ADDRSTRLEN];
    EBNet_Socket_Entry *multiplex_entry = NULL;
    EBNet_Socket_Entry *new_entry = NULL;
    int new_file = -1;

    /*
     * Get IP addresses of `host'.
     */
    *ipv6_address = '\0';
    *ipv4_address = '\0';
    ebnet_get_addresses(host, ipv6_address, ipv4_address);

    switch (family) {
    case PF_UNSPEC:
	if (*ipv6_address == '\0' && *ipv4_address == '\0')
	    goto failed;
	break;

    case PF_INET6:
	if (*ipv6_address == '\0')
	    goto failed;
	break;

    case PF_INET:
	if (*ipv4_address == '\0')
	    goto failed;
	break;
    }

    /*
     * Search `ebnet_socket_entries' for a connection entry with
     * the server.
     */
    multiplex_entry = ebnet_find_multiplex_entry(ipv6_address, ipv4_address,
	port, family);

    /*
     * Create a socket entry.
     */
    new_entry = (EBNet_Socket_Entry *)malloc(sizeof(EBNet_Socket_Entry));
    if (new_entry == NULL)
	goto failed;

    new_entry->address[0]      = '\0';
    new_entry->port            = port;
    new_entry->file            = -1;
    new_entry->reference_count = 1;
    new_entry->reference_id    = -1;
    new_entry->lost_sync       = 0;
    new_entry->next            = NULL;
    new_entry->back            = NULL;
    new_entry->book_name[0]    = '\0';
    new_entry->file_path[0]    = '\0';
    new_entry->offset          = 0;
    new_entry->file_size       = 0;

    if (multiplex_entry != NULL) {
	/*
	 * There is an IPv6 or IPv4 socket with the server.
	 * Duplicate the socket entry.
	 */
#ifndef WINSOCK
	new_file = dup(multiplex_entry->file);
#else /* WINSOCK */
	{
	    WSAPROTOCOL_INFO info;

	    if (WSADuplicateSocket(multiplex_entry->file,
		GetCurrentProcessId(), &info) != 0)
		goto failed;
	    new_file = WSASocket(FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO,
		FROM_PROTOCOL_INFO, &info, 0, 0);
	}
#endif /* WINSOCK */
	if (new_file < 0)
	    goto failed;

	strcpy(new_entry->address, multiplex_entry->address);
	new_entry->file = new_file;
	new_entry->reference_count = multiplex_entry->reference_count;
	new_entry->reference_id = multiplex_entry->reference_id;

    } else {
	/*
	 * There is no socket connected with the server.
	 * Establish a connection with the server.
	 */
	do {
	    if ((family == PF_INET6 || family == PF_UNSPEC)
		&& *ipv6_address != '\0') {
		new_file = ebnet_create_new_connection(ipv6_address, port);
		if (0 <= new_file) {
		    strcpy(new_entry->address, ipv6_address);
		    new_entry->file = new_file;
		    new_entry->reference_id = new_file;
		    break;
		}
	    }
	    if ((family == PF_INET || family == PF_UNSPEC)
		&& *ipv4_address != '\0') {
		new_file = ebnet_create_new_connection(ipv4_address, port);
		if (0 <= new_file) {
		    strcpy(new_entry->address, ipv4_address);
		    new_entry->file = new_file;
		    new_entry->reference_id = new_file;
		    break;
		}
	    }
	    goto failed;

	} while (0);

    }

    /*
     * Add the entry to `ebnet_socket_entries'.
     */
    ebnet_add_socket_entry(new_entry);

    /*
     * Say hello.
     */
    if (multiplex_entry == NULL && hello_hook != NULL) {
	if (hello_hook(new_file) < 0)
	    goto failed;
    }

    return new_file;

    /*
     * An error occurs...
     */
  failed:
    if (new_entry != NULL) {
	if (ebnet_find_socket_entry(new_file) != NULL)
	    ebnet_delete_socket_entry(new_entry);
	else
	    free(new_entry);
    }
    if (new_file >= 0)
	close(new_file);
    return -1;
}


/*
 * Get IPv6 and IPv4 addresses of `host'.
 *
 * `host' may be either an host name or an IP address.
 * If `host' has an IPv6 address, the address is written on `ipv6_address'.
 * Otherwise an empty string is written.
 * If `host' has an IPv4 address, the address is written on `ipv4_address'.
 * Otherwise an empty string is written.
 */
static void
ebnet_get_addresses(const char *host, char *ipv6_address, char *ipv4_address)
{
    struct addrinfo hints;
    struct addrinfo *info_list = NULL;
    struct addrinfo *info;
    char dummy_service[IN_PORTSTRLEN];
    int gai_error;

    *ipv6_address = '\0';
    *ipv4_address = '\0';

    /*
     * Look up `ebnet_host_info_cache'.
     */
    if (ebnet_host_info_cache.host != NULL
	&& strcmp(ebnet_host_info_cache.host, host) == 0) {
	strcpy(ipv6_address, ebnet_host_info_cache.ipv6_address);
	strcpy(ipv4_address, ebnet_host_info_cache.ipv4_address);
	return;
    }

    /*
     * Get IP addresses using getaddrinfo().
     */
    hints.ai_flags = 0;
    hints.ai_family = PF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = 0;
    hints.ai_addrlen = 0;
    hints.ai_canonname = NULL;
    hints.ai_addr = NULL;
    hints.ai_next = NULL;

    gai_error = getaddrinfo(host, EBNET_DEFAULT_PORT, &hints, &info_list);
    if (gai_error != 0)
	return;

    for (info = info_list; info != NULL; info = info->ai_next) {
	if (info->ai_family != PF_INET6)
	    continue;
	gai_error = getnameinfo(info->ai_addr, info->ai_addrlen,
	    ipv6_address, INET6_ADDRSTRLEN + IF_NAMESIZE,
	    dummy_service, sizeof(dummy_service),
	    NI_NUMERICHOST | NI_NUMERICSERV | NI_WITHSCOPEID);
#if NI_WITHSCOPEID != 0
	if (gai_error != 0) {
	    gai_error = getnameinfo(info->ai_addr, info->ai_addrlen,
		ipv6_address, INET6_ADDRSTRLEN + IF_NAMESIZE,
		dummy_service, sizeof(dummy_service),
		NI_NUMERICHOST | NI_NUMERICSERV);
	}
#endif
	if (gai_error == 0)
	    break;
	*ipv6_address = '\0';
    }

    for (info = info_list; info != NULL; info = info->ai_next) {
	if (info->ai_family != PF_INET)
	    continue;
	gai_error = getnameinfo(info->ai_addr, info->ai_addrlen,
	    ipv4_address, INET_ADDRSTRLEN,
	    dummy_service, sizeof(dummy_service),
	    NI_NUMERICHOST | NI_NUMERICSERV);
	if (gai_error == 0)
	    break;
	*ipv4_address = '\0';
    }

    freeaddrinfo(info_list);

    /*
     * Update `ebnet_host_info_cache'.
     */
    if (ebnet_host_info_cache.host != NULL) {
	free(ebnet_host_info_cache.host);
	ebnet_host_info_cache.host = NULL;
    }
    ebnet_host_info_cache.host = malloc(strlen(host) + 1);
    if (ebnet_host_info_cache.host != NULL) {
	strcpy(ebnet_host_info_cache.host, host);
	strcpy(ebnet_host_info_cache.ipv6_address, ipv6_address);
	strcpy(ebnet_host_info_cache.ipv4_address, ipv4_address);
    }

    return;
}


/*
 * Find `ebnet_socket_entries' for a socket entry which matches with
 * port, family and either `ipv6_address' or `ipv4_address'.
 *
 * If found, the function returns the entry.  Otherwise it returns NULL.
 */
static EBNet_Socket_Entry *
ebnet_find_multiplex_entry(const char *ipv6_address, const char *ipv4_address,
    int port, int family)
{
    EBNet_Socket_Entry *entry;

    /*
     * Inspect `ebnet_socket_entry_cache'.
     */
    if (ebnet_socket_entry_cache != NULL
	&& (family == PF_INET6 || family == PF_UNSPEC)
	&& strcasecmp(ebnet_socket_entry_cache->address, ipv6_address) == 0
	&& ebnet_socket_entry_cache->port == port
	&& !ebnet_socket_entry_cache->lost_sync) {
	return ebnet_socket_entry_cache;
    }
    if (ebnet_socket_entry_cache != NULL
	&& (family == PF_INET || family == PF_UNSPEC)
	&& strcasecmp(ebnet_socket_entry_cache->address, ipv4_address) == 0
	&& ebnet_socket_entry_cache->port == port
	&& !ebnet_socket_entry_cache->lost_sync) {
	return ebnet_socket_entry_cache;
    }

    /*
     * Then inspect entires in `ebnet_socket_entries'.
     */
    for (entry = ebnet_socket_entries; entry != NULL; entry = entry->next) {
	if ((family == PF_INET6 || family == PF_UNSPEC)
	    && strcasecmp(entry->address, ipv6_address) == 0
	    && entry->port == port
	    && !entry->lost_sync) {
	    ebnet_socket_entry_cache = entry;
	    return entry;
	}
	if ((family == PF_INET || family == PF_UNSPEC)
	    && strcasecmp(entry->address, ipv4_address) == 0
	    && entry->port == port
	    && !entry->lost_sync) {
	    ebnet_socket_entry_cache = entry;
	    return entry;
	}
    }

    return NULL;
}


/*
 * Establish a TCP connection with an EBNET server.
 *
 * `host' is a host name or an IP address of the server.  `port' is
 * destination port number of the TCP connection.
 *
 * Upon success, file descriptor of the socket is returned.
 * Otherwise -1 is returned.
 */
static int
ebnet_create_new_connection(const char *address, int port)
{
    struct addrinfo hints;
    struct addrinfo *info_list = NULL;
    int new_file = -1;
    int gai_error;
    char port_string[IN_PORTSTRLEN];
#ifdef O_NONBLOCK
    int file_flags;
#endif

    if (port < 0 || 65535 < port)
	goto failed;
    sprintf(port_string, "%d", port);

    hints.ai_flags = AI_NUMERICHOST;
    hints.ai_family = PF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = 0;
    hints.ai_addrlen = 0;
    hints.ai_canonname = NULL;
    hints.ai_addr = NULL;
    hints.ai_next = NULL;

    gai_error = getaddrinfo(address, port_string, &hints, &info_list);
    if (gai_error != 0)
	goto failed;

    new_file = socket(info_list->ai_addr->sa_family, SOCK_STREAM, 0);
    if (new_file < 0)
	goto failed;
    if (connect(new_file, info_list->ai_addr, info_list->ai_addrlen) < 0)
	goto failed;

#ifdef O_NONBLOCK
    file_flags = fcntl(new_file, F_GETFL, 0);
    if (file_flags >= 0)
	fcntl(new_file, F_SETFL, file_flags | O_NONBLOCK);
#endif

    freeaddrinfo(info_list);

    return new_file;

    /*
     * An error occurs...
     */
  failed:
    if (info_list != NULL)
	freeaddrinfo(info_list);
    if (new_file >= 0)
	close(new_file);
    return -1;
}


/*
 * Add `new_entry' to `ebnet_socket_entries'.
 */
static void
ebnet_add_socket_entry(EBNet_Socket_Entry *new_entry)
{
    EBNet_Socket_Entry *entry;
    int reference_count;

    /*
     * Increment reference counts.
     */
    reference_count = 1;
    for (entry = ebnet_socket_entries; entry != NULL; entry = entry->next) {
	if (entry->reference_id == new_entry->reference_id) {
	    entry->reference_count++;
	    reference_count = entry->reference_count;
	}
    }

    new_entry->reference_count = reference_count;

    /*
     * Add `new_entry' to `ebnet_socket_entries'.
     */
    if (ebnet_socket_entries != NULL)
	ebnet_socket_entries->back = new_entry;
    new_entry->back = NULL;
    new_entry->next = ebnet_socket_entries;
    ebnet_socket_entries = new_entry;

    ebnet_socket_entry_cache = new_entry;
}


/*
 * Delete `target_entry' from `ebnet_socket_entries'.
 */
static void
ebnet_delete_socket_entry(EBNet_Socket_Entry *target_entry)
{
    EBNet_Socket_Entry *entry;
    int new_reference_id;

    if (ebnet_socket_entry_cache == target_entry)
	ebnet_socket_entry_cache = NULL;

    /*
     * Delete `target_entry' from `ebnet_socket_entries'.
     */
    if (target_entry->next != NULL)
	target_entry->next->back = target_entry->back;
    if (target_entry->back != NULL)
	target_entry->back->next = target_entry->next;
    if (target_entry == ebnet_socket_entries)
	ebnet_socket_entries = ebnet_socket_entries->next;

    /*
     * Decrement reference counts and update reference ID.
     */
    entry = ebnet_socket_entries;
    while (entry != NULL) {
	if (entry->reference_id == target_entry->reference_id) {
	    new_reference_id = entry->file;
	    break;
	}
	entry = entry->next;
    }
    while (entry != NULL) {
	if (entry->reference_id == target_entry->reference_id) {
	    entry->reference_id = new_reference_id;
	    entry->reference_count--;
	}
	entry = entry->next;
    }

    /*
     * Dispose `target_entry'.
     */
    free(target_entry);
}


/*
 * Disconnect with a server.
 */
void
ebnet_disconnect_socket(int file)
{
    EBNet_Socket_Entry *entry;

    /*
     * Search `ebnet_socket_entries' for a connection entry with `file'.
     */
    entry = ebnet_find_socket_entry(file);
    if (entry == NULL)
	return;

    /*
     * Say good bye and close the connection.
     */
    if (entry->reference_count == 1 && !entry->lost_sync && bye_hook != NULL)
	bye_hook(entry->file);

    close(entry->file);
    ebnet_delete_socket_entry(entry);
}


/*
 * Reconnect `file'.
 * It returns `file' upon success, -1 otherwise.
 */
int
ebnet_reconnect_socket(int file)
{
    EBNet_Socket_Entry *old_entry;
    EBNet_Socket_Entry *new_entry = NULL;
    int new_file;

    /*
     * Search `ebnet_socket_entries' for a connection entry with `file'.
     */
    old_entry = ebnet_find_socket_entry(file);
    if (old_entry == NULL)
	goto failed;

    /*
     * Say good bye.
     */
    if (old_entry->reference_count == 1 && !old_entry->lost_sync
	&& bye_hook != NULL)
	bye_hook(old_entry->file);

    /*
     * Establish a new connection with the server.
     */
    ebnet_set_lost_sync(file);
    new_file = ebnet_connect_socket(old_entry->address, old_entry->port,
	PF_UNSPEC);
    if (new_file < 0)
	goto failed;

    new_entry = ebnet_find_socket_entry(new_file);
    if (new_entry == NULL)
	goto failed;

    /*
     * Replace `old_entry' by `new_entry'.
     */
    strcpy(new_entry->book_name, old_entry->book_name);
    strcpy(new_entry->file_path, old_entry->file_path);
    new_entry->offset = old_entry->offset;
    new_entry->file_size = old_entry->file_size;

    ebnet_delete_socket_entry(old_entry);

#ifndef WINSOCK
    if (dup2(new_entry->file, file) < 0)
	goto failed;
    close(new_entry->file);

    if (new_entry->reference_id == new_entry->file)
	new_entry->reference_id = file;
    new_entry->file = file;
#else
    close(file);
#endif

    return new_entry->file;

    /*
     * An error occurs...
     */
  failed:
    if (new_entry != NULL && new_entry->file != file)
	ebnet_disconnect_socket(new_entry->file);
    return -1;
}


/*
 * Mark files as lost-synchronization.
 *
 * Socket entries in `ebnet_socket_entries' which has the same reference
 * ID as `file' are marked as lost-synchronization.
 * The function doesn't close the files, but ebnet_connect_socket()
 * doesn't duplicate lost-synchronized files.
 */
int
ebnet_set_lost_sync(int file)
{
    EBNet_Socket_Entry *entry;
    int reference_id;

    entry = ebnet_find_socket_entry(file);
    if (entry == NULL)
	return -1;
    reference_id = entry->reference_id;

    for (entry = ebnet_socket_entries; entry != NULL; entry = entry->next) {
	if (entry->reference_id == reference_id)
	    entry->lost_sync = 1;
    }

    return 0;
}


/*
 * Search `ebnet_socket_entries' for a connection entry with `file'.
 */
static EBNet_Socket_Entry *
ebnet_find_socket_entry(int file)
{
    EBNet_Socket_Entry *entry;

    if (ebnet_socket_entry_cache != NULL
	&& ebnet_socket_entry_cache->file == file)
	return ebnet_socket_entry_cache;

    for (entry = ebnet_socket_entries; entry != NULL; entry = entry->next) {
	if (entry->file == file) {
	    ebnet_socket_entry_cache = entry;
	    return entry;
	}
    }

    return NULL;
}


/*
 * Set book name associated with `file'.
 */
int
ebnet_set_book_name(int file, const char *book_name)
{
    EBNet_Socket_Entry *entry;

    entry = ebnet_find_socket_entry(file);
    if (entry == NULL)
	return -1;

    strncpy(entry->book_name, book_name, EBNET_MAX_BOOK_NAME_LENGTH + 1);
    *(entry->book_name + EBNET_MAX_BOOK_NAME_LENGTH + 1) = '\0';

    return 0;
}


/*
 * Get book name associated with `file'.
 */
const char *
ebnet_get_book_name(int file)
{
    EBNet_Socket_Entry *entry;

    entry = ebnet_find_socket_entry(file);
    if (entry == NULL)
	return NULL;

    return entry->book_name;
}


/*
 * Set book name associated with `file'.
 */
int
ebnet_set_file_path(int file, const char *file_path)
{
    EBNet_Socket_Entry *entry;

    entry = ebnet_find_socket_entry(file);
    if (entry == NULL)
	return -1;

    strncpy(entry->file_path, file_path, EB_MAX_RELATIVE_PATH_LENGTH + 1);
    *(entry->file_path + EB_MAX_RELATIVE_PATH_LENGTH + 1) = '\0';

    return 0;
}


/*
 * Get file path associated with `file'.
 */
const char *
ebnet_get_file_path(int file)
{
    EBNet_Socket_Entry *entry;

    entry = ebnet_find_socket_entry(file);
    if (entry == NULL)
	return NULL;

    return entry->file_path;
}


/*
 * Set file offset.
 */
int
ebnet_set_offset(int file, off_t offset)
{
    EBNet_Socket_Entry *entry;

    entry = ebnet_find_socket_entry(file);
    if (entry == NULL)
	return -1;

    entry->offset = offset;
    return 0;
}


/*
 * Get file offset.
 */
off_t
ebnet_get_offset(int file)
{
    EBNet_Socket_Entry *entry;

    entry = ebnet_find_socket_entry(file);
    if (entry == NULL)
	return -1;

    return entry->offset;
}


/*
 * Set file size.
 */
int
ebnet_set_file_size(int file, size_t file_size)
{
    EBNet_Socket_Entry *entry;

    entry = ebnet_find_socket_entry(file);
    if (entry == NULL)
	return -1;

    entry->file_size = file_size;
    return 0;
}


/*
 * Get file size.
 */
ssize_t
ebnet_get_file_size(int file)
{
    EBNet_Socket_Entry *entry;

    entry = ebnet_find_socket_entry(file);
    if (entry == NULL)
	return -1;

    return entry->file_size;
}


