/* ** 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 #include #include #include #include #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 #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 #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 #include #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; inchunks; 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;jchunks[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; inchunks; 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;jchunks[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\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;ierror = 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;inchunks;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; ieof); #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: *** */