/***************************/
/*   j_net.c by IoDream    */
/*  j_net main source code */
/* Not released yet        */
/*  UNDER THE Lesser GPL   */
/***************************/

/* IoDream (IoDream@ifrance.com) */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
/* #include <sys/types.h> is already in j_net.h */
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include "j_net.h"

#ifndef socklen_t
#define socklen_t int
#endif

/***********************************/
/*** Socket creation/destruction ***/
/***********************************/

/* Opens a new socket bind to port number port_number.
   If port_number == 0, the post number is automatically given by the system.
   The choosen port number is returned in port_number. */
int j_net_create_new_socket(short *port_number)
{
    int my_sock;
    int optval;
    struct sockaddr_in sockname;

    if ((my_sock = socket(PF_INET, SOCK_STREAM, 0)) < 0)
    {
        perror("socket()");
        j_log_error(__FILE__, __LINE__, 0, "socket() opening error. Exit");
        exit(-1);
    }

    setsockopt(my_sock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(int));

    memset(&sockname, 0, sizeof(struct sockaddr_in));
    sockname.sin_family = AF_INET;
    sockname.sin_port = htons(*port_number);
    sockname.sin_addr.s_addr = htonl(INADDR_ANY);

    if (bind (my_sock, (struct sockaddr *) &sockname, sizeof(struct sockaddr_in)) < 0)
    {
        perror("bind()");
        j_log_error(__FILE__, __LINE__, 0, "bind() error. Exit");
        exit(-1);
    }

    *port_number = j_net_get_sock_port_number(my_sock);

    return(my_sock);
}

/* Close the socket sock for the current process.
   Return 0 if successful, or -1 in case of failure (check errno). */
int j_net_close_socket(int sock)
{
    return(close(sock));
}

/* Close the socket sock. The parameter how if set to close either in read, in write, or both
  (repectively SHUT_RD, SHUT_RD, SHUT_RDWR) for ALL the processus belonging to the same group.
   Return 0 if successful, or -1 in case of failure (check errno). */
int j_net_shutdown_socket(int sock, int how)
{
    return(shutdown(sock, how));
}

/*************************************/
/*** Server run & client connexion ***/
/*************************************/

/* Unused for now */
/* Exit signal handler */
void j_net_handler_server_termination(int s)
{
    printf("Exit signal %u intercepted\n", s);
    if (kill(0, s) < 0)
    {
        perror("kill()");
        j_log_error(__FILE__, __LINE__, 0, "kill() error. Exit");
        exit(-1);
    }
    j_net_sweeper();
    printf("Exiting.\n");
    exit(0);
}

/* SIG_CHLD handler for servers (used in j_net_run_server) */
void j_net_handler_sigchld(int s)
{
    /*printf("SIGCHLD intercepted\n");*/
    j_net_sweeper();
    signal(SIGCHLD, j_net_handler_sigchld);
    return;
}

/* Start a server using socket sock, with max_client maximum connections
   and p_server_function the server function. */
void j_net_run_server(int sock, int max_client, void (*p_server_function)(void *arg))
{
    typedef void (*sighandler_t)(int);
    int client_sock;
    sighandler_t old_sigchld;

    if (p_server_function == NULL)
    {
        j_log_error(__FILE__, __LINE__, 0, "p_server_function is NULL. Return");
        return;
    }

    if (listen(sock, max_client) < 0)
    {
        perror("listen()");
        j_log_error(__FILE__, __LINE__, 0, "listen() error. Exit");
        exit(-1);
    }

    old_sigchld = signal(SIGCHLD, j_net_handler_sigchld);
    if (old_sigchld == SIG_ERR)
    {
        perror("signal()");
        j_log_error(__FILE__, __LINE__, 0, "signal() error.");
    }

    for(;;)
    {
        do
            client_sock = accept(sock, NULL, 0);
        while ((client_sock < 0) && (errno == EINTR));
        if (client_sock < 0)
        {
            perror("accept()");
            j_log_error(__FILE__, __LINE__, 0, "accept() error. Exit");
            exit(-1);
        }
        switch(fork())
        {
            case -1:
                perror("fork()");
                j_log_error(__FILE__, __LINE__, 0, "fork() error. Exit");
                exit(-1);
            case 0:
                j_net_close_socket(sock);
                (void) signal(SIGCHLD, old_sigchld);
                (*p_server_function)((void*) &client_sock);
                (void) j_net_shutdown_socket(client_sock, SHUT_RDWR);
                if ((j_net_close_socket(client_sock) < 0) && (errno != EBADF))
                {
                    perror("j_net_close_socket()");
                    j_log_error(__FILE__, __LINE__, 0, "j_net_close_socket() error.");
                }
                exit(0);
            default:
                j_net_close_socket(client_sock);
        }
    }

    return;
}

/* Connect to the server named remote_name at port port_number.
   Return the socket number if successfully connected, -1 otherwise. */
int j_net_connect(char *remote_name, short port_number)
{
    int my_sock;
    int optval;
    struct hostent *hostname;
    struct sockaddr_in sockname;

    if ((hostname = gethostbyname(remote_name)) == NULL)
    {
        perror("gethostbyname()");
	    j_log_error(__FILE__, __LINE__, 0, "Unknown remote name");
	    return(-1);
    }

    if ((my_sock = socket(PF_INET, SOCK_STREAM, 0)) < 0)
    {
        perror("socket()");
        j_log_error(__FILE__, __LINE__, 0, "socket() opening error");
        exit(-1);
    }

    setsockopt(my_sock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(int));

    memset(&sockname, 0, sizeof(struct sockaddr_in));
    sockname.sin_family = AF_INET;
    sockname.sin_port = htons(port_number);
    (void) memcpy(&(sockname.sin_addr.s_addr), hostname->h_addr, hostname->h_length);

    if (connect(my_sock, (struct sockaddr *)&sockname, sizeof(struct sockaddr_in)) < 0)
    {
	    perror("connect()");
        j_log_error(__FILE__, __LINE__, 0, "connect() error");
        return(-1);
    }

    return(my_sock);
}

/******************************/
/*** Socket reading/writing ***/
/******************************/

/* Improved read that handle interruption on reading.
   Read up to len characters, may be less.
   Return length of data read if successful, 0 if nothing to read, or -1 on failure (check errno). */
ssize_t j_net_read_max(int fd, const void *dest, size_t len)
{
	int len_read = 0;
	char *buffer = (char*) dest;

	do
	    len_read = read(fd, buffer, len);
    while ((len_read < 0) && (errno == EINTR));

    return(len_read);
}

/* Improved read that handle interruption on reading, and partial reads.
   Read len characters, no more, no less.
   Return length of data read if successful, 0 if nothing to read, or -1 on failure (check errno). */
ssize_t j_net_read_all(int fd, const void *dest, size_t len)
{
	int len_read = 0;
	char *buffer = (char*) dest;
	char *p = (char*) dest;

	while ((p - buffer) < len)
	{
	    len_read = read(fd, p, len - (p - buffer));
		if (len_read < 0)
		{
		    if (errno == EINTR)
		        continue;
	        return(-1);
		}
		if (len_read == 0)
		    break;
		p += len_read;
	}

	return(p - buffer);
}

/* Improved write that handle interruption on writing, and partial writes.
   Return length of data written if successful, 0 if nothing to write, or -1 on failure (check errno). */
ssize_t j_net_write_all(int fd, const void *source, size_t len)
{
	int len_written = 0;
	char *buffer = (char*) source;
	char *p = (char*) source;

	while ((p - buffer) < len)
	{
	    len_written = write(fd, p, len - (p - buffer));
		if (len_written < 0)
		{
		    if (errno == EINTR)
		        continue;
            return(-1);
		}
		if (len_written == 0)
		    break;
		p += len_written;
	}

	return(p - buffer);
}

/*********************/
/*** Network stuff ***/
/*********************/

/* Return the name of the host, or "!Unknown!". */
const char *j_net_get_hostname(void)
{
    static char localhostname[MAXHOSTNAMELEN] = "";
    char unknown_name[] = "!Unknown!";

    if (localhostname[0] != '\0')
        return(localhostname);
    if (gethostname(localhostname, MAXHOSTNAMELEN) < 0)
    {
        perror("gethostname()");
        j_log_error(__FILE__, __LINE__, 0, "gethostname() error. Return unknown_name");
        (void) strcpy(localhostname, unknown_name);
    }
    return(localhostname);
}

/* Return the port number from the socket sock, or -1 if unknown. */
short j_net_get_sock_port_number(int sock)
{
    struct sockaddr_in sockname;
    socklen_t sockname_len = sizeof(struct sockaddr_in);

    if (getsockname(sock, (struct sockaddr *) &sockname, &sockname_len) < 0)
    {
        perror("bind()");
        j_log_error(__FILE__, __LINE__, 0, "getsockname() error. Return -1");
        return(-1);
    }
    return(htons(sockname.sin_port));
}

/*******************/
/*** Misc. stuff ***/
/*******************/

/* Sweep all the zombie child processus around. Non blocking. */
void j_net_sweeper(void)
{
    int pid;

    while ((pid = waitpid(-1, NULL, WNOHANG)))
        if (pid < 0)
        {
            if (errno == ECHILD)
                break;
            perror("waitpid()");
            j_log_error(__FILE__, __LINE__, 0, "waitpid() error. Exit");
            exit(-1);
        }

    return;
}

/* Generic error logging. Unfinished. Temporary use. */
void j_log_error(char *f, unsigned int l, int severity, char *text, ...)
{
    fprintf(stderr, "%s:%d %s\n", f, l, text);
}
