/* $Header: /san01/cvs/ashpool/csrc/continuity/cont/mmc.c,v 1.6 2004/03/18 15:04:15 aleigh Exp $ */
/*
 * Copyright (c) 1994, 2004 Alex Leigh, All Rights Reserved
 *
 * THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF ALEX LEIGH The copyright
 * notice above does not evidence any actual or intended publication of such
 * source code.
 *
 * Disclosure, copying, distribution, or transmission of this code without
 * express permission is prohibited.
 */

#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <errno.h>
#include <sys/types.h>
#include <pwd.h>
#include <uuid/uuid.h>
#include <sys/time.h>
#include <ctype.h>

#include "continuity.h"
#include "../mecha/mecha.h"

#ifdef HAVE_MMAP
#include <sys/mman.h>
#endif                          /* HAVE_MMAP */

/* Defines. */
#ifndef DEFAULT_EXPIRE_AGE
#define DEFAULT_EXPIRE_AGE 600
#endif
#ifndef DESIRED_FREE_COUNT
#define DESIRED_FREE_COUNT 100
#endif
#ifndef DESIRED_MAX_MAPPED_FILES
#define DESIRED_MAX_MAPPED_FILES 2000
#endif
#ifndef INITIAL_HASH_SIZE
#define INITIAL_HASH_SIZE (1 << 10)
#endif

#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))


/* The Map struct. */
typedef struct MapStruct {
    ino_t ino;
    dev_t dev;
    off_t size;
    time_t ctime;
    int refcount;
    time_t reftime;
    void *addr;
    unsigned int hash;
    int hash_idx;
    struct MapStruct *next;
} Map;

static Map *maps = (Map *) 0;
static Map *free_maps = (Map *) 0;
static int alloc_count = 0, map_count = 0, free_count = 0;
static Map **hash_table = (Map **) 0;
static int hash_size;
static unsigned int hash_mask;
static time_t expire_age = DEFAULT_EXPIRE_AGE;

static void really_unmap(Map ** mm);
static int check_hash_size(void);
static int add_hash(Map * m);
static Map *find_hash(ino_t ino, dev_t dev, off_t size, time_t stat_ctime);
static unsigned int hash(ino_t ino, dev_t dev, off_t size,
                         time_t stat_ctime);

int mmcFinit(void)
{
    pthread_t ignored;

    pthread_create(&ignored, NULL, (void *(*)(void *)) mmcFcleanup_thr,
                   NULL);

    return STATUS_OK;
}

void *mmcFmap(const char *filename, struct stat *sbP, struct timeval *nowP)
{
    time_t now;
    struct stat sb;
    Map *m;
    int fd;

    /* Stat the file, if necessary. */
    if (sbP != (struct stat *) 0)
        sb = *sbP;
    else {
        if (stat(filename, &sb) != 0) {
            logFmsgid(1, "CON", 12, filename, errno);
            return NULL;
        }
    }

    /* Get the current time, if necessary. */
    if (nowP != (struct timeval *) 0)
        now = nowP->tv_sec;
    else
        now = time((time_t *) 0);

    /* See if we have it mapped already, via the hash table. */
    if (check_hash_size() < 0) {
        logFmsgid(1, "CON", 13, "mmc: check_hash_size() failed.");
        return NULL;
    }
    m = find_hash(sb.st_ino, sb.st_dev, sb.st_size, sb.st_ctime);
    if (m != (Map *) 0) {
        /* Yep.  Just return the existing map */
        ++m->refcount;
        m->reftime = now;
        return m->addr;
    }
    /* Open the file. */
    fd = open(filename, O_RDONLY);
    if (fd < 0) {
        logFmsgid(1, "CON", 14, filename, errno);
        return NULL;
    }

    /* don't keep this fd open on fork() */
    fleFset_close_exec(fd);

    /* Find a free Map entry or make a new one. */
    if (free_maps != (Map *) 0) {
        m = free_maps;
        free_maps = m->next;
        --free_count;
    } else {
        m = (Map *) malloc(sizeof(Map));
        if (m == (Map *) 0) {
            (void) close(fd);
            logFmsgid(1, "CON", 15, "mmc map creation");
            return NULL;
        }
        ++alloc_count;
    }

    /* Fill in the Map entry. */
    m->ino = sb.st_ino;
    m->dev = sb.st_dev;
    m->size = sb.st_size;
    m->ctime = sb.st_ctime;
    m->refcount = 1;
    m->reftime = now;

    /*
     * Avoid doing anything for zero-length files; some systems don't like *
     * to mmap them, other systems dislike mallocing zero bytes.
     */
    if (m->size == 0)
        m->addr = (void *) 1;   /* arbitrary non-NULL address */
    else {
#ifdef HAVE_MMAP
        /* Map the file into memory. */
        m->addr = mmap(0, m->size, PROT_READ, MAP_SHARED, fd, 0);
        if (m->addr == MAP_FAILED) {
            logFmsgid(1, "CON", 16, errno, "mmc");
            (void) close(fd);
            free((void *) m);
            --alloc_count;
            return NULL;
        }
#else                           /* HAVE_MMAP */
        /* Read the file into memory. */
        m->addr = (void *) malloc(m->size);
        if (m->addr == NULL) {
            logFmsgid(1, "CON", 15, "mmc: file store");
            (void) close(fd);
            free((void *) m);
            --alloc_count;
            return NULL;
        }
        if (read(fd, m->addr, m->size) != m->size) {
            logFmsgid(1, "CON", 17, errno, "mmcFmap");
            (void) close(fd);
            free((void *) m);
            --alloc_count;
            return NULL;
        }
#endif                          /* HAVE_MMAP */
    }
    (void) close(fd);

    /* Put the Map into the hash table. */
    if (add_hash(m) < 0) {
        logFmsgid(1, "CON", 13, "mmc: add_hash() failure.");
        free((void *) m);
        --alloc_count;
        return (void *) 0;
    }
    /* Put the Map on the active list. */
    m->next = maps;
    maps = m;
    ++map_count;

    /* And return the address. */
    return m->addr;
}


void mmc_unmap(void *addr, struct stat *sbP, struct timeval *nowP)
{
    Map *m = (Map *) 0;

    /* Find the Map entry for this address.  First try a hash. */
    if (sbP != (struct stat *) 0) {
        m = find_hash(sbP->st_ino, sbP->st_dev, sbP->st_size,
                      sbP->st_ctime);
        if (m != (Map *) 0 && m->addr != addr)
            m = (Map *) 0;
    }
    /* If that didn't work, try a full search. */
    if (m == (Map *) 0)
        for (m = maps; m != (Map *) 0; m = m->next)
            if (m->addr == addr)
                break;
    if (m == (Map *) 0)
        logFmsgid(1, "CON", 13, "mmcFunmap: Unable to find entry.");
    else if (m->refcount <= 0)
        logFmsgid(1, "CON", 13, "mmcFunmap: Reference was <=0");
    else {
        --m->refcount;
        if (nowP != (struct timeval *) 0)
            m->reftime = nowP->tv_sec;
        else
            m->reftime = time((time_t *) 0);
    }
}


void mmcFcleanup(void)
{
    time_t now;
    Map **mm;
    Map *m;

    /* Get the current time, if necessary. */
    now = time((time_t *) 0);

    /* Really unmap any unreferenced entries older than the age limit. */
    for (mm = &maps; *mm != (Map *) 0;) {
        m = *mm;
        if (m->refcount == 0 && now - m->reftime >= expire_age)
            really_unmap(mm);
        else
            mm = &(*mm)->next;
    }

    /* If there are still too many (or too few) maps, adjust the age limit. */
    if (map_count > DESIRED_MAX_MAPPED_FILES)
        expire_age = max((expire_age * 2) / 3, DEFAULT_EXPIRE_AGE / 10);
    else if (map_count < DESIRED_MAX_MAPPED_FILES / 2)
        expire_age = min((expire_age * 5) / 4, DEFAULT_EXPIRE_AGE * 3);

    /* Really free excess blocks on the free list. */
    while (free_count > DESIRED_FREE_COUNT) {
        m = free_maps;
        free_maps = m->next;
        --free_count;
        free((void *) m);
        --alloc_count;
    }
}

void mmcFcleanup_thr(void)
{
    while (1) {
        mmcFcleanup();
        thrFsleep(60000);
    }
}

static void really_unmap(Map ** mm)
{
    Map *m;

    m = *mm;
    if (m->size != 0) {
#ifdef HAVE_MMAP
        if (munmap(m->addr, m->size) < 0)
            logFmsgid(1, "CON", 18, errno, "mmc");
#else                           /* HAVE_MMAP */
        free((void *) m->addr);
#endif                          /* HAVE_MMAP */
    }
    /* And move the Map to the free list. */
    *mm = m->next;
    --map_count;
    m->next = free_maps;
    free_maps = m;
    ++free_count;
    /*
     * This will sometimes break hash chains, but that's harmless; the *
     * unmapping code that searches the hash table knows to keep searching.
     */
    hash_table[m->hash_idx] = (Map *) 0;
}


void mmc_destroy(void)
{
    Map *m;

    while (maps != (Map *) 0)
        really_unmap(&maps);
    while (free_maps != (Map *) 0) {
        m = free_maps;
        free_maps = m->next;
        --free_count;
        free((void *) m);
        --alloc_count;
    }
}

/* Make sure the hash table is big enough. */
static int check_hash_size(void)
{
    int i;
    Map *m;

    /* Are we just starting out? */
    if (hash_table == (Map **) 0) {
        hash_size = INITIAL_HASH_SIZE;
        hash_mask = hash_size - 1;
    }
    /* Is it at least three times bigger than the number of entries? */
    else if (hash_size >= map_count * 3)
        return 0;
    else {
        /* No, got to expand. */
        free((void *) hash_table);
        /* Double the hash size until it's big enough. */
        do {
            hash_size = hash_size << 1;
        }
        while (hash_size < map_count * 6);
        hash_mask = hash_size - 1;
    }
    /* Make the new table. */
    hash_table = (Map **) malloc(hash_size * sizeof(Map *));
    if (hash_table == (Map **) 0)
        return -1;
    /* Clear it. */
    for (i = 0; i < hash_size; ++i)
        hash_table[i] = (Map *) 0;
    /* And rehash all entries. */
    for (m = maps; m != (Map *) 0; m = m->next)
        if (add_hash(m) < 0)
            return -1;
    return 0;
}


static int add_hash(Map * m)
{
    unsigned int h, he, i;

    h = hash(m->ino, m->dev, m->size, m->ctime);
    he = (h + hash_size - 1) & hash_mask;
    for (i = h;; i = (i + 1) & hash_mask) {
        if (hash_table[i] == (Map *) 0) {
            hash_table[i] = m;
            m->hash = h;
            m->hash_idx = i;
            return 0;
        }
        if (i == he)
            break;
    }
    return -1;
}


static Map *find_hash(ino_t ino, dev_t dev, off_t size, time_t stat_ctime)
{
    unsigned int h, he, i;
    Map *m;

    h = hash(ino, dev, size, stat_ctime);
    he = (h + hash_size - 1) & hash_mask;
    for (i = h;; i = (i + 1) & hash_mask) {
        m = hash_table[i];
        if (m == (Map *) 0)
            break;
        if (m->hash == h && m->ino == ino && m->dev == dev &&
            m->size == size && m->ctime == stat_ctime)
            return m;
        if (i == he)
            break;
    }
    return (Map *) 0;
}


static unsigned int hash(ino_t ino, dev_t dev, off_t size,
                         time_t stat_ctime)
{
    unsigned int h = 177573;

    h ^= ino;
    h += h << 5;
    h ^= dev;
    h += h << 5;
    h ^= size;
    h += h << 5;
    h ^= stat_ctime;

    return h & hash_mask;
}


/*
 * Stolen from VI
 */

int mmcFopen(char *filename, char **buf, int *len)
{
    int fd;
    void *map;
    struct stat sbuf;

    if ((fd = open(filename, O_RDONLY)) == -1)
        return (-errno);

    fleFset_close_exec(fd);

    if (fstat(fd, &sbuf) == -1) {
        close(fd);
        return (-1);
    }
    if ((map =
         mmap(0, sbuf.st_size, PROT_READ, MAP_PRIVATE, fd,
              0)) == MAP_FAILED) {
        close(fd);
        return 0;
    }
    close(fd);

    *buf = map;
    *len = sbuf.st_size;

    return 0;
}

/* Stolen from VI */

int mmcFclose(char *buf, int len)
{
    if (munmap(buf, len) == -1)
        return (-1);
    return (0);
}
