72c70060d8
An open() with O_APPEND followed by an lseek() to determine the position will return zero for a non-empty file, even though the next write will start at the end of the file. This commit works around that by doing an lseek() to the end when appending.
637 lines
16 KiB
C
637 lines
16 KiB
C
/* gzlib.c -- zlib functions common to reading and writing gzip files
|
|
* Copyright (C) 2004, 2010, 2011, 2012, 2013 Mark Adler
|
|
* For conditions of distribution and use, see copyright notice in zlib.h
|
|
*/
|
|
|
|
#include "gzguts.h"
|
|
|
|
#if defined(_WIN32) && !defined(__BORLANDC__)
|
|
# define LSEEK _lseeki64
|
|
#else
|
|
#if defined(_LARGEFILE64_SOURCE) && _LFS64_LARGEFILE-0
|
|
# define LSEEK lseek64
|
|
#else
|
|
# define LSEEK lseek
|
|
#endif
|
|
#endif
|
|
|
|
/* Local functions */
|
|
local void gz_reset OF((gz_statep));
|
|
local gzFile gz_open OF((const void *, int, const char *));
|
|
|
|
#if defined UNDER_CE
|
|
|
|
/* Map the Windows error number in ERROR to a locale-dependent error message
|
|
string and return a pointer to it. Typically, the values for ERROR come
|
|
from GetLastError.
|
|
|
|
The string pointed to shall not be modified by the application, but may be
|
|
overwritten by a subsequent call to gz_strwinerror
|
|
|
|
The gz_strwinerror function does not change the current setting of
|
|
GetLastError. */
|
|
char ZLIB_INTERNAL *gz_strwinerror (error)
|
|
DWORD error;
|
|
{
|
|
static char buf[1024];
|
|
|
|
wchar_t *msgbuf;
|
|
DWORD lasterr = GetLastError();
|
|
DWORD chars = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM
|
|
| FORMAT_MESSAGE_ALLOCATE_BUFFER,
|
|
NULL,
|
|
error,
|
|
0, /* Default language */
|
|
(LPVOID)&msgbuf,
|
|
0,
|
|
NULL);
|
|
if (chars != 0) {
|
|
/* If there is an \r\n appended, zap it. */
|
|
if (chars >= 2
|
|
&& msgbuf[chars - 2] == '\r' && msgbuf[chars - 1] == '\n') {
|
|
chars -= 2;
|
|
msgbuf[chars] = 0;
|
|
}
|
|
|
|
if (chars > sizeof (buf) - 1) {
|
|
chars = sizeof (buf) - 1;
|
|
msgbuf[chars] = 0;
|
|
}
|
|
|
|
wcstombs(buf, msgbuf, chars + 1);
|
|
LocalFree(msgbuf);
|
|
}
|
|
else {
|
|
sprintf(buf, "unknown win32 error (%ld)", error);
|
|
}
|
|
|
|
SetLastError(lasterr);
|
|
return buf;
|
|
}
|
|
|
|
#endif /* UNDER_CE */
|
|
|
|
/* Reset gzip file state */
|
|
local void gz_reset(state)
|
|
gz_statep state;
|
|
{
|
|
state->x.have = 0; /* no output data available */
|
|
if (state->mode == GZ_READ) { /* for reading ... */
|
|
state->eof = 0; /* not at end of file */
|
|
state->past = 0; /* have not read past end yet */
|
|
state->how = LOOK; /* look for gzip header */
|
|
}
|
|
state->seek = 0; /* no seek request pending */
|
|
gz_error(state, Z_OK, NULL); /* clear error */
|
|
state->x.pos = 0; /* no uncompressed data yet */
|
|
state->strm.avail_in = 0; /* no input data yet */
|
|
}
|
|
|
|
/* Open a gzip file either by name or file descriptor. */
|
|
local gzFile gz_open(path, fd, mode)
|
|
const void *path;
|
|
int fd;
|
|
const char *mode;
|
|
{
|
|
gz_statep state;
|
|
size_t len;
|
|
int oflag;
|
|
#ifdef O_CLOEXEC
|
|
int cloexec = 0;
|
|
#endif
|
|
#ifdef O_EXCL
|
|
int exclusive = 0;
|
|
#endif
|
|
|
|
/* check input */
|
|
if (path == NULL)
|
|
return NULL;
|
|
|
|
/* allocate gzFile structure to return */
|
|
state = (gz_statep)malloc(sizeof(gz_state));
|
|
if (state == NULL)
|
|
return NULL;
|
|
state->size = 0; /* no buffers allocated yet */
|
|
state->want = GZBUFSIZE; /* requested buffer size */
|
|
state->msg = NULL; /* no error message yet */
|
|
|
|
/* interpret mode */
|
|
state->mode = GZ_NONE;
|
|
state->level = Z_DEFAULT_COMPRESSION;
|
|
state->strategy = Z_DEFAULT_STRATEGY;
|
|
state->direct = 0;
|
|
while (*mode) {
|
|
if (*mode >= '0' && *mode <= '9')
|
|
state->level = *mode - '0';
|
|
else
|
|
switch (*mode) {
|
|
case 'r':
|
|
state->mode = GZ_READ;
|
|
break;
|
|
#ifndef NO_GZCOMPRESS
|
|
case 'w':
|
|
state->mode = GZ_WRITE;
|
|
break;
|
|
case 'a':
|
|
state->mode = GZ_APPEND;
|
|
break;
|
|
#endif
|
|
case '+': /* can't read and write at the same time */
|
|
free(state);
|
|
return NULL;
|
|
case 'b': /* ignore -- will request binary anyway */
|
|
break;
|
|
#ifdef O_CLOEXEC
|
|
case 'e':
|
|
cloexec = 1;
|
|
break;
|
|
#endif
|
|
#ifdef O_EXCL
|
|
case 'x':
|
|
exclusive = 1;
|
|
break;
|
|
#endif
|
|
case 'f':
|
|
state->strategy = Z_FILTERED;
|
|
break;
|
|
case 'h':
|
|
state->strategy = Z_HUFFMAN_ONLY;
|
|
break;
|
|
case 'R':
|
|
state->strategy = Z_RLE;
|
|
break;
|
|
case 'F':
|
|
state->strategy = Z_FIXED;
|
|
break;
|
|
case 'T':
|
|
state->direct = 1;
|
|
break;
|
|
default: /* could consider as an error, but just ignore */
|
|
;
|
|
}
|
|
mode++;
|
|
}
|
|
|
|
/* must provide an "r", "w", or "a" */
|
|
if (state->mode == GZ_NONE) {
|
|
free(state);
|
|
return NULL;
|
|
}
|
|
|
|
/* can't force transparent read */
|
|
if (state->mode == GZ_READ) {
|
|
if (state->direct) {
|
|
free(state);
|
|
return NULL;
|
|
}
|
|
state->direct = 1; /* for empty file */
|
|
}
|
|
|
|
/* save the path name for error messages */
|
|
#ifdef _WIN32
|
|
if (fd == -2) {
|
|
len = wcstombs(NULL, path, 0);
|
|
if (len == (size_t)-1)
|
|
len = 0;
|
|
}
|
|
else
|
|
#endif
|
|
len = strlen((const char *)path);
|
|
state->path = (char *)malloc(len + 1);
|
|
if (state->path == NULL) {
|
|
free(state);
|
|
return NULL;
|
|
}
|
|
#ifdef _WIN32
|
|
if (fd == -2)
|
|
if (len)
|
|
wcstombs(state->path, path, len + 1);
|
|
else
|
|
*(state->path) = 0;
|
|
else
|
|
#endif
|
|
#if !defined(NO_snprintf) && !defined(NO_vsnprintf)
|
|
snprintf(state->path, len + 1, "%s", (const char *)path);
|
|
#else
|
|
strcpy(state->path, path);
|
|
#endif
|
|
|
|
/* compute the flags for open() */
|
|
oflag =
|
|
#ifdef O_LARGEFILE
|
|
O_LARGEFILE |
|
|
#endif
|
|
#ifdef O_BINARY
|
|
O_BINARY |
|
|
#endif
|
|
#ifdef O_CLOEXEC
|
|
(cloexec ? O_CLOEXEC : 0) |
|
|
#endif
|
|
(state->mode == GZ_READ ?
|
|
O_RDONLY :
|
|
(O_WRONLY | O_CREAT |
|
|
#ifdef O_EXCL
|
|
(exclusive ? O_EXCL : 0) |
|
|
#endif
|
|
(state->mode == GZ_WRITE ?
|
|
O_TRUNC :
|
|
O_APPEND)));
|
|
|
|
/* open the file with the appropriate flags (or just use fd) */
|
|
state->fd = fd > -1 ? fd : (
|
|
#ifdef _WIN32
|
|
fd == -2 ? _wopen(path, oflag, 0666) :
|
|
#endif
|
|
open((const char *)path, oflag, 0666));
|
|
if (state->fd == -1) {
|
|
free(state->path);
|
|
free(state);
|
|
return NULL;
|
|
}
|
|
if (state->mode == GZ_APPEND) {
|
|
LSEEK(state->fd, 0, SEEK_END); /* so gzoffset() is correct */
|
|
state->mode = GZ_WRITE; /* simplify later checks */
|
|
}
|
|
|
|
/* save the current position for rewinding (only if reading) */
|
|
if (state->mode == GZ_READ) {
|
|
state->start = LSEEK(state->fd, 0, SEEK_CUR);
|
|
if (state->start == -1) state->start = 0;
|
|
}
|
|
|
|
/* initialize stream */
|
|
gz_reset(state);
|
|
|
|
/* return stream */
|
|
return (gzFile)state;
|
|
}
|
|
|
|
/* -- see zlib.h -- */
|
|
gzFile ZEXPORT gzopen(path, mode)
|
|
const char *path;
|
|
const char *mode;
|
|
{
|
|
return gz_open(path, -1, mode);
|
|
}
|
|
|
|
/* -- see zlib.h -- */
|
|
gzFile ZEXPORT gzopen64(path, mode)
|
|
const char *path;
|
|
const char *mode;
|
|
{
|
|
return gz_open(path, -1, mode);
|
|
}
|
|
|
|
/* -- see zlib.h -- */
|
|
gzFile ZEXPORT gzdopen(fd, mode)
|
|
int fd;
|
|
const char *mode;
|
|
{
|
|
char *path; /* identifier for error messages */
|
|
gzFile gz;
|
|
|
|
if (fd == -1 || (path = (char *)malloc(7 + 3 * sizeof(int))) == NULL)
|
|
return NULL;
|
|
#if !defined(NO_snprintf) && !defined(NO_vsnprintf)
|
|
snprintf(path, 7 + 3 * sizeof(int), "<fd:%d>", fd); /* for debugging */
|
|
#else
|
|
sprintf(path, "<fd:%d>", fd); /* for debugging */
|
|
#endif
|
|
gz = gz_open(path, fd, mode);
|
|
free(path);
|
|
return gz;
|
|
}
|
|
|
|
/* -- see zlib.h -- */
|
|
#ifdef _WIN32
|
|
gzFile ZEXPORT gzopen_w(path, mode)
|
|
const wchar_t *path;
|
|
const char *mode;
|
|
{
|
|
return gz_open(path, -2, mode);
|
|
}
|
|
#endif
|
|
|
|
/* -- see zlib.h -- */
|
|
int ZEXPORT gzbuffer(file, size)
|
|
gzFile file;
|
|
unsigned size;
|
|
{
|
|
gz_statep state;
|
|
|
|
/* get internal structure and check integrity */
|
|
if (file == NULL)
|
|
return -1;
|
|
state = (gz_statep)file;
|
|
if (state->mode != GZ_READ && state->mode != GZ_WRITE)
|
|
return -1;
|
|
|
|
/* make sure we haven't already allocated memory */
|
|
if (state->size != 0)
|
|
return -1;
|
|
|
|
/* check and set requested size */
|
|
if (size < 2)
|
|
size = 2; /* need two bytes to check magic header */
|
|
state->want = size;
|
|
return 0;
|
|
}
|
|
|
|
/* -- see zlib.h -- */
|
|
int ZEXPORT gzrewind(file)
|
|
gzFile file;
|
|
{
|
|
gz_statep state;
|
|
|
|
/* get internal structure */
|
|
if (file == NULL)
|
|
return -1;
|
|
state = (gz_statep)file;
|
|
|
|
/* check that we're reading and that there's no error */
|
|
if (state->mode != GZ_READ ||
|
|
(state->err != Z_OK && state->err != Z_BUF_ERROR))
|
|
return -1;
|
|
|
|
/* back up and start over */
|
|
if (LSEEK(state->fd, state->start, SEEK_SET) == -1)
|
|
return -1;
|
|
gz_reset(state);
|
|
return 0;
|
|
}
|
|
|
|
/* -- see zlib.h -- */
|
|
z_off64_t ZEXPORT gzseek64(file, offset, whence)
|
|
gzFile file;
|
|
z_off64_t offset;
|
|
int whence;
|
|
{
|
|
unsigned n;
|
|
z_off64_t ret;
|
|
gz_statep state;
|
|
|
|
/* get internal structure and check integrity */
|
|
if (file == NULL)
|
|
return -1;
|
|
state = (gz_statep)file;
|
|
if (state->mode != GZ_READ && state->mode != GZ_WRITE)
|
|
return -1;
|
|
|
|
/* check that there's no error */
|
|
if (state->err != Z_OK && state->err != Z_BUF_ERROR)
|
|
return -1;
|
|
|
|
/* can only seek from start or relative to current position */
|
|
if (whence != SEEK_SET && whence != SEEK_CUR)
|
|
return -1;
|
|
|
|
/* normalize offset to a SEEK_CUR specification */
|
|
if (whence == SEEK_SET)
|
|
offset -= state->x.pos;
|
|
else if (state->seek)
|
|
offset += state->skip;
|
|
state->seek = 0;
|
|
|
|
/* if within raw area while reading, just go there */
|
|
if (state->mode == GZ_READ && state->how == COPY &&
|
|
state->x.pos + offset >= 0) {
|
|
ret = LSEEK(state->fd, offset - state->x.have, SEEK_CUR);
|
|
if (ret == -1)
|
|
return -1;
|
|
state->x.have = 0;
|
|
state->eof = 0;
|
|
state->past = 0;
|
|
state->seek = 0;
|
|
gz_error(state, Z_OK, NULL);
|
|
state->strm.avail_in = 0;
|
|
state->x.pos += offset;
|
|
return state->x.pos;
|
|
}
|
|
|
|
/* calculate skip amount, rewinding if needed for back seek when reading */
|
|
if (offset < 0) {
|
|
if (state->mode != GZ_READ) /* writing -- can't go backwards */
|
|
return -1;
|
|
offset += state->x.pos;
|
|
if (offset < 0) /* before start of file! */
|
|
return -1;
|
|
if (gzrewind(file) == -1) /* rewind, then skip to offset */
|
|
return -1;
|
|
}
|
|
|
|
/* if reading, skip what's in output buffer (one less gzgetc() check) */
|
|
if (state->mode == GZ_READ) {
|
|
n = GT_OFF(state->x.have) || (z_off64_t)state->x.have > offset ?
|
|
(unsigned)offset : state->x.have;
|
|
state->x.have -= n;
|
|
state->x.next += n;
|
|
state->x.pos += n;
|
|
offset -= n;
|
|
}
|
|
|
|
/* request skip (if not zero) */
|
|
if (offset) {
|
|
state->seek = 1;
|
|
state->skip = offset;
|
|
}
|
|
return state->x.pos + offset;
|
|
}
|
|
|
|
/* -- see zlib.h -- */
|
|
z_off_t ZEXPORT gzseek(file, offset, whence)
|
|
gzFile file;
|
|
z_off_t offset;
|
|
int whence;
|
|
{
|
|
z_off64_t ret;
|
|
|
|
ret = gzseek64(file, (z_off64_t)offset, whence);
|
|
return ret == (z_off_t)ret ? (z_off_t)ret : -1;
|
|
}
|
|
|
|
/* -- see zlib.h -- */
|
|
z_off64_t ZEXPORT gztell64(file)
|
|
gzFile file;
|
|
{
|
|
gz_statep state;
|
|
|
|
/* get internal structure and check integrity */
|
|
if (file == NULL)
|
|
return -1;
|
|
state = (gz_statep)file;
|
|
if (state->mode != GZ_READ && state->mode != GZ_WRITE)
|
|
return -1;
|
|
|
|
/* return position */
|
|
return state->x.pos + (state->seek ? state->skip : 0);
|
|
}
|
|
|
|
/* -- see zlib.h -- */
|
|
z_off_t ZEXPORT gztell(file)
|
|
gzFile file;
|
|
{
|
|
z_off64_t ret;
|
|
|
|
ret = gztell64(file);
|
|
return ret == (z_off_t)ret ? (z_off_t)ret : -1;
|
|
}
|
|
|
|
/* -- see zlib.h -- */
|
|
z_off64_t ZEXPORT gzoffset64(file)
|
|
gzFile file;
|
|
{
|
|
z_off64_t offset;
|
|
gz_statep state;
|
|
|
|
/* get internal structure and check integrity */
|
|
if (file == NULL)
|
|
return -1;
|
|
state = (gz_statep)file;
|
|
if (state->mode != GZ_READ && state->mode != GZ_WRITE)
|
|
return -1;
|
|
|
|
/* compute and return effective offset in file */
|
|
offset = LSEEK(state->fd, 0, SEEK_CUR);
|
|
if (offset == -1)
|
|
return -1;
|
|
if (state->mode == GZ_READ) /* reading */
|
|
offset -= state->strm.avail_in; /* don't count buffered input */
|
|
return offset;
|
|
}
|
|
|
|
/* -- see zlib.h -- */
|
|
z_off_t ZEXPORT gzoffset(file)
|
|
gzFile file;
|
|
{
|
|
z_off64_t ret;
|
|
|
|
ret = gzoffset64(file);
|
|
return ret == (z_off_t)ret ? (z_off_t)ret : -1;
|
|
}
|
|
|
|
/* -- see zlib.h -- */
|
|
int ZEXPORT gzeof(file)
|
|
gzFile file;
|
|
{
|
|
gz_statep state;
|
|
|
|
/* get internal structure and check integrity */
|
|
if (file == NULL)
|
|
return 0;
|
|
state = (gz_statep)file;
|
|
if (state->mode != GZ_READ && state->mode != GZ_WRITE)
|
|
return 0;
|
|
|
|
/* return end-of-file state */
|
|
return state->mode == GZ_READ ? state->past : 0;
|
|
}
|
|
|
|
/* -- see zlib.h -- */
|
|
const char * ZEXPORT gzerror(file, errnum)
|
|
gzFile file;
|
|
int *errnum;
|
|
{
|
|
gz_statep state;
|
|
|
|
/* get internal structure and check integrity */
|
|
if (file == NULL)
|
|
return NULL;
|
|
state = (gz_statep)file;
|
|
if (state->mode != GZ_READ && state->mode != GZ_WRITE)
|
|
return NULL;
|
|
|
|
/* return error information */
|
|
if (errnum != NULL)
|
|
*errnum = state->err;
|
|
return state->err == Z_MEM_ERROR ? "out of memory" :
|
|
(state->msg == NULL ? "" : state->msg);
|
|
}
|
|
|
|
/* -- see zlib.h -- */
|
|
void ZEXPORT gzclearerr(file)
|
|
gzFile file;
|
|
{
|
|
gz_statep state;
|
|
|
|
/* get internal structure and check integrity */
|
|
if (file == NULL)
|
|
return;
|
|
state = (gz_statep)file;
|
|
if (state->mode != GZ_READ && state->mode != GZ_WRITE)
|
|
return;
|
|
|
|
/* clear error and end-of-file */
|
|
if (state->mode == GZ_READ) {
|
|
state->eof = 0;
|
|
state->past = 0;
|
|
}
|
|
gz_error(state, Z_OK, NULL);
|
|
}
|
|
|
|
/* Create an error message in allocated memory and set state->err and
|
|
state->msg accordingly. Free any previous error message already there. Do
|
|
not try to free or allocate space if the error is Z_MEM_ERROR (out of
|
|
memory). Simply save the error message as a static string. If there is an
|
|
allocation failure constructing the error message, then convert the error to
|
|
out of memory. */
|
|
void ZLIB_INTERNAL gz_error(state, err, msg)
|
|
gz_statep state;
|
|
int err;
|
|
const char *msg;
|
|
{
|
|
/* free previously allocated message and clear */
|
|
if (state->msg != NULL) {
|
|
if (state->err != Z_MEM_ERROR)
|
|
free(state->msg);
|
|
state->msg = NULL;
|
|
}
|
|
|
|
/* if fatal, set state->x.have to 0 so that the gzgetc() macro fails */
|
|
if (err != Z_OK && err != Z_BUF_ERROR)
|
|
state->x.have = 0;
|
|
|
|
/* set error code, and if no message, then done */
|
|
state->err = err;
|
|
if (msg == NULL)
|
|
return;
|
|
|
|
/* for an out of memory error, return literal string when requested */
|
|
if (err == Z_MEM_ERROR)
|
|
return;
|
|
|
|
/* construct error message with path */
|
|
if ((state->msg = (char *)malloc(strlen(state->path) + strlen(msg) + 3)) ==
|
|
NULL) {
|
|
state->err = Z_MEM_ERROR;
|
|
return;
|
|
}
|
|
#if !defined(NO_snprintf) && !defined(NO_vsnprintf)
|
|
snprintf(state->msg, strlen(state->path) + strlen(msg) + 3,
|
|
"%s%s%s", state->path, ": ", msg);
|
|
#else
|
|
strcpy(state->msg, state->path);
|
|
strcat(state->msg, ": ");
|
|
strcat(state->msg, msg);
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
#ifndef INT_MAX
|
|
/* portably return maximum value for an int (when limits.h presumed not
|
|
available) -- we need to do this to cover cases where 2's complement not
|
|
used, since C standard permits 1's complement and sign-bit representations,
|
|
otherwise we could just use ((unsigned)-1) >> 1 */
|
|
unsigned ZLIB_INTERNAL gz_intmax()
|
|
{
|
|
unsigned p, q;
|
|
|
|
p = 1;
|
|
do {
|
|
q = p;
|
|
p <<= 1;
|
|
p++;
|
|
} while (p > q);
|
|
return q >> 1;
|
|
}
|
|
#endif
|