/*
** 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