/* Copyright (C) 1993,1994 by the author(s).
 
 This software is published in the hope that it will be useful, but
 WITHOUT ANY WARRANTY for any part of this software to work correctly
 or as described in the manuals. See the ShapeTools Public License
 for details.

 Permission is granted to use, copy, modify, or distribute any part of
 this software but only under the conditions described in the ShapeTools 
 Public License. A copy of this license is supposed to have been given
 to you along with ShapeTools in a file named LICENSE. Among other
 things, this copyright notice and the Public License must be
 preserved on all copies.
*/
/*
 * AtFStk -- Attribute Filesystem Toolkit Library
 *
 * read.c
 *
 * Author: Juergen Nickelsen (Juergen.Nickelsen@cs.tu-berlin.de)
 *
 * $Header: read.c[7.0] Fri Jun 25 16:39:04 1993 andy@cs.tu-berlin.de frozen $
 */

/*
 * Read versions with attributes expanded.
 *
 * For each version opened with atOpenExpand() a descriptor is
 * allocated in desc_table[]. This descriptor contains:
 *
 *    all necessary data about the version (AtFS key, FILE *, file
 *    descriptor, name, contents),
 *
 *    two flags if expansion shall be done (toggled by $__xoff$,
 *    xpon) and if expansion shall be done at all (e.g. usually
 *    not for busy versions),
 *
 *    state of the reader (position in version contents, current
 *    expansion (if one is active), position in current expansion).
 *
 * Contents of the version means that the complete contents of the
 * version is read into memory (readin_version()).
 *
 * atReadExpand() copies data from the descriptor buffer to the
 * user-supplied buffer. When it encounters an attribute citation, the
 * citation is expanded with retrieve_expansion(), which calls
 * atRetAttr(). Then characters are copied from the expansion buffer
 * to the user-supplied buffer.
 *
 * atCloseExpand() frees the descriptor and associated resources.
 */

#include <errno.h>
#include <ctype.h>
#include "atfs.h"
#include "atfstk.h"


extern int errno ;

#define INITIAL_BUFFER_SIZE  8192
#define NO_AR_DESC             64
#define XPOFF_ATTR           "xpoff"
#define XPON_ATTR            "xpon"


/* descriptor for an open version */
typedef struct at_read_desc {
    char	 *ar_name ;	/* name of version */
    Af_key       *ar_key ;	/* AtFS key of version */
    FILE         *ar_fp ;	/* returned by af_open() */
    int		  ar_fd ;	/* file descriptor */
    char         *ar_buffer ;	/* buffer for version contents */
    char         *ar_bufend ;	/* ptr to first byte after end of buffer */
    char         *ar_pos ;	/* position in buffer */
    char	  ar_noexpand ;	/* no expansion at all */
    char	  ar_expand ;	/* non-zero if expansion shall be done */
    char	 *ar_expansion;	/* active expansion */
    char	 *ar_exp_pos ;	/* position in expansion */
} at_read_desc ;


/* descriptor table */
static at_read_desc *desc_table[NO_AR_DESC] ;

/*
 * INTERNALS
 */

static int   allocate_descriptor A((void)) ;
static int   readin_version A((at_read_desc *dp)) ;
static int   std_attr_p A((Af_key *key, char *attr)) ;
static char *end_of_attribute_or_not A((char *p, char *limit)) ;
static int   retrieve_expansion A((at_read_desc *dp, char *attrname)) ;



/* allocate and initialize a descriptor */
static int allocate_descriptor()
{
    int index ;

    /* Look for free index (0 is reserved for stdin handling).
     * desc_table[] should be small enough not to make linear search a
     * pain. */
    for (index = 1; index < NO_AR_DESC && desc_table[index] != 0; index++) ;
    if (index == NO_AR_DESC) {
	errno = EMFILE ;
	return -1 ;
    }

    /* allocate and clean descriptor structure */
    desc_table[index] = (at_read_desc *) malloc(sizeof(at_read_desc)) ;
    if (desc_table[index] == 0) {
	errno = ENOMEM ;
	return -1 ;
    }
    memset((char *) desc_table[index], 0, sizeof(at_read_desc)) ;

    return index ;
}



/* Read contents of version into a buffer.
 * Return appropriate errno on failure, else 0. */
static int readin_version(dp)
at_read_desc *dp ;
{
    unsigned long buffer_size ;	/* size of buffer */
    struct stat statbuf ;	/* argument to fstat(2) */
    unsigned long nbytes ;	/* no of bytes already read */
    unsigned long nread ;	/* no of bytes read at one time */
    char *newbuf ;
    

    if (fstat(dp->ar_fd, &statbuf) == -1) {
	return -1 ;
    }
    /* regular file? */
    if (S_ISREG(statbuf.st_mode)) {
	/* yes, so we try to read it in one chunk */

	/* allocate and initialize buffer */
	buffer_size = statbuf.st_size ;
	dp->ar_buffer = malloc(buffer_size) ;
	if (dp->ar_buffer == 0) {
	    return EFBIG ;
	}
	dp->ar_pos = dp->ar_buffer ;

	/* read data */
	nbytes = 0 ;
	while (nbytes < buffer_size) {
	    nread = read(dp->ar_fd, dp->ar_pos, buffer_size - nbytes) ;
	    if (nread == -1) {
		return errno ;
	    }
	    if (nread == 0) {
		break ;
	    }
	    dp->ar_pos += nread ;
	    nbytes += nread ;
	}
	dp->ar_bufend = dp->ar_pos ;
    } else {
	/* not a regular file (urgh) */

	unsigned long increment ; /* for growing of buffer */

	buffer_size = INITIAL_BUFFER_SIZE ;
	increment = INITIAL_BUFFER_SIZE ;

	/* allocate initial buffer */
	dp->ar_buffer = malloc(buffer_size) ;
	if (dp->ar_buffer == 0) {
	    return ENOMEM ;
	}

	/* read data */
	nbytes = 0 ;
	nread = 1 ;		/* fake initial condition */
	while (nread > 0) {
	    nread = read(dp->ar_fd,
			 dp->ar_buffer + nbytes,
			 buffer_size - nbytes) ;
	    if (nread == -1) {
		return errno ;
	    }
	    if (nread == 0) {
		break ;
	    }
	    nbytes += nread ;

	    /* if buffer gets full, allocate larger buffer */
	    if (nbytes > 3 * buffer_size / 4) {
		increment = increment * 4 / 3 ;
		newbuf = realloc(dp->ar_buffer, buffer_size + increment) ;
		if (newbuf == 0) {
		    return EFBIG ;
		}
		dp->ar_buffer = newbuf ;
		buffer_size += increment ;
	    }
	}
	/* adjust buffer to final size */
	dp->ar_buffer = realloc(dp->ar_buffer, nbytes) ;
	if (dp->ar_buffer == 0) {
	    return ENOMEM ;
	}
	dp->ar_bufend = dp->ar_buffer + nbytes ;
    }

    dp->ar_pos = dp->ar_buffer ;
    return 0 ;
}


/* open a version for reading with atReadExpand() */
int atOpenExpand(vname, expand_busy)
char *vname ;			/* name of version */
int expand_busy ;		/* expand attributes of busy versions */
{
    Af_key *key ;		/* key returned by atBindVersion() */
    int desc ;			/* index into desc_table */
    struct at_read_desc *dp ;	/* descriptor struct */

    /* get AtFS key for vname */
    if (!(key = atBindVersion (vname, ""))) {
	return -1 ;
    }

    /* initialize std_attr_p() */
    std_attr_p(key, 0) ;

    /* allocate descriptor */
    desc = allocate_descriptor() ;
    if (desc == -1) {
	af_dropkey(key) ;
	return -1 ;
    }

    dp = desc_table[desc] ;
    dp->ar_key = key ;
    dp->ar_name = strdup(atWriteName(key, af_afpath(vname))) ;
    if (dp->ar_name == 0) {
	errno = ENOMEM ;
	atCloseExpand(desc) ;
	return -1 ;
    }

    /* open version */
    if ((dp->ar_fp = af_open(key, "r")) == 0) {
	/* this should not happen:
	 * atBindVersion() promised a version
	 */
	atCloseExpand(desc) ;
	return -1 ;
    }
    dp->ar_fd = fileno(dp->ar_fp) ;

    /* expand attribute citations in busy version only
     * if expand_busy is non-zero */
    if (af_retnumattr(key, AF_ATTSTATE) == AF_BUSY) {
	dp->ar_noexpand = ! expand_busy ;
	dp->ar_expand = expand_busy ;
    } else {
	dp->ar_noexpand = 0 ;
	dp->ar_expand = 1 ;
    }

    /* read version contents into buffer */
    if (readin_version(dp) != 0) {
	atCloseExpand(desc) ;
	return -1 ;
    }
    fclose(dp->ar_fp) ;
    dp->ar_fp = 0 ;
    
    return desc ;
}


/* return the name of a version opened with atOpenExpand() */
char *atGetWriteName(desc)
int desc ;
{
    return desc_table[desc]->ar_name ;
}


/* Returns non-zero if attribute is standard attribute.
 * Must be initialized with a valid key first.
 * Kludge recommended by Andy. */
static int std_attr_p(key, attr)
Af_key *key ;			/* only for initialization */
char *attr ;			/* name of attribute */
{
    static char *stdattr_ptr = 0 ; /* ptr to location of standard attributes */

    if (stdattr_ptr == 0) {
	stdattr_ptr = af_retattr(key, AF_ATTTYPE) ;
	/* this does not fail */
    }

    return attr == stdattr_ptr ;
}


/* free resources allocated to descriptor */
void atCloseExpand(desc)
int desc ;
{
    at_read_desc *dp ;

    dp = desc_table[desc] ;
    if (dp != 0) {
	if (dp->ar_expansion) {
	    free(dp->ar_expansion) ;
	}
	if (dp->ar_key) {
	    af_dropkey(dp->ar_key) ;
	}
	if (dp->ar_fp != 0) {
	    fclose(dp->ar_fp) ;
	}
	if (dp->ar_buffer != 0) {
	    free(dp->ar_buffer) ;
	}
	if (dp->ar_name != 0) {
	    free(dp->ar_name) ;
	}
	free(dp) ;
	desc_table[desc] = 0 ;
    }
}


/* return ptr to end of attribute name or 0 if no attribute present */
static char *end_of_attribute_or_not(p, limit)
char *p ;			/* expected to point to "$" */
char *limit ;
{
    enum { dollar, first_, second_, inname } state ;
    char *endp ;

    state = dollar ;
    if (*p++ != '$') {
	return 0 ;
    }
    endp = p ;
    while (endp <= limit) {
	switch (state) {
	  case dollar:
	    if (*p++ != '_') {
		return 0 ;
	    }
	    state = first_ ;
	    break ;
	  case first_:
	    if (*p++ != '_') {
		return 0 ;
	    }
	    state = second_ ;
	    break ;
	  case second_:
	    if (*p == '\0' || *p == '$' || isspace(*p)) {
		return 0 ;
	    }
	    p++ ;
	    state = inname ;
	    break ;
	  case inname:
	    if (*p == '\0' || *p == '$' || isspace(*p)) {
		return p ;
	    }
	    p++ ;
	}
    }
    return p ;
}


/* Retrieve attribute,
 * handle xpoff and xpon pseudo attributes.
 * Return 0 if no value exists.
 * If an error occurs, return value is also 0, but hopefully
 * on of atRetAttr() or strdup() has set errno appropriately.
 */
static int retrieve_expansion(dp, attrname)
at_read_desc *dp ;
char *attrname ;
{
    char *value ;		/* attribute value */
    int ret = 0 ;		/* return value to be */

    errno = 0 ;			/* may get set by atRetAttr() or
				 * strdup() */
    
    /* confusing if-statement follows */
    if (dp->ar_noexpand) {
	ret = 0 ;
    } else if (dp->ar_expand) {
	if (!strcmp(attrname, XPOFF_ATTR)) {
	    dp->ar_expand = 0 ;
	    ret = 0 ;
	} else {
	    value = atRetAttr(dp->ar_key, attrname) ;
	    if (value) {
		/* atRetAttr returns standard and "Header" attributes
		 * in static memory. These must be copied. */
		if (!strcmp(attrname, "Header") || std_attr_p(0, value)) {
		    dp->ar_expansion = strdup(value) ;
		    if (dp->ar_expansion == 0) {
			errno = ENOMEM ;
			ret = 0 ;
		    } else {
			ret = 1 ;
		    }
		} else {
		    dp->ar_expansion = value ;
		    ret = 1 ;
		}
		dp->ar_exp_pos = dp->ar_expansion ;
	    } else {
		ret = 0 ;
	    }
	}
    } else if (!strcmp(attrname, XPON_ATTR)) {
	dp->ar_expand = 1 ;
	ret = 0 ;
    }
    free(attrname) ;		/* This is not as nice as it should 
				 * be, but the caller has no
				 * opportunity to free() attrname. */
    
    return ret ;
}
	


/* Read from a version opened with atOpenExpand(). */
int atReadExpand(desc, bufp, size)
int desc ;			/* descriptor, index into desc_table[] */
char *bufp ;			/* ptr to data area */
int size ;			/* read thus many bytes */
{
    at_read_desc *dp ;		/* descriptor struct */
    char *endattr ;		/* ptr to end of attribute name */
    char *attrname ;		/* ptr to allocated attribute name */
    int wanted = size ;
    char *old_pos ;		/* previous position in version buffer */

    if (desc == 0) {		/* read from stdin */
	return read(0, bufp, size) ;
    }
    dp = desc_table[desc] ;
    if (dp == 0) {		/* check if desc is valid */
	errno = EBADF ;
	return -1 ;
    }
    old_pos = dp->ar_pos ;	/* save position for rollback */

  read_expansion:		/* the name of this label is nearly
				 * enough comment for the following
				 * while loop */

    while (dp->ar_expansion && size > 0) {
	/* there is an expansion, so read it */
	*bufp++ = *dp->ar_exp_pos++ ; size-- ;

	/* at end of attribute value? */
	if (*dp->ar_exp_pos == 0) {
	    /* free expansion */
	    free(dp->ar_expansion) ;
	    dp->ar_exp_pos = 0 ;
	    dp->ar_expansion = 0 ;
	}
    }

    /* read from version contents */
    while (size > 0 && dp->ar_pos < dp->ar_bufend) {
	if (*dp->ar_pos == '$') {
	    /* maybe an attribute citation here */
	    endattr = end_of_attribute_or_not(dp->ar_pos, dp->ar_bufend) ;
	    if (endattr) {
		/* Yes, indeed. Skip to beginning of name. */
		dp->ar_pos += 3 ;
		/* copy attribute name,
		 * gets free()d in retrieve_expansion() */
		attrname = malloc(endattr - dp->ar_pos + 1) ;
		if (attrname == 0) {
		    dp->ar_pos = old_pos ; /* rollback */
		    errno = ENOMEM ;
		    return -1 ;
		}
		memcpy(attrname, dp->ar_pos, endattr - dp->ar_pos) ;
		*(attrname + (endattr - dp->ar_pos)) = 0 ;

		/* retrieve attribute value */
		if (retrieve_expansion(dp, attrname)) {
		    dp->ar_pos = endattr ;
		    if (*dp->ar_pos == '$') {
			dp->ar_pos++ ;
		    }
				   
		    goto read_expansion ; /* If memory serves me right,
					   * this is my first goto statement
					   * in a C program ever. */
		} else {
		    switch (errno) {
		      case 0:
			break ;
		      case ENOMEM:
			dp->ar_pos = old_pos ;
		      default:
			return -1 ;
		    }
		}
		/* inhibit expansion of the following comment with $__xpoff$ */
		/* no expansion, skip back to "$__" */
		/* now we can switch $__xpon$ again */
		dp->ar_pos -= 3 ;
	    }
	}
	
	/* copy a character (yes, finally!) */
	*bufp++ = *dp->ar_pos++ ; size-- ;
    }

    return wanted - size ;
}		

/* EOF */
