/*
** Copyright (C) 2002 TTTech Computertechnik AG. All rights reserved
** Schönbrunnerstraße 7, A--1040 Wien, Austria. office@tttech.com
** 
**
**++
** Name
**    ALF
**
** Purpose
**    Abstract large file 
**
** Revision Dates
**    28-Feb-2002 (SM) First code checkin
**    01-Mar-2002 (SM) clean up trailing empty file chunks in _alf_close
**    01-Mar-2002 (SM) set & test stream->error in lots of places
**    01-Mar-2002 (SM) fix switch() bug in open_chunk
**    01-Mar-2002 (SM) set stream->error in _alf_close just in case someone's
**                     foolish enough to reuse it
**    01-Mar-2002 (SM) get error return of alf_write correct (0, not -1)
**    01-Mar-2002 (SM) call clearerr on underlying FILE* in alf_clearerr
**    05-Mar-2002 (SM) first steps towards a version for Windows
**    08-Mar-2002 (SM) lots of conditional prints related to debugging
**                     w/o a debugger
**    ««revision-date»»···
**--
**
*/

/*

  ALF - abstract large files

  The intent of this package is to create something that behaves like a
  large file even on systems that don't support POSIX large files.  To the
  greatest extent possible, this just mimics the interface provided to work
  with stdio's FILE object.

  Author: Skip Montanaro (skip@pobox.com)
  Date: 2002-01-25

*/

/*
 * the comments containing strings like "@null@" are understood by the
 * splint program.  check out http://www.splint.org/ for more info.
 */

/*
 * the peculiar macros like ENTER, RETURN, PATH_FAIL, and MARK_TERRITORY
 * macros are there because during early development I only had remote
 * access to MSVC on Win32 via a "command server" - consequently all
 * debugging was done post-mortem and chains of call/return output were very
 * useful in figuring out how the program had gotten into the weeds.
 */

/* $Id: alf.c,v 1.5 2002/04/25 21:07:15 montanaro Exp $ */
/* $Source: /cvsroot/largefiles/alf/src/alf.c,v $ */

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>

#define DIM(a) (sizeof(a)/sizeof((a)[0]))

#ifdef DEBUG_NO_DEBUGGER
#define PATH_FAIL(msg, path, errno) do { fprintf(stderr, "   "); fprintf(stderr, msg, path, errno) ; } while (0)
#define PRINT_PATH(path) do { fprintf(stderr, "   %s\n", path) ; } while (0)
#else
#define PATH_FAIL(msg, path, errno)
#define PRINT_PATH(path)
#endif

#ifdef MS_WIN32
#define REOPEN_ON_WRITE
#define SEP "\\"
#include <io.h>
#define STRUCT_STAT struct _stat
#define STAT _stat
#define FSTAT _fstat
#define uid_t short
#define gid_t short
#define MKDIR(path,mode) mkdir(path)
#define L "I64"
#define STDERR stdout
#else
#define SEP "/"
#include <unistd.h>
#define STRUCT_STAT struct stat
#define STAT stat
#define FSTAT fstat
#define MKDIR(path,mode) mkdir(path,mode)
#define L "L"
#define STDERR stderr
#endif

#include <errno.h>
#include <assert.h>
#include "alf.h"

#ifdef DMALLOC
#include "dmalloc.h"
#endif

/*
 * odd file name is simply an attempt to keep from finding directories
 * which coincidentally have a filename that matches our index file.
 * at this point nothing actually resides in the file - it's just a marker.
 */
#define INDEX_FILE "_indexQz.dd6"

/*
 * a long long can hold a 16 hex digit number - filename indicates the
 * offset into the file
 */
#define CHUNK_FILE_PAT "%016Lx.chk"
#define CHUNK_FILE_NAMLEN 20

static int _alf_stat(ALF *stream, unsigned int chunk,
                    /*@out@*/ STRUCT_STAT *buf);

/* no checking! */
#define CHUNK_OFFSET(stream,n)  ((stream)->chunks[n].offset)
#define CHUNK_PATH(stream,n)    ((stream)->chunks[n].path)
#define CURRENT_OFFSET(stream)  CHUNK_OFFSET((stream), (stream)->current_chunk)
#define CURRENT_FILE(stream)    CHUNK_PATH((stream), (stream)->current_chunk)
#define NEXT_OFFSET(stream) \
        ((stream)->current_chunk==(stream)->nchunks-1 \
           ? (CURRENT_OFFSET(stream)+LONG32_MAX) \
           : (CHUNK_OFFSET((stream),(stream)->current_chunk+1)))
#define LAST_OFFSET(stream)     CHUNK_OFFSET(stream, stream->nchunks-1)
#define ALF_IS_WRITABLE(s) ((s)->mode[0] == 'w' || \
                            (s)->mode[0] == 'a' || \
                            ((s)->mode[0] == 'r' && \
                             ((s)->mode[1] == '+' || \
                              ((s)->mode[1] == 'b' && \
                               (s)->mode[2] == '+'))))
#define ALF_IS_READABLE(s) ((s)->mode[0] == 'r' || \
                            (((s)->mode[0] == 'a' || (s)->mode[0] == 'w') && \
                             ((s)->mode[1] == '+' || \
                              ((s)->mode[1] == 'b' && \
                               (s)->mode[2] == '+'))))

#define ALF_DEBUG_CHECK 0x01
#define ALF_DEBUG_PRINT 0x02
#define ALF_DEBUG_TRACE_CALLS 0x04
#define ALF_DEBUG_TRACE_STDIO_CALLS 0x08

#ifdef ALF_DEBUG

#define ASSERT(expr) ((void)((expr) ? 0 : (_fail (#expr, __FILE__, line), 0)))

#ifdef ALF_FATAL
/* just a hook we can catch with a debugger */
/* "exit" is sometimes a macro, so don't know what to set a breakpoint in */
static void
_myexit(int n) {
    exit(n);
}
#endif

static void
_fail(const char *__assertion, const char *__file, unsigned int __line)
{
    fprintf(STDERR, "  %s:%d: Assertion `%s' failed.\n", __file, __line,
            __assertion);
#ifdef ALF_FATAL
    _myexit (1);
#endif
}

static unsigned int hook_count = 0;
static void
_check(unsigned int v, ALF *s, int line) {
    unsigned int i,j;
    STRUCT_STAT buf;
    unsigned LONG_LONG last;

#if (ALF_DEBUG) & ALF_DEBUG_PRINT
    fprintf(STDERR, "-- %3u -- %5d ------------------\n", v, line);
#endif

    ASSERT(s != NULL);
    ASSERT(s->current != NULL);
    ASSERT(errno == 0);
    ASSERT(!ferror(s->current));

#if (ALF_DEBUG) & ALF_DEBUG_PRINT
    fprintf(STDERR, "current file: %s\n", CURRENT_FILE(s));
    fprintf(STDERR, "current offset: %" L "u\n", CURRENT_OFFSET(s));
    fprintf(STDERR, "next offset: %" L "u\n", NEXT_OFFSET(s));
    fprintf(STDERR, "number of chunks: %u\n", s->nchunks);
    fprintf(STDERR, "current chunk: %u\n", s->current_chunk);
    fprintf(STDERR, "position: %" L "u\n", s->position);
    fprintf(STDERR, "current file position: %ld\n", ftell(s->current));
    fprintf(STDERR, "chunks:\n");
#endif

    last = 0;
    for (i=0; i<s->nchunks; i++) {
        if (_alf_stat(s, i, &buf) == -1)
            fprintf(STDERR, "error stating %s\n", CHUNK_PATH(s, i));
        else {
#if (ALF_DEBUG) & ALF_DEBUG_PRINT
            fprintf(STDERR, "  %d: %s (%" L "u -> %" L "u)\n",
                    i, CHUNK_PATH(s, i), CHUNK_OFFSET(s, i),
                    s->chunks[i].offset+buf.st_size);
#endif
            for (j=0;j<DIM(s->chunks[i].pad1);j++) {
                ASSERT(s->chunks[i].pad1[j] == (LONG_LONG)0);
                ASSERT(s->chunks[i].pad2[j] == (LONG_LONG)0);
            }
            ASSERT(CHUNK_OFFSET(s, i) >= last);
            last = CHUNK_OFFSET(s, i)+buf.st_size;
        }
    }

    /* length of the file must be the offset + length of last non-zero
       length chunk - we may have seeked beyond that point but not
       written anything. */
    last = 0;
    for (i=0; i<s->nchunks; i++) {
        if (_alf_stat(s, i, &buf) == -1)
            fprintf(STDERR, "error stat()ing %s\n", CHUNK_PATH(s, i));
        else {
            if (buf.st_size > 0)
                last = CHUNK_OFFSET(s, i)+buf.st_size;
        }
    }
    /* bad assertion? */
    /* ASSERT((s)->length == last); */

    ASSERT((s)->current_chunk >= 0);
    ASSERT((s)->current_chunk < (s)->nchunks);
    ASSERT((s)->position < NEXT_OFFSET(s));
}

static void
_mark(char *s, unsigned int nbytes, unsigned int line)
{
    while (nbytes >= 7) {
        sprintf(s, "<%04d>", line);
        nbytes -= 6;
        s += 6;
    }
}

#define CHECK_STREAM_CONSISTENCY(s) _check(hook_count++,s,__LINE__)
#define MARK_TERRITORY(s,nbytes,line) _mark((char *)s,nbytes,line)

#define CLEAR_CHUNK_PADS(stream,n)  \
    do {\
        unsigned int j; \
        for(j=0;j<DIM((stream)->chunks[n].pad1);j++) \
            (stream)->chunks[n].pad1[j] = \
            (stream)->chunks[n].pad2[j] = \
            (LONG_LONG)0; \
    } while (0)


#if (ALF_DEBUG) & (ALF_DEBUG_TRACE_CALLS|ALF_DEBUG_TRACE_STDIO_CALLS)
static unsigned int _funclevel = 0;
#define ENTER do { fprintf(STDERR, "%*s%s>%s\n", _funclevel, "", (_funclevel==0?"\n":""), (_FUNC)); _funclevel++; } while (0)
#define RETURN(val) do { fprintf(STDERR, "%*s<%s\n", --_funclevel, "", (_FUNC)); return (val); } while (0)
#define RETURNVOID do { fprintf(STDERR, "%*s<%s\n", --_funclevel, "", (_FUNC)); return; } while (0)
#else
#define ENTER
#define RETURN(val) return (val)
#define RETURNVOID return
#endif

    /* stdio function tracing for MS where I have no debugging facility... */
    /* this should get ripped out eventually */
#if (ALF_DEBUG) & ALF_DEBUG_TRACE_STDIO_CALLS

static FILE *
_ffunc_fopen(const char *f, const char *m, FILE *_s, int _lvl)
{
  FILE *p;
  fprintf(_s, "%*s:%s(%s,%s) ", _lvl, "", "fopen", f, m);
  p = fopen(f, m);
  fprintf(_s, "== %p, %d\n", p, errno);
  return p;
}

static char *
_escape(unsigned char c) {
  static unsigned char s[100];
  if (c <= ' ' || c > '~')
    sprintf(s, "\\x%02x", c);
  else
    sprintf(s, "%c", c);
  return s;
}

static size_t
_ffunc_fwrite(void *p, size_t s, size_t n, FILE *f, FILE *_s, int _lvl)
{
  size_t rslt;
  unsigned int i;
  fprintf(_s, "%*s:%s(%p,%d,%d,%p) ", _lvl, "", "fwrite",p,s,n,f);
  rslt = fwrite(p,s,n,f);
  fprintf(_s, "== %d, %d\n", rslt, errno);
  fprintf(_s, "%*s:  <", _lvl, "");
  for (i=0; i<s*n; i++)
    fprintf(_s, "%s", _escape(((char *)p)[i]));
  fprintf(_s, ">\n");
  return rslt;
}

static size_t
_ffunc_fread(void *p, size_t s, size_t n, FILE *f, FILE *_s, int _lvl)
{
  size_t rslt;
  fprintf(_s, "%*s:%s(%p,%d,%d,%p) ", _lvl, "", "fread",p,s,n,f);
  rslt = fread(p,s,n,f);
  fprintf(_s, "== %d, %d\n", rslt, errno);
  return rslt;
}

static int
_ffunc_fflush(FILE *f, FILE *_s, int _lvl)
{
  int rslt;
  fprintf(_s, "%*s:%s(%p) ", _lvl, "", "fflush",f);
  rslt = fflush(f);
  fprintf(_s, "== %d, %d\n", rslt, errno);
  return rslt;
}

static int
_ffunc_fileno(FILE *f, FILE *_s, int _lvl)
{
  int rslt;
  fprintf(_s, "%*s:%s(%p) ", _lvl, "", "fileno",f);
  rslt = fileno(f);
  fprintf(_s, "== %d, %d\n", rslt, errno);
  return rslt;
}

static int
_ffunc_fclose(FILE *f, FILE *_s, int _lvl)
{
  int rslt;
  fprintf(_s, "%*s:%s(%p) ", _lvl, "", "fclose",f);
  rslt = fclose(f);
  fprintf(_s, "== %d, %d\n", rslt, errno);
  return rslt;
}

static int
_ffunc_feof(FILE *f, FILE *_s, int _lvl)
{
  int rslt;
  fprintf(_s, "%*s:%s(%p) ", _lvl, "", "feof",f);
  rslt = feof(f);
  fprintf(_s, "== %d, %d\n", rslt, errno);
  return rslt;
}

static void
_ffunc_rewind(FILE *f, FILE *_s, int _lvl)
{
  fprintf(_s, "%*s:%s(%p) ", _lvl, "", "rewind",f);
  rewind(f);
  fprintf(_s, "== void, %d\n", errno);
  return;
}

static int
_ffunc_fseek(FILE *f, long offset, int whence, FILE *_s, int _lvl)
{
  int rslt;
  fprintf(_s, "%*s:%s(%p,%ld,%d) ", _lvl, "", "fseek",f,offset,whence);
  rslt = fseek(f, offset, whence);
  fprintf(_s, "== %d, %d\n", rslt, errno);
  return rslt;
}

static long
_ffunc_ftell(FILE *f, FILE *_s, int _lvl)
{
  long rslt;
  fprintf(_s, "%*s:%s(%p) ", _lvl, "", "ftell",f);
  rslt = ftell(f);
  fprintf(_s, "== %ld, %d\n", rslt, errno);
  return rslt;
}

#ifndef MS_WIN32
static int
_ffunc_ftruncate(int fd, off_t offset, FILE *_s, int _lvl)
{
    int rslt;
    fprintf(_s, "%*s:%s(%d,%ld) ", _lvl, "", "ftruncte",fd,offset);
    rslt = ftruncate(fd, offset);
    fprintf(_s, "== %d, %d\n", rslt, errno);
    return rslt;
}

static int
_ffunc_truncate(const char *f, off_t offset, FILE *_s, int _lvl)
{
    int rslt;
    fprintf(_s, "%*s:%s(%s,%ld) ", _lvl, "", "ftruncte",f,offset);
    rslt = truncate(f, offset);
    fprintf(_s, "== %d, %d\n", rslt, errno);
    return rslt;
}

#define fopen(p,m) (_ffunc_fopen(p,m,STDERR,_funclevel))
#define fwrite(p,s,n,f) (_ffunc_fwrite(p,s,n,f,STDERR,_funclevel))
#define fread(p,s,n,f) (_ffunc_fread(p,s,n,f,STDERR,_funclevel))
#define fflush(f) (_ffunc_fflush(f,STDERR,_funclevel))
#define fileno(f) (_ffunc_fileno(f,STDERR,_funclevel))
#define fclose(f) (_ffunc_fclose(f,STDERR,_funclevel))
#define feof(f) (_ffunc_feof(f,STDERR,_funclevel))
#define rewind(f) (_ffunc_rewind(f,STDERR,_funclevel))
#define fseek(f,p,w) (_ffunc_fseek(f,p,w,STDERR,_funclevel))
#define ftell(f) (_ffunc_ftell(f,STDERR,_funclevel))
#ifndef MS_WIN32
#define ftruncate(f,l) (_ffunc_ftruncate(f,l,STDERR,_funclevel))
#define truncate(f,l) (_ffunc_truncate(f,l,STDERR,_funclevel))
#endif

#endif

#endif /* ! (ALF_DEBUG) & ALF_DEBUG_TRACE_STDIO_CALLS */

#else

#define CHECK_STREAM_CONSISTENCY(s)
#define MARK_TERRITORY(s,nbytes,line)
#define CLEAR_CHUNK_PADS(stream,n)
#define ENTER
#define RETURN(val) return (val)
#define RETURNVOID return

#endif /* ifndef ALF_DEBUG */

/* create a full path by joining dir, SEP and filename */
/*@null@*/
static char *
file_path(const char *dir, const char *filename)
{
    char *s;

    s = malloc(strlen(dir)+strlen(filename)+2);
    if (!s) {
        errno = ENOMEM;
        return NULL;
    }
    (void)strcpy(s, dir);
    (void)strcat(s, SEP);
    (void)strcat(s, filename);
    return s;
}

static int
_alf_stat(ALF *stream, unsigned int chunk, /*@out@*/ STRUCT_STAT *buf) {
    if (stream->current && ALF_IS_WRITABLE(stream) && alf_flush(stream) == -1) {
        fprintf(stderr, "flush on stream failed\n");
        stream->error = 1;
        return -1;
    }
    if (chunk == stream->current_chunk && stream->current != NULL)
        return FSTAT(fileno(stream->current), buf);
    else
        return STAT(CHUNK_PATH(stream, chunk), buf);
}

/*@null@*/
static char *
index_file_path(const char *path)
{
    return file_path(path, INDEX_FILE);
}

/*@null@*/
static char *
chunk_file_path(const char *path, unsigned LONG_LONG offset)
{
    char *s;
    char *t;

    s = malloc(CHUNK_FILE_NAMLEN+1);
    if (!s) {
        errno = ENOMEM;
        return NULL;
    }
    sprintf(s, CHUNK_FILE_PAT, offset);
    t = file_path(path, s);
    MARK_TERRITORY(s, strlen(s), __LINE__);
    free(s);
    return t;
}

static int
cmp_ALF_chunk (const void *p1, const void *p2)
{
    unsigned LONG_LONG o1 = ((ALF_chunk *)p1)->offset;
    unsigned LONG_LONG o2 = ((ALF_chunk *)p2)->offset;

    if (o1 == o2) return 0;
    if (o1 > o2) return 1;
    return -1;
}

/*
 * insert a new file chunk in stream->chunks, that starts at offset
 */
static int
insert_new_chunk(ALF *stream, unsigned LONG_LONG offset)
{
    FILE *newf;

    /* extend offsets list */
    stream->chunks = realloc(stream->chunks,
                             (stream->nchunks+1)*sizeof(ALF_chunk));
    if (stream->chunks == NULL) {
        stream->error = 1;
        return -1;
    }

    CLEAR_CHUNK_PADS(stream,stream->nchunks);
    CHUNK_OFFSET(stream,stream->nchunks) = offset;
    CHUNK_PATH(stream, stream->nchunks) = chunk_file_path(stream->path,
                                                          offset);
    if (CHUNK_PATH(stream, stream->nchunks) == NULL) {
        stream->error = 1;
        return -1;
    }

    
    /* create a new file for this chunk */
    newf = fopen(CHUNK_PATH(stream, stream->nchunks), "wb");
    if (newf == NULL || fclose(newf) == EOF) {
        PATH_FAIL("create %s failed: %d.\n",
                  CHUNK_PATH(stream, stream->nchunks), errno);
        stream->error = 1;
        return -1;
    }

    stream->nchunks++;

    qsort (stream->chunks, (size_t)stream->nchunks, sizeof(ALF_chunk),
           cmp_ALF_chunk);
    
    return 0;
}

static int
create_path(const char *path)
{
    FILE *fp;
    char *ifpath;

    if (MKDIR(path, 0755) == -1) {
        PATH_FAIL("mkdir %s failed: %d.\n", path, errno);
        return -1;
    }
    ifpath = index_file_path(path);
    if (ifpath == NULL)
        return -1;
    fp = fopen(ifpath, "wb");
    if (fp == NULL || fclose(fp) == -1) {
        PATH_FAIL("open %s for write failed: %d.\n", ifpath, errno);
        MARK_TERRITORY(ifpath, strlen(ifpath), __LINE__);
        free(ifpath);
        return -1;
    }
    MARK_TERRITORY(ifpath, strlen(ifpath), __LINE__);
    free(ifpath);
    return 0;
}

static int
read_permission_check (const char *dir)
{
    char *s;
    FILE *f;

    s = index_file_path(dir);
    if (s == NULL)
        return -1;
    f = fopen(s, "r");
    if (f == NULL) {
        /* nonexistent or unreadable file - errno was set by fopen */
        PATH_FAIL("read check %s failed: %d.\n", dir, errno);
        MARK_TERRITORY(s, strlen(s), __LINE__);
        free(s);
        return -1;
    }
    MARK_TERRITORY(s, strlen(s), __LINE__);
    free(s);
    if (fclose(f) == -1) {
        PATH_FAIL("close of %s failed: %d.\n", dir, errno);
        return -1;
    }

    return 0;
}
    
static int
write_permission_check (const char *dir)
{
    STRUCT_STAT buf;
    char *s;
    FILE *f;
    int result;

    errno = 0;
    /* ignore return value - errno value gives all the info we need */
    (void)STAT(dir, &buf);
    switch (errno) {
    case 0:
        /* dir exists */
        if (!S_IFDIR & buf.st_mode) {
            /* dir is a directory - does it have an INDEX_FILE? */
            s = index_file_path(dir);
            if (s == NULL)
                return -1;
            if (STAT(s, &buf) == -1) {
                /* no index file found - plain old directory */
                PATH_FAIL("index file check %s failed: %d.\n", s, errno);
                errno = EBADF;
                MARK_TERRITORY(s, strlen(s), __LINE__);
                free(s);
                return -1;
            }
            MARK_TERRITORY(s, strlen(s), __LINE__);
            free(s);
        }
        break;

    case ENOENT:
        if (create_path(dir) == -1)
            /* failed to create necessary structure */
            return -1;
        return 0;
            
    default:
        /* stat failed for some other, unrecoverable, reason */
        PATH_FAIL("stat %s failed: %d.\n", dir, errno);
        return -1;
    }

    /* make sure we can create a file in dir */
    s = malloc(strlen(dir)+strlen(SEP)+strlen("dummy")+1);
    strcpy(s, dir);
    strcat(s, SEP);
    strcat(s, "dummy");
    f = fopen(s, "w");
    result = 0;
    if (f != NULL) {
        fclose(f);
        if (unlink(s) == -1) {
            PATH_FAIL("unlink after write check %s failed: %d.\n", s, errno);
            result = -1;
        }
    }
    else {
        PATH_FAIL("write check %s failed: %d.\n", s, errno);
        result = -1;
    }
    MARK_TERRITORY(s, strlen(s), __LINE__);
    free(s);

    return result;
}

static int
permission_check (const char *dir, const char *mode)
{
    switch (mode[0]) {
    case 'r':
        return read_permission_check(dir);

    case 'w':
    case 'a':
        return write_permission_check(dir);

    default:
        errno = EINVAL;
        return -1;
    }
}

/* load the offsets and compute the file's length */
static int
load_index (ALF *stream)
{
    unsigned int i,n;

#ifdef MS_WIN32
    HANDLE hFindFile;
    WIN32_FIND_DATA FileData;
    char *glob = NULL;
#else
    struct dirent *ep;
    DIR *basedir;
#endif

    errno = 0;
    stream->error = 0;
    stream->nchunks = 0;

    /* how many entries are in the directory? */
    /*@noeffect@*/
    n = 0;
#ifdef MS_WIN32
    glob = malloc(strlen(stream->path)+strlen("\\*.chk")+1);
    if (glob == NULL)
        goto fini;
    strcpy(glob, stream->path);
    strcat(glob, "\\*.chk");
    hFindFile = FindFirstFile(glob, &FileData);
    if (hFindFile != INVALID_HANDLE_VALUE) {
        do {
            n++;
        } while (FindNextFile(hFindFile, &FileData) == TRUE);
        if (!FindClose(hFindFile)) {
            errno = GetLastError();
            PATH_FAIL("handle close of %s failed: %d.\n", glob, errno);
            goto fini;
        }
    }
#else
    basedir = opendir(stream->path);
    if (basedir == NULL)
        goto fini;
    rewinddir(basedir);
    while ((ep = readdir(basedir)) != NULL)
        if (ep->d_name[0] != '_' && ep->d_name[0] != '.')
            n++;
#endif

    stream->nchunks = n;
    if (n == 0) {
        if (insert_new_chunk(stream, 0) == -1)
            goto fini;
    }
    else {
        stream->chunks = realloc(stream->chunks, n * sizeof(ALF_chunk));
        if (stream->chunks == NULL) {
            stream->nchunks = 0;
            stream->error = 1;
            errno = ENOMEM;
            goto fini;
        }
        for (i=0;i<n;i++) {
            CLEAR_CHUNK_PADS(stream, i);
            CHUNK_OFFSET(stream, i) = 0;
            CHUNK_PATH(stream, i) = NULL;
        }
    }

    /* second pass - process 'em */
    /*@noeffect@*/
    n = 0;
#ifdef MS_WIN32
    hFindFile = FindFirstFile(glob, &FileData);
    if (hFindFile != INVALID_HANDLE_VALUE) {
        do {
            if (sscanf(FileData.cFileName, CHUNK_FILE_PAT,
                       &CHUNK_OFFSET(stream, n)) != 1) {
                fprintf(STDERR, "** sscanf failed.\n");
                stream->error = 1;
                errno = EINVAL;
                goto fini;
            }
            CHUNK_PATH(stream, n) = chunk_file_path(stream->path,
                                                    CHUNK_OFFSET(stream, n));
            n++;
        } while (FindNextFile(hFindFile, &FileData) == TRUE);
        if (!FindClose(hFindFile)) {
            errno = GetLastError();
            PATH_FAIL("handle close of %s failed: %d.\n", glob, errno);
            goto fini;
        }
    }
#else
    rewinddir(basedir);
    while ((ep = readdir(basedir)) != NULL)
        if (ep->d_name[0] != INDEX_FILE[0] && ep->d_name[0] != '.') {
            if (sscanf(ep->d_name, CHUNK_FILE_PAT, &CHUNK_OFFSET(stream, n))
                       != 1) {
                stream->error = 1;
                errno = EINVAL;
                goto fini;
            }

            CHUNK_PATH(stream, n) = chunk_file_path(stream->path,
                                                    CHUNK_OFFSET(stream, n));
            n++;
        }
    closedir(basedir);
#endif

    qsort (stream->chunks, (size_t)stream->nchunks, sizeof(ALF_chunk),
           cmp_ALF_chunk);

    /* compute file's length */
    stream->length = 0;
    if (stream->nchunks != 0) {
        STRUCT_STAT buf;
        if (_alf_stat(stream, stream->nchunks-1, &buf) == -1)
            goto fini;
        stream->length = LAST_OFFSET(stream) + buf.st_size;
    }

 fini:
#ifdef MS_WIN32
    if (glob != NULL) {
        MARK_TERRITORY(glob, strlen(glob), __LINE__);
        free(glob);
    }
#endif
    return (errno != 0) ? -1 : 0;
}

static int
open_chunk(ALF *stream, unsigned int which)
{
    ALF_chunk *next;
    STRUCT_STAT buf;
    char *mode;

    if (stream->current != NULL)
        if (fclose(stream->current) == -1) {
            PATH_FAIL("close of %s failed: %d.\n", CURRENT_FILE(stream), errno);
            stream->error = 1;
            return -1;
        }
    stream->current = NULL;
    stream->eof = 0;

    stream->current_chunk = which;
    next = &(stream->chunks[which]);

    switch (stream->mode[0]) {
    case 'r':
    case 'a':
        mode = stream->mode;
        break;
    default:
        mode = "rb+";
        break;
    }

    stream->current = fopen(next->path, mode);
    if (stream->current == NULL) {
        PATH_FAIL("open %s failed: %d.\n", next->path, errno);
        stream->error = 1;
        return -1;
    }
    if (FSTAT(fileno(stream->current), &buf) == -1) {
        (void)fclose(stream->current);
        PATH_FAIL("close %s failed: %d.\n", CURRENT_FILE(stream), errno);
        stream->current = NULL;
        stream->error = 1;
        return -1;
    }
    return 0;
}

/* check context - fail if user tried to open invalid file */
/* opening an existing non-ALF file for read is an error */
/* opening a directory that doesn't look like it has ALF contents is
   also an error */
/* opening an existing non-ALF file for write or append is *not*
   an error - it just gets silently truncated or migrated into the new
   ALF directory */

/*
 * possible modes - from fopen man page
 *
 * r  - open for reading 
 * w  - open for writing (file may be created, file pointer
 *      positioned at start, file truncated)
 * a  - open for writing (file may be created, file pointer
 *      positioned at end, file is not truncated)
 * r+ - open for reading and writing (file must exist) - TBD
 * w+ - open for reading and writing (file may be created, file pointer
 *      positioned at start, file truncated) - TBD
 * a+ - open for reading and writing (file may be created, file pointer
 *      positioned at end, file is not truncated) - TBD
 */

/*
 * possible combinations of modes and paths
 *
 * mode == r, file does not exist - error (ENOENT)
 * mode == r, file exists but is non-ALF - error (EBADF)
 * mode == r, file is ALF - success depends on access permissions
 * mode == w, file is non-ALF - depending on access permissions, file
              is deleted and recreated as ALF
 * mode == w, file is ALF - success depends on access permissions
 * mode == a, file is non-ALF - depending on access permissions, file
              is converted to ALF
 * mode == a, file is ALF - success depends on access permissions
 * any other mode - error (EINVAL)
 */

ALF *
alf_open (const char *path, const char *mode,
          /*@unused@*/ unsigned int use_large)
{
#define _FUNC "alf_open"
    ALF *stream = NULL;

    ENTER;

    if (permission_check(path, mode) == -1)
        /* errno was set already */
        RETURN(NULL);

    stream = calloc(1, sizeof(ALF));
    if (stream == NULL)
        RETURN(NULL);

    stream->eof = 0;
    stream->error = 1;
    stream->_needs_flush = 0;
#ifdef REOPEN_ON_WRITE
    stream->_reopen_on_write = 0;
#endif

    stream->mode = strdup(mode);
    if (stream->mode == NULL)
        goto error;
    stream->path = strdup(path);
    if (stream->path == NULL)
        goto error;
    /* malloc one just to have something */
    stream->chunks = calloc(1, sizeof(ALF_chunk));
    if (stream->chunks == NULL)
        goto error;
    CHUNK_OFFSET(stream, 0) = 0;
    CHUNK_PATH(stream, 0) = NULL;
    stream->position = 0;

    if (load_index(stream) == -1 ||
        open_chunk(stream, 0) == -1)
        goto error;

    switch (mode[0]) {
    case 'r':
        alf_rewind(stream);
        break;
    case 'a':
        if (alf_seek(stream, 0, SEEK_END) == -1)
            goto error;
        break;
    case 'w':
        if (alf_truncate(stream, 0) == -1)
            goto error;
        break;
    }
        
    RETURN(stream);

 error:
    if (stream->path != NULL) {
        MARK_TERRITORY(stream->path, strlen(stream->path), __LINE__);
        free(stream->path);
    }
    if (stream->mode != NULL) {
        MARK_TERRITORY(stream->mode, strlen(stream->mode), __LINE__);
        free(stream->mode);
    }
    if (stream->chunks != NULL) {
        MARK_TERRITORY(stream->chunks, stream->nchunks*sizeof(ALF_chunk),
                       __LINE__);
        free(stream->chunks);
    }
    MARK_TERRITORY(stream, sizeof(ALF), __LINE__);
    free(stream);
    RETURN(NULL);
#undef _FUNC
}

static void
_cleanup_empty_chunks(ALF *stream)
{
    STRUCT_STAT buf;
    unsigned int i;
    
    for (i=stream->nchunks-1;i>0;i--) {
        if (STAT(CHUNK_PATH(stream, i), &buf) == -1 ||
            (buf.st_size == 0 && unlink(CHUNK_PATH(stream, i)) == -1))
            return;
    }
}

static int
_alf_close (ALF *stream)
{
    unsigned int i;
    int n;

    n = fclose(stream->current);
    stream->current = NULL;

    _cleanup_empty_chunks(stream);

    for (i=0;i<stream->nchunks;i++) {
        MARK_TERRITORY(CHUNK_PATH(stream, i), strlen(CHUNK_PATH(stream, i)),
                       __LINE__);
        free(CHUNK_PATH(stream, i));
    }
    MARK_TERRITORY(stream->chunks, stream->nchunks*sizeof(ALF_chunk), __LINE__);
    free(stream->chunks);
    stream->chunks = NULL;

    MARK_TERRITORY(stream->path, strlen(stream->path), __LINE__);
    free(stream->path);
    stream->path = NULL;

    MARK_TERRITORY(stream->mode, strlen(stream->mode), __LINE__);
    free(stream->mode);
    stream->mode = NULL;

    stream->length = 0;
    stream->nchunks = 0;

    /* just in case someone tries to use it after the close... */
    stream->error = stream->eof = 1;

    return n;
#undef _FUNC
}

int
alf_close(ALF *stream)
{
#define _FUNC "alf_close"
    int n;

    ENTER;
    n = _alf_close(stream);
    MARK_TERRITORY(stream, sizeof(ALF), __LINE__);
    free(stream);
    RETURN(n);
#undef _FUNC
}

ALF *
alf_reopen (const char *path, const char *mode, unsigned int use_large,
            ALF *stream)
{
#define _FUNC "alf_reopen"

    ALF *new_stream;

    ENTER;

    if (stream->error == 1)
        RETURN(NULL);

    CHECK_STREAM_CONSISTENCY(stream);

    if (_alf_close(stream) == -1)
        RETURN(NULL);

    new_stream = alf_open (path, mode, use_large);
    if (new_stream == NULL) {
        MARK_TERRITORY(stream, sizeof(ALF), __LINE__);
        free(stream);
        RETURN(NULL);
    }

    stream->mode = new_stream->mode;
    stream->path = new_stream->path;
    stream->position = new_stream->position;
    stream->length = new_stream->length;
    stream->current = new_stream->current;
    stream->nchunks = new_stream->nchunks;
    stream->current_chunk = new_stream->current_chunk;
    stream->chunks = new_stream->chunks;
    stream->error = new_stream->error;
    stream->eof = new_stream->eof;

    MARK_TERRITORY(new_stream, sizeof(ALF), __LINE__);
    free(new_stream);

    RETURN(stream);
#undef _FUNC
}

size_t
alf_read (void *ptr, size_t size, size_t nmemb, ALF *stream)
{
#define _FUNC "alf_read"
    size_t nbytes;
    size_t needbytes;

    ENTER;

    if (!ALF_IS_READABLE(stream) ||
        stream->current == NULL ||
        stream->error == 1) {
        stream->error = 1;
        RETURN(0);
    }

    CHECK_STREAM_CONSISTENCY(stream);

#ifdef REOPEN_ON_WRITE
    if (stream->mode[1] == '+' || stream->mode[2] == '+')
        stream->_reopen_on_write = 1;
#endif

    /* try to read all the bytes from the current file - if that is
       incomplete, consider what our options are */
    /* change the call slightly because we want to suck every byte possible
       from the file, not just a multiple of the object size - it's not clear
       if the read will go clear to the end of the file or not */
    /* XXX - need to break this down into chunks that read <= whatever can
       be held in a size_t */
    /* fprintf(STDERR, "alf_read: start @ %" L "d,", stream->position); */
    nbytes = fread(ptr, 1, size*nmemb, stream->current);
    stream->position += nbytes;
    /* fprintf(STDERR, "    wanted %d bytes, read %d bytes, now at %" L "d\n",
       size*nmemb, nbytes, stream->position); */
    if (nbytes == size*nmemb) {
        CHECK_STREAM_CONSISTENCY(stream);
        RETURN(nmemb);
    }

    /* case 1 - at end of last chunk ==> we read to the true EOF */
    if (stream->current_chunk == stream->nchunks-1) {
        /* fprintf(STDERR, "alf_read: case 1\n"); */
        stream->eof = feof(stream->current);
        CHECK_STREAM_CONSISTENCY(stream);
        RETURN(nbytes/size);
    }

    /* still have this much to read */
    needbytes = size*nmemb-nbytes;

    /* case 2 - position+needbytes < offset of next file => remainder of this
       file is a hole - pad w/ NULs */
    if (stream->position+needbytes < NEXT_OFFSET(stream)) {
        /* fprintf(STDERR, "alf_read: case 2a\n"); */
        (void)memset ((void *)((char *)ptr+nbytes), 0, needbytes);
        stream->position += needbytes;
        CHECK_STREAM_CONSISTENCY(stream);
        RETURN(nmemb);
    }
    else {
        /* pad w/ zeroes until the start of the next file */
        size_t gap = (size_t)(NEXT_OFFSET(stream)-stream->position);
        /* fprintf(STDERR, "alf_read: case 2b\n"); */
        (void)memset ((void *)((char *)ptr+nbytes), 0, gap);
        nbytes += gap;
        needbytes -= gap;
        stream->position += gap;
        /* fall thru to next case */
    }

    /* case 3 - position+nbytes overlaps w/ start of next file -
       advance current file and try to read the rest */
    if (open_chunk(stream, stream->current_chunk+1) == -1) {
        /* fprintf(STDERR, "alf_read: case 3\n"); */
        /* something bad happened... errno will already be set */
        RETURN(nbytes/size);
    }

    /* successfully advanced to the next file - try reading the rest from
       there */
    nbytes += alf_read ((void *)((char *)ptr+nbytes), 1, needbytes, stream);
    if (nbytes == size*nmemb) {
        CHECK_STREAM_CONSISTENCY(stream);
        RETURN(nmemb);
    }

    CHECK_STREAM_CONSISTENCY(stream);

    RETURN(nbytes/size);
#undef _FUNC
}

static int
expand_chunks_to(ALF *stream, unsigned LONG_LONG offset)
{
    unsigned int i;
    STRUCT_STAT buf;
    unsigned LONG_LONG chunk_offset;

    for (i=0; i < stream->nchunks; i++) {
        chunk_offset = CHUNK_OFFSET(stream, i);
        if (chunk_offset > offset)
            break;
        if (_alf_stat(stream, i, &buf) == -1)
            return -1;
        if (offset < chunk_offset + buf.st_size) {
            return (int)i;
        }
        if (offset == chunk_offset + buf.st_size) {
            /* last chunk? */
            if (i == stream->nchunks-1) {
                return (int)i;
            }
            /* no, peek ahead and look for a match */
            if (CHUNK_OFFSET(stream, i+1) == offset) {
                return (int)(i+1);
            }
            /* gap between i and i+1 - want to write there */
            CHECK_STREAM_CONSISTENCY(stream);
            return (int)i;
        }
    }
    if (insert_new_chunk(stream, offset) == -1)
        return -1;

    CHECK_STREAM_CONSISTENCY(stream);

    return (int)i;
}

size_t
alf_write (const void *ptr, size_t size, size_t nmemb, ALF *stream)
{
#define _FUNC "alf_write"

    size_t nbytes;

    ENTER;

    CHECK_STREAM_CONSISTENCY(stream);

    /* not an error to write an empty string? */
    if (size*nmemb == 0)
        RETURN(0);

    if (!ALF_IS_WRITABLE(stream) ||
        stream->current == NULL ||
        stream->error == 1) {
        stream->error = 1;
        RETURN(0);
    }

#ifdef REOPEN_ON_WRITE
    if (stream->_reopen_on_write) {
        long where = ftell(stream->current);
        stream->current = freopen(CURRENT_FILE(stream), stream->mode,
                                  stream->current);
        fseek(stream->current, where, SEEK_SET);
        stream->_reopen_on_write = 0;
    }
#endif

    /* force proper append behavior */
    if (stream->mode[0] == 'a')
        alf_seek(stream, 0, SEEK_END);

    CHECK_STREAM_CONSISTENCY(stream);
    /* if it all fits, a single fwrite will do */
    if (stream->position+size*nmemb < NEXT_OFFSET(stream)) {
        stream->_needs_flush = 1;
        if ((nbytes = fwrite(ptr, size, nmemb, stream->current)) != size*nmemb) {
            stream->error = 1;
            CHECK_STREAM_CONSISTENCY(stream);
            RETURN(0);
        }
        stream->position += nbytes;
        stream->length = stream->position > stream->length
            ? stream->position
            : stream->length;

        CHECK_STREAM_CONSISTENCY(stream);
        RETURN(nbytes);
    }
    
    /* otherwise, write what we can and recurse */
    nbytes = (size_t)(NEXT_OFFSET(stream)-stream->position);
    stream->_needs_flush = 1;
    if (fwrite(ptr, 1, nbytes, stream->current) != nbytes ||
        alf_seek(stream, stream->position+nbytes, SEEK_SET) == -1) {
        stream->error = 1;
        CHECK_STREAM_CONSISTENCY(stream);
        RETURN(0);
    }
    if (size*nmemb-nbytes > 0)
        nbytes += alf_write((void *)((const char *)ptr+nbytes), 1,
                            size*nmemb-nbytes, stream);
    CHECK_STREAM_CONSISTENCY(stream);
    RETURN(nbytes);
#undef _FUNC
}

int
alf_flush (ALF *stream)
{
#define _FUNC "alf_flush"
    ENTER;

    if (!ALF_IS_WRITABLE(stream) ||
        stream->error == 1) {
        stream->error = 1;
        errno = EBADF;
        RETURN(EOF);
    }
    if (stream->_needs_flush) {
        stream->_needs_flush = 0;
        RETURN(fflush(stream->current));
    }
    else
        RETURN(0);
#undef _FUNC
}

int
alf_seek (ALF *stream, LONG_LONG offset, int whence)
{
#define _FUNC "alf_seek"

    int chunk;

    ENTER;

    if (stream->error == 1)
        RETURN(-1);

    CHECK_STREAM_CONSISTENCY(stream);

    if (stream->mode[0] != 'r')
        if (alf_flush(stream) == -1)
            RETURN(-1);

    switch (whence) {
    case SEEK_SET:
        chunk = expand_chunks_to(stream, (unsigned LONG_LONG)offset);
        CHECK_STREAM_CONSISTENCY(stream);
        if (chunk == -1 || open_chunk(stream, (unsigned int)chunk) == -1)
            RETURN(-1);
        stream->position = (unsigned LONG_LONG)offset;
        CHECK_STREAM_CONSISTENCY(stream);
        break;
    case SEEK_CUR:
        chunk = expand_chunks_to(stream, stream->position+offset);
        if (chunk == -1 || open_chunk(stream, (unsigned int)chunk) == -1)
            RETURN(-1);
        stream->position += offset;
        CHECK_STREAM_CONSISTENCY(stream);
        break;
    case SEEK_END:
        chunk = expand_chunks_to(stream, stream->length+offset);
        if (chunk == -1 || open_chunk(stream, (unsigned int)chunk) == -1)
            RETURN(-1);
        stream->position = stream->length+offset;
        CHECK_STREAM_CONSISTENCY(stream);
        break;
    }
    if (fseek(stream->current, (long)(stream->position-CURRENT_OFFSET(stream)),
              SEEK_SET) == -1) {
        stream->error = 1;
        RETURN(-1);
    }
    CHECK_STREAM_CONSISTENCY(stream);
    RETURN(0);
#undef _FUNC
}

unsigned LONG_LONG
alf_tell (ALF *stream)
{
#define _FUNC "alf_tell"
    ENTER;
    CHECK_STREAM_CONSISTENCY(stream);
    RETURN(stream->position);
#undef _FUNC
}

void
alf_rewind (ALF *stream)
{
#define _FUNC "alf_rewind"
    ENTER;
    CHECK_STREAM_CONSISTENCY(stream);
    if (stream->nchunks == 0 ||
        stream->error == 1 ||
        stream->position == 0)
        RETURNVOID;

    (void)open_chunk(stream, 0);
    stream->position = 0;
    CHECK_STREAM_CONSISTENCY(stream);
    RETURNVOID;
#undef _FUNC
}

int
alf_supports_large_files (void)
{
#define _FUNC "alf_supports_large_files"
    /* TBD */
    ENTER;
    RETURN(0);
#undef _FUNC
}

int
alf_putc (int c, ALF *stream)
{
#define _FUNC "alf_putc"

    unsigned char uc;

    ENTER;
    uc = (unsigned char)c;
    if (alf_write(&uc, 1, 1, stream) == 0)
        RETURN(EOF);
    RETURN((int)uc);
#undef _FUNC
}

int
alf_getc (ALF *stream)
{
#define _FUNC "alf_getc"

    unsigned char uc;

    ENTER;
    if (alf_read(&uc, 1, 1, stream) == 0)
        RETURN(EOF);
    RETURN((int)uc);
#undef _FUNC
}

int
alf_truncate (ALF *stream, unsigned LONG_LONG length)
{
#define _FUNC "alf_truncate"

    int i;
    STRUCT_STAT buf;
    unsigned LONG_LONG pos;
#ifdef MS_WIN32
    FILE *f;
#endif

    ENTER;

    if (!ALF_IS_WRITABLE(stream) ||
        stream->error == 1) {
        stream->error = 1;
        errno = EBADF;
        RETURN(-1);
    }

    CHECK_STREAM_CONSISTENCY(stream);

    if (alf_flush(stream) == -1)
        RETURN(-1);

    /* do nothing if the file isn't longer than length */
    if (length >= stream->length)
        RETURN(0);

    pos = stream->position;
    if (open_chunk(stream, 0) == -1)
        RETURN(-1);

    for (i=stream->nchunks-1; i>=0; i--) {
        if (i > 0 && CHUNK_OFFSET(stream, i) >= length) {
            stream->nchunks--;
            if (i > 0) {
                if (unlink(CHUNK_PATH(stream, i)) == -1) {
                    PATH_FAIL("unlink %s failed: %d.\n", CHUNK_PATH(stream,i),
                              errno);
                    stream->error = 1;
                    RETURN(-1);
                }
                MARK_TERRITORY(CHUNK_PATH(stream, i),
                               strlen(CHUNK_PATH(stream, i)),
                               __LINE__);
                free(CHUNK_PATH(stream, i));
            }
        }
        else {
            if (_alf_stat(stream, (unsigned int)i, &buf) == -1)
                RETURN(-1);
            if (i == (int)stream->current_chunk) {
#ifdef MS_WIN32
                if (_chsize(fileno(stream->current),
                            (long)(length-CHUNK_OFFSET(stream, i))) == -1) {
                    PATH_FAIL("_chsize %s failed: %d.\n", CURRENT_FILE(stream), errno);
                    stream->error = 1;
                    RETURN(-1);
                }
#else
                if (ftruncate(fileno(stream->current),
                              length-CHUNK_OFFSET(stream, i)) == -1) {
                    PATH_FAIL("ftruncate %s failed: %d.\n", CURRENT_FILE(stream), errno);
                    stream->error = 1;
                    RETURN(-1);
                }
#endif
            }
            else {
#ifdef MS_WIN32
                f = fopen(CHUNK_PATH(stream, i), "r+");
                if (f == NULL) {
                    PATH_FAIL("open %s failed: %d.\n", CHUNK_PATH(stream, i), errno);
                    stream->error = 1;
                    RETURN(-1);
                }
                if (_chsize(fileno(f),
                            (long)(length-CHUNK_OFFSET(stream, i))) == -1) {
                    PATH_FAIL("_chsize %s failed: %d.\n", CHUNK_PATH(stream, i), errno);
                    stream->error = 1;
                    RETURN(-1);
                }
#else
                if (truncate(CHUNK_PATH(stream, i),
                             length-CHUNK_OFFSET(stream, i)) == -1) {
                    PATH_FAIL("truncate %s failed: %d.\n", CHUNK_PATH(stream, i), errno);
                    stream->error = 1;
                    RETURN(-1);
                }
#endif
            }
            break;
        }
    }
    /* always make sure we alloc at least one chunk */
    if (stream->nchunks == 0) {
        if (insert_new_chunk(stream, 0) == -1)
            RETURN(-1);
    }
    else {
        stream->chunks = realloc(stream->chunks,
                                 sizeof(ALF_chunk)*stream->nchunks);
        if (stream->chunks == NULL) {
            stream->error = 1;
            RETURN(-1);
        }
    }

    stream->position = pos > length ? length : pos;
    stream->length = length;

    /* leave the stream positioned at the lesser of the requested length
       and the original position */
    RETURN(alf_seek(stream, (LONG_LONG)(pos>length ? length : pos), SEEK_SET));
#undef _FUNC
}

int
alf_puts (const char *s, ALF *stream)
{
#define _FUNC "alf_puts"

    size_t len;

    ENTER;
    len = strlen(s);
    if (alf_write(s, 1, len, stream) != len)
        RETURN(EOF);
    RETURN(len);
#undef _FUNC
}

char *
alf_gets (char *s, int size, ALF *stream)
{
#define _FUNC "alf_gets"
    int i = 0;
    int c;
    s[size-1] = s[0] = '\0';
    for (i=0; i<size-1; i++) {
        c = alf_getc(stream);
        switch (c) {
        case EOF:
            if (i != 0) {
                s[i] = '\0';
                return s;
            }
            else
                return NULL;

        case '\n':
            s[i] = '\n';
            s[i+1] = '\0';
            return s;

        default:
            s[i] = (char)c;
            break;
        }
    }
    /* filled up the buffer without seeing a newline */
    return s;
#undef _FUNC
}

int
alf_eof (ALF *stream)
{
#define _FUNC "alf_eof"
    ENTER;
    RETURN(stream->eof);
#undef _FUNC
}

int
alf_error (/*@unused@*/ ALF *stream)
{
#define _FUNC "alf_error"
    ENTER;
    RETURN(stream->error == 1);
#undef _FUNC
}

int
alf_clearerr (/*@unused@*/ ALF *stream)
{
#define _FUNC "alf_clearerr"
    ENTER;
    stream->error = stream->eof = 0;
    clearerr(stream->current);
    errno = 0;
    RETURN(0);
#undef _FUNC
}

/* TBD */
#if 0
int
alf_printf (ALF *stream, const char *format, ...)
{
}

int
alf_scanf (ALF *stream, const char *format, ...)
{
}
#endif

/*
 * Local Variables: ***
 * c-basic-offset: 4 ***
 * indent-tabs-mode: nil ***
 * End: ***
 */


syntax highlighted by Code2HTML, v. 0.9.1