/* $Cambridge: hermes/src/prayer/session/portlist.c,v 1.3 2008/09/16 09:59:58 dpc22 Exp $ */
/************************************************
 *    Prayer - a Webmail Interface              *
 ************************************************/

/* Copyright (c) University of Cambridge 2000 - 2008 */
/* See the file NOTICE for conditions of use and distribution. */

#include "prayer_session.h"

/* portlist is a class which manages of lists of (HTTP) ports which are
 * used by session_inet(). Each port can have an associated file descriptor
 * if the port is active or allocated but idle. Can also have an associated
 * child process if port currently active. Experimental!  */

/* portlist_create() ****************************************************
 *
 * Create a new portlist structure.
 *   pool:     Target pool
 * first_port: First port in range
 *      count: Number of ports in range
 *
 * Returns: Ptr to new portlist structure.
 ***********************************************************************/

struct portlist *portlist_create(unsigned long first_port,
                                 unsigned long count)
{
    struct portlist *pl = pool_alloc(NIL, sizeof(struct portlist));

    pl->idle = list_create(NIL, T);
    pl->active = list_create(NIL, T);
    pl->next = first_port;
    pl->last = first_port + count - 1;

    return (pl);
}

/* portlist_free() *******************************************************
 *
 * Free portlist
 ************************************************************************/

void portlist_free(struct portlist *pl)
{
    list_free(pl->idle);
    list_free(pl->active);
    free(pl);
}

/* ====================================================================== */

/* portlist_reserve_port() ***********************************************
 *
 * Reserve a port from the given list, either from idle list or free range
 *   pl: Portlist
 *
 * Returns: Ptr to port structure (allocated automatically if needed)
 ***********************************************************************/

struct port *portlist_reserve_port(struct portlist *pl)
{
    struct port *port;
    unsigned long inet_port;
    int sockfd;

    if (list_length(pl->idle) > 0) {
        struct list_item *li = list_shift(pl->idle);
        list_unshift(pl->active, li, NIL);

        return ((struct port *) li);
    }

    if (pl->next > pl->last)
        return (NIL);

    port = pool_alloc(NIL, sizeof(struct port));

    /* Assign the next next available port */
    do {
        inet_port = pl->next++;
        sockfd = os_bind_inet_socket(inet_port, NIL);
    }
    while ((sockfd < 0) && (inet_port <= pl->last));

    port->inet_port = inet_port;
    port->sockfd = sockfd;
    port->pid = 0;

    list_unshift(pl->active, (struct list_item *) port, NIL);
    return (port);
}

/* portlist_free_port_bypid() *******************************************
 *
 * Free a port based on process id of associated child process. (Actually
 * just moves the port from active to idle list).
 *   pl: Portlist
 *  pid: Process id,
 *
 * Returns: T if port was allocated, NIL otherwise
 ***********************************************************************/

BOOL portlist_free_port_bypid(struct portlist * pl, pid_t pid)
{
    unsigned long offset;
    struct list_item *li;
    struct port *port = NIL;
    struct port *newport;

    if (pl == NIL)
        return (NIL);

    for (offset = 0, li = pl->active->head; li; offset++, li = li->next) {
        port = (struct port *) li;

        if (port->pid == pid)
            break;
    }

    if (li == NIL)
        return (NIL);

    /* Move from active to inactive list: Could make this more efficient! */
    newport = pool_alloc(NIL, sizeof(struct port));
    newport->inet_port = port->inet_port;
    newport->sockfd = port->sockfd;
    newport->pid = 0;
    list_unshift(pl->idle, (struct list_item *) newport, NIL);

    list_remove_byoffset(pl->active, offset);

    return (T);
}

/* portlist_free_port() **************************************************
 *
 * Free a port based on port number (Actually just moves the port from
 * active to idle list).
 *   pl: Portlist
 *  pid: Process id,
 *
 * Returns: T if port was allocated, NIL otherwise
 ***********************************************************************/

BOOL portlist_free_port(struct portlist * pl, unsigned long inet_port)
{
    unsigned long offset;
    struct list_item *li;
    struct port *port = NIL;
    struct port *newport;

    for (offset = 0, li = pl->active->head; li; offset++, li = li->next) {
        port = (struct port *) li;

        if (port->inet_port == inet_port)
            break;
    }

    if (li == NIL)
        return (NIL);

    /* Move from active to inactive list: Could make this more efficient! */
    newport = pool_alloc(NIL, sizeof(struct port));
    newport->inet_port = port->inet_port;
    newport->sockfd = port->sockfd;
    newport->pid = 0;
    list_unshift(pl->idle, (struct list_item *) newport, NIL);

    list_remove_byoffset(pl->active, offset);
    return (T);
}

/* ====================================================================== */

/* portlist_close_all() **************************************************
 *
 * Close all ports in list.
 *    pl: Portlist
 ***********************************************************************/

void portlist_close_all(struct portlist *pl)
{
    struct list_item *li;

    for (li = pl->active->head; li; li = li->next) {
        struct port *port = (struct port *) li;
        int fd = port->sockfd;

        close(fd);
    }

    for (li = pl->idle->head; li; li = li->next) {
        struct port *port = (struct port *) li;
        int fd = port->sockfd;

        close(fd);
    }
}

/* ====================================================================== */

/* portlist_close_all_execpt() ******************************************
 *
 * Close all ports in list, except the nominated one which is about to
 * be passed to child process
 *     pl: Portlist
 * except: fd to be left open
 ***********************************************************************/

void portlist_close_all_except(struct portlist *pl, int except_fd)
{
    struct list_item *li;

    for (li = pl->active->head; li; li = li->next) {
        struct port *port = (struct port *) li;
        int fd = port->sockfd;

        if (fd != except_fd)
            close(fd);
    }

    for (li = pl->idle->head; li; li = li->next) {
        struct port *port = (struct port *) li;
        int fd = port->sockfd;

        close(fd);
    }
}
