/*
 * Copyright (c) 1995, 1996, 1997, 1998 Kungliga Tekniska Högskolan
 * (Royal Institute of Technology, Stockholm, Sweden).
 * All rights reserved.
 * 
 * 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. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by the Kungliga Tekniska
 *      Högskolan and its contributors.
 * 
 * 4. Neither the name of the Institute 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 INSTITUTE 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 INSTITUTE 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.
 */

#include "milko_locl.h"
RCSID("$Id: mbuf.c,v 1.13 1998/11/04 14:54:12 lha Exp $") ;

static int
fixfilelen(int fd, size_t off, size_t len)
{
    struct stat sb;

    if (fstat(fd, &sb) == -1)
	return errno;
    
    if (sb.st_size < off + len && ftruncate (fd, off + len) < 0)
	return errno; 

    return 0;
}

#ifdef HAVE_MMAP

#ifndef MAP_FAILED
#define MAP_FAILED ((void *)(-1))
#endif

#if defined(MAP_ANON) && !defined(MAP_ANONYMOUS)
#define MAP_ANONYMOUS MAP_ANON
#endif


static int
mmap_flags (mbuf_flags flags)
{
    int ret = 0;

    if (flags & MBUF_ANON)
	ret = MAP_ANONYMOUS|MAP_PRIVATE;
    else if (flags & MBUF_READ)
	ret = MAP_PRIVATE;
    else if (flags & MBUF_WRITE)
	ret = MAP_SHARED;
    return ret;
}

static int
mmap_create (mbuf *mbuf, int fd, size_t off, size_t len, mbuf_flags flags)
{
    void *buf;

    if (len > 0) {
	struct stat sb;

	/* Store the real length so mmap don't will change it for us. */
	if (fstat(fd, &sb) == -1)
	    return errno;
	mbuf->reallen = sb.st_size;

	buf = mmap (0, len, PROT_READ | PROT_WRITE, mmap_flags(flags),fd,off);
	
	if (buf == (void *) MAP_FAILED) {
	    if (fd != -1) 
		close (fd);
	    return errno;
	}
    } else 
	buf = NULL;

    mbuf->buf   = buf;
    mbuf->fd    = fd;
    mbuf->off   = off;
    mbuf->len   = len;
    mbuf->flags = flags;
    return 0;
}

static int
mmap_resize (mbuf *buf, size_t off, size_t len)
{
    int ret;

    if (buf->flags & MBUF_ANON) {
	mbuf new_mbuf;
	
	ret = mmap_create (&new_mbuf, buf->fd, off, len, buf->flags);
	if (ret) {
	    ret = errno ;
	    munmap(buf->buf, buf->len);
	    return ret;
	}
	
	/*
	 * XXX This is not really right, only working when expanding
	 * and keeping the offset 
	 */
	if (off == buf->off && len > buf->len)
	    memcpy(new_mbuf.buf, buf->buf, buf->len);
	
	munmap (buf->buf, buf->len);
	*buf = new_mbuf;
	return 0;
    } else {
	ret = munmap (buf->buf, buf->len);
	if (ret < 0) {
	    close (buf->fd);
	    return errno;
	}

	ret = fixfilelen(buf->fd, off, len);
	if (ret < 0) {
	    close (buf->fd);
	    return errno;
	}
	return mmap_create (buf, buf->fd, off, len, buf->flags);
    }
    return -1;
}

static int
mmap_end (mbuf *mbuf)
{
    int ret;

    ret = munmap (mbuf->buf, mbuf->len);
    if (ret < 0)
	ret = errno;
    if (! (mbuf->flags & MBUF_ANON)) {
	ftruncate(mbuf->fd, mbuf->reallen);
	close (mbuf->fd);
    }
    return ret;
}

static int 
mmap_copyrx2fd (struct rx_call *call, int fd, size_t off, size_t len)
{
    void *buf;
    int r_len;
    int save_errno, ret;

    ret = fixfilelen(fd, off, len);
    if (ret)
	return ret;

    buf = mmap (0, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, off);
    if (buf == (void *) MAP_FAILED)
	return errno;
    r_len = rx_Read (call, buf, len);
    save_errno = errno;
    munmap (buf, len);
    close (fd);
    if (r_len != len) {
	return save_errno;
    } else {
	return 0;
    }
}

static int
mmap_copyfd2rx (int fd, struct rx_call *call, size_t off, size_t len)
{
    void *buf;
    int r_write;
    int save_errno;

    buf = mmap (0, len, PROT_READ, MAP_PRIVATE, fd, off);
    if (buf == (void *) MAP_FAILED)
	return errno;
    r_write = rx_Write (call, buf, len);
    save_errno = errno;
    munmap (buf, len);
    close (fd);
    if (r_write != len)
	return save_errno;
    else
	return 0;
}

#else /* !HAVE_MMAP */

static int
malloc_create (mbuf *mbuf, int fd, size_t off, size_t len, mbuf_flags flags)
{
    struct stat sb;
    void *buf;
    size_t readlen;
    size_t donelen;

    buf = malloc (len);
    if (buf == NULL) {
	close(fd);
	return ENOMEM;
    }
    if (lseek(fd, 0, SEEK_SET) == -1) {
	buf = NULL;
	close(fd);
    } else if (!(flags & MBUF_ANON)) {
	donelen = 0;
	do {
	    readlen = read (fd, buf+donelen, len-donelen);
	    donelen += readlen;
	} while (readlen != -1 && donelen < len);
	if (readlen == -1) {
	    free(buf);
	    close(fd);
	    return errno;
	}
	/* Store the real length so mmap don't will change it for us. */
	if (fstat(fd, &sb) == -1) {
	    free(buf);
	    return errno;
	}
	mbuf->reallen = sb.st_size;
    }
    mbuf->reallen = sb.st_size;
    mbuf->buf   = buf;
    mbuf->fd    = fd;
    mbuf->off   = off;
    mbuf->len   = len;
    mbuf->flags = flags;
    return 0;
}

static int
malloc_flush (mbuf *mbuf)
{
    if (mbuf->flags & MBUF_WRITE) {
	lseek (mbuf->fd, SEEK_SET, mbuf->off);
	if (write (mbuf->fd, mbuf->buf, mbuf->len) != mbuf->len)
	    return errno;
    }
    return 0;
}

static int
malloc_end (mbuf *mbuf)
{
    int ret;

    ret = malloc_flush (mbuf);
    ftruncate(mbuf->fd, mbuf->reallen);
    close (mbuf->fd);
    free (mbuf->buf);
    return ret;
}

static int
malloc_resize (mbuf *buf, size_t off, size_t len)
{
    int ret;

    if (buf->flags & MBUF_ANON) {
	mbuf new_mbuf;
	
	ret = malloc_create (&new_mbuf, buf->fd, off, len, buf->flags);
	
	/*
	 * XXX This is not really right, only working when expanding
	 * and keeping the offset 
	 */
	if (off == buf->off && len > buf->len)
	    memcpy(new_mbuf.buf, buf->buf, buf->len);
	
	free (buf->buf);
	*buf = new_mbuf;
	return 0;
    } else {
        free (buf->buf);

	ret = fixfilelen(buf->fd, off, len);
	if (ret < 0) {
	    close (buf->fd);
	    return errno;
	}
	return malloc_create (buf, buf->fd, off, len, buf->flags);
    }

    return -11;
}

static int 
malloc_copyrx2fd (struct rx_call *call, int fd, size_t off, size_t len)
{
    void *buf;
    struct stat statbuf;
    u_long bufsize;
    u_long nread;
    size_t reallen = len;
    int ret = 0;

    if (fstat (fd, &statbuf)) {
	bufsize = 1024;
    } else
	bufsize = statbuf.st_blksize;

    buf = malloc (bufsize);
    if (buf == NULL)
	return ENOMEM;

    while ( len && (nread = rx_Read (call, buf, MIN(bufsize,
						    len))) > 0) {
	if (write (fd, buf, nread) != nread) {
	    ret = errno;
	    break;
	}
	len -= nread;
    }
    if (ftruncate (fd, reallen) < 0)
	ret = errno;
    close (fd);
    free (buf);
    return ret;
}

static int
malloc_copyfd2rx (int fd, struct rx_call *call, size_t off, size_t len)
{
    void *buf;
    struct stat statbuf;
    u_long bufsize;
    u_long nread;
    int ret = 0;

    if (fstat (fd, &statbuf)) {
	bufsize = 1024;
    } else
	bufsize = statbuf.st_blksize;

    buf = malloc (bufsize);
    if (buf == NULL)
	return ENOMEM;

    while (len
	   && (nread = read (fd, buf, MIN(bufsize, len))) > 0) {
	if (rx_Write (call, buf, nread) != nread) {
	    ret = errno;
	    break;
	}
	len -= nread;
    }
    close (fd);
    free (buf);
    return ret;
}
#endif /* !HAVE_MMAP */

/*
 * Return a pointer to a copy of this file contents. If we have mmap,
 * we use that, otherwise we have to allocate some memory and read it.
 */

int
mbuf_create (mbuf **mbuf, int fd, size_t off, size_t len, mbuf_flags flags)
{
    if (mbuf == NULL)
	return EINVAL;
    
    *mbuf = calloc(1, sizeof(**mbuf));
    if (*mbuf == NULL)
	return ENOMEM;

    ++(*mbuf)->refcount;
#ifdef HAVE_MMAP
    return mmap_create (*mbuf, fd, off, len, flags);
#else
    return malloc_create (*mbuf, fd, off, len, flags);
#endif
}

/*
 * Inc the refcount
 */

int
mbuf_use (mbuf *buf)
{
    return ++buf->refcount;
}

/*
 * Undo everything we did in mbuf_create.
 */

int
mbuf_end (mbuf *mbuf)
{
    int error;

    if (--mbuf->refcount)
	return 0;
#ifdef HAVE_MMAP
    error = mmap_end (mbuf);
#else
    error =  malloc_end (mbuf);
#endif
    free(mbuf);
    return error;
}

/*
 * Resize the mbuf to new offset, len.
 */

int
mbuf_resize (mbuf *mbuf, size_t off, size_t len)
{
#ifdef HAVE_MMAP
    return mmap_resize (mbuf, off, len);
#else
    return malloc_resize (mbuf, off, len);
#endif
}

int
mcopybuf2rx(struct rx_call *call, mbuf *buf, size_t off, size_t len)
{
    char *ptr;

    assert(buf);
    assert(buf->off <= off) ;
    assert(buf->off + buf->len >= off + len);
    
    ptr = (char *) buf->buf + (off - buf->off);
    
    return rx_Write(call, ptr, len) != len;
}


/*
 * Copy from a RX_call to a fd.
 */

int 
mcopyrx2fd (struct rx_call *call, int fd, size_t off, size_t len)
{
#ifdef HAVE_MMAP
    return mmap_copyrx2fd (call, fd, off, len);
#else
    return malloc_copyrx2fd (call, fd, off, len);
#endif
}

/*
 * Copy from a file descriptor to a RX call.
 */

int
mcopyfd2rx (int fd, struct rx_call *call, size_t off, size_t len)
{
#ifdef HAVE_MMAP
    return mmap_copyfd2rx (fd, call, off, len);
#else
    return malloc_copyfd2rx (fd, call, off, len);
#endif
}
