* - gz_statep was converted to union to work with -Wstrict-aliasing=1 */
* Copyright (C) 2004, 2005, 2010, 2011, 2012, 2013, 2016 Mark Adler
* For conditions of distribution and use, see https://www.zlib.net/zlib_license.html
*/
#include "gzguts.h"
* see https://github.com/facebook/zstd/issues/1800#issuecomment-545945050 */
#if defined(_MSC_VER) && !defined(ssize_t)
# include <BaseTsd.h>
typedef SSIZE_T ssize_t;
#endif
local int gz_load _Z_OF((gz_statep, unsigned char *, unsigned, unsigned *));
local int gz_avail _Z_OF((gz_statep));
local int gz_look _Z_OF((gz_statep));
local int gz_decomp _Z_OF((gz_statep));
local int gz_fetch _Z_OF((gz_statep));
local int gz_skip _Z_OF((gz_statep, z_off64_t));
local z_size_t gz_read _Z_OF((gz_statep, voidp, z_size_t));
state.state->fd, and update state.state->eof, state.state->err, and state.state->msg as appropriate.
This function needs to loop on read(), since read() is not guaranteed to
read the number of bytes requested, depending on the type of descriptor. */
local int gz_load(gz_statep state, unsigned char *buf, unsigned len,
unsigned *have) {
ssize_t ret;
unsigned get, max = ((unsigned)-1 >> 2) + 1;
*have = 0;
do {
get = len - *have;
if (get > max)
get = max;
ret = read(state.state->fd, buf + *have, get);
if (ret <= 0)
break;
*have += (unsigned)ret;
} while (*have < len);
if (ret < 0) {
gz_error(state, Z_ERRNO, zstrerror());
return -1;
}
if (ret == 0)
state.state->eof = 1;
return 0;
}
error, 0 otherwise. Note that the eof flag is set when the end of the input
file is reached, even though there may be unused data in the buffer. Once
that data has been used, no more attempts will be made to read the file.
If strm->avail_in != 0, then the current data is moved to the beginning of
the input buffer, and then the remainder of the buffer is loaded with the
available data from the input file. */
local int gz_avail(gz_statep state)
{
unsigned got;
z_streamp strm = &(state.state->strm);
if (state.state->err != Z_OK && state.state->err != Z_BUF_ERROR)
return -1;
if (state.state->eof == 0) {
if (strm->avail_in) {
unsigned char *p = state.state->in;
unsigned const char *q = strm->next_in;
unsigned n = strm->avail_in;
do {
*p++ = *q++;
} while (--n);
}
if (gz_load(state, state.state->in + strm->avail_in,
state.state->size - strm->avail_in, &got) == -1)
return -1;
strm->avail_in += got;
strm->next_in = state.state->in;
}
return 0;
}
If this is the first time in, allocate required memory. state.state->how will be
left unchanged if there is no more input data available, will be set to COPY
if there is no gzip header and direct copying will be performed, or it will
be set to GZIP for decompression. If direct copying, then leftover input
data from the input buffer will be copied to the output buffer. In that
case, all further file reads will be directly to either the output buffer or
a user buffer. If decompressing, the inflate state will be initialized.
gz_look() will return 0 on success or -1 on failure. */
local int gz_look(gz_statep state) {
z_streamp strm = &(state.state->strm);
if (state.state->size == 0) {
state.state->in = (unsigned char *)malloc(state.state->want);
state.state->out = (unsigned char *)malloc(state.state->want << 1);
if (state.state->in == NULL || state.state->out == NULL) {
free(state.state->out);
free(state.state->in);
gz_error(state, Z_MEM_ERROR, "out of memory");
return -1;
}
state.state->size = state.state->want;
state.state->strm.zalloc = Z_NULL;
state.state->strm.zfree = Z_NULL;
state.state->strm.opaque = Z_NULL;
state.state->strm.avail_in = 0;
state.state->strm.next_in = Z_NULL;
if (inflateInit2(&(state.state->strm), 15 + 16) != Z_OK) {
free(state.state->out);
free(state.state->in);
state.state->size = 0;
gz_error(state, Z_MEM_ERROR, "out of memory");
return -1;
}
}
if (strm->avail_in < 2) {
if (gz_avail(state) == -1)
return -1;
if (strm->avail_in == 0)
return 0;
}
a logical dilemma here when considering the case of a partially written
gzip file, to wit, if a single 31 byte is written, then we cannot tell
whether this is a single-byte file, or just a partially written gzip
file -- for here we assume that if a gzip file is being written, then
the header will be written in a single operation, so that reading a
single byte is sufficient indication that it is not a gzip file) */
if (strm->avail_in > 1 &&
((strm->next_in[0] == 31 && strm->next_in[1] == 139)
|| (strm->next_in[0] == 40 && strm->next_in[1] == 181))) {
inflateReset(strm);
state.state->how = GZIP;
state.state->direct = 0;
return 0;
}
garbage. Ignore the trailing garbage and finish. */
if (state.state->direct == 0) {
strm->avail_in = 0;
state.state->eof = 1;
state.state->x.have = 0;
return 0;
}
the output buffer is larger than the input buffer, which also assures
space for gzungetc() */
state.state->x.next = state.state->out;
if (strm->avail_in) {
memcpy(state.state->x.next, strm->next_in, strm->avail_in);
state.state->x.have = strm->avail_in;
strm->avail_in = 0;
}
state.state->how = COPY;
state.state->direct = 1;
return 0;
}
On return, state.state->x.have and state.state->x.next point to the just decompressed
data. If the gzip stream completes, state.state->how is reset to LOOK to look for
the next gzip stream or raw data, once state.state->x.have is depleted. Returns 0
on success, -1 on failure. */
local int gz_decomp(gz_statep state) {
int ret = Z_OK;
unsigned had;
z_streamp strm = &(state.state->strm);
had = strm->avail_out;
do {
if (strm->avail_in == 0 && gz_avail(state) == -1)
return -1;
if (strm->avail_in == 0) {
gz_error(state, Z_BUF_ERROR, "unexpected end of file");
break;
}
ret = inflate(strm, Z_NO_FLUSH);
if (ret == Z_STREAM_ERROR || ret == Z_NEED_DICT) {
gz_error(state, Z_STREAM_ERROR,
"internal error: inflate stream corrupt");
return -1;
}
if (ret == Z_MEM_ERROR) {
gz_error(state, Z_MEM_ERROR, "out of memory");
return -1;
}
if (ret == Z_DATA_ERROR) {
gz_error(state, Z_DATA_ERROR,
strm->msg == NULL ? "compressed data error" : strm->msg);
return -1;
}
} while (strm->avail_out && ret != Z_STREAM_END);
state.state->x.have = had - strm->avail_out;
state.state->x.next = strm->next_out - state.state->x.have;
if (ret == Z_STREAM_END)
state.state->how = LOOK;
return 0;
}
Data is either copied from the input file or decompressed from the input
file depending on state.state->how. If state.state->how is LOOK, then a gzip header is
looked for to determine whether to copy or decompress. Returns -1 on error,
otherwise 0. gz_fetch() will leave state.state->how as COPY or GZIP unless the
end of the input file has been reached and all data has been processed. */
local int gz_fetch(gz_statep state) {
z_streamp strm = &(state.state->strm);
do {
switch(state.state->how) {
case LOOK:
if (gz_look(state) == -1)
return -1;
if (state.state->how == LOOK)
return 0;
break;
case COPY:
if (gz_load(state, state.state->out, state.state->size << 1, &(state.state->x.have))
== -1)
return -1;
state.state->x.next = state.state->out;
return 0;
case GZIP:
strm->avail_out = state.state->size << 1;
strm->next_out = state.state->out;
if (gz_decomp(state) == -1)
return -1;
}
} while (state.state->x.have == 0 && (!state.state->eof || strm->avail_in));
return 0;
}
local int gz_skip(gz_statep state, z_off64_t len) {
unsigned n;
while (len)
if (state.state->x.have) {
n = GT_OFF(state.state->x.have) || (z_off64_t)state.state->x.have > len ?
(unsigned)len : state.state->x.have;
state.state->x.have -= n;
state.state->x.next += n;
state.state->x.pos += n;
len -= n;
}
else if (state.state->eof && state.state->strm.avail_in == 0)
break;
else {
if (gz_fetch(state) == -1)
return -1;
}
return 0;
}
input. Return the number of bytes read. If zero is returned, either the
end of file was reached, or there was an error. state.state->err must be
consulted in that case to determine which. */
local z_size_t gz_read(gz_statep state, voidp buf, z_size_t len) {
z_size_t got;
unsigned n;
if (len == 0)
return 0;
if (state.state->seek) {
state.state->seek = 0;
if (gz_skip(state, state.state->skip) == -1)
return 0;
}
got = 0;
do {
n = -1;
if (n > len)
n = (unsigned)len;
if (state.state->x.have) {
if (state.state->x.have < n)
n = state.state->x.have;
memcpy(buf, state.state->x.next, n);
state.state->x.next += n;
state.state->x.have -= n;
}
else if (state.state->eof && state.state->strm.avail_in == 0) {
state.state->past = 1;
break;
}
buffer */
else if (state.state->how == LOOK || n < (state.state->size << 1)) {
if (gz_fetch(state) == -1)
return 0;
continue;
output buffer, allowing at least one gzungetc() to succeed */
}
else if (state.state->how == COPY) {
if (gz_load(state, (unsigned char *)buf, n, &n) == -1)
return 0;
}
else {
state.state->strm.avail_out = n;
state.state->strm.next_out = (unsigned char *)buf;
if (gz_decomp(state) == -1)
return 0;
n = state.state->x.have;
state.state->x.have = 0;
}
len -= n;
buf = (char *)buf + n;
got += n;
state.state->x.pos += n;
} while (len);
return got;
}
int ZEXPORT gzread(gzFile file, voidp buf, unsigned len) {
gz_statep state;
if (file == NULL)
return -1;
state.file = file;
if (state.state->mode != GZ_READ ||
(state.state->err != Z_OK && state.state->err != Z_BUF_ERROR))
return -1;
with an error (this avoids a flaw in the interface) */
if ((int)len < 0) {
gz_error(state, Z_STREAM_ERROR, "request does not fit in an int");
return -1;
}
len = (unsigned)gz_read(state, buf, len);
if (len == 0 && state.state->err != Z_OK && state.state->err != Z_BUF_ERROR)
return -1;
return (int)len;
}
z_size_t ZEXPORT gzfread(voidp buf, z_size_t size, z_size_t nitems,
gzFile file) {
z_size_t len;
gz_statep state;
if (file == NULL)
return 0;
state.file = file;
if (state.state->mode != GZ_READ ||
(state.state->err != Z_OK && state.state->err != Z_BUF_ERROR))
return 0;
len = nitems * size;
if (size && len / size != nitems) {
gz_error(state, Z_STREAM_ERROR, "request does not fit in a size_t");
return 0;
}
return len ? gz_read(state, buf, len) / size : 0;
}
#if ZLIB_VERNUM >= 0x1261
#ifdef Z_PREFIX_SET
# undef z_gzgetc
#else
# undef gzgetc
#endif
#endif
#if ZLIB_VERNUM == 0x1260
# undef gzgetc
#endif
#if ZLIB_VERNUM <= 0x1250
ZEXTERN int ZEXPORT gzgetc _Z_OF((gzFile file));
ZEXTERN int ZEXPORT gzgetc_ _Z_OF((gzFile file));
#endif
int ZEXPORT gzgetc(gzFile file) {
int ret;
unsigned char buf[1];
gz_statep state;
if (file == NULL)
return -1;
state.file = file;
if (state.state->mode != GZ_READ ||
(state.state->err != Z_OK && state.state->err != Z_BUF_ERROR))
return -1;
if (state.state->x.have) {
state.state->x.have--;
state.state->x.pos++;
return *(state.state->x.next)++;
}
ret = (int)gz_read(state, buf, 1);
return ret < 1 ? -1 : buf[0];
}
int ZEXPORT gzgetc_(gzFile file) {
return gzgetc(file);
}
int ZEXPORT gzungetc(int c, gzFile file) {
gz_statep state;
if (file == NULL)
return -1;
state.file = file;
if (state.state->mode != GZ_READ ||
(state.state->err != Z_OK && state.state->err != Z_BUF_ERROR))
return -1;
if (state.state->seek) {
state.state->seek = 0;
if (gz_skip(state, state.state->skip) == -1)
return -1;
}
if (c < 0)
return -1;
if (state.state->x.have == 0) {
state.state->x.have = 1;
state.state->x.next = state.state->out + (state.state->size << 1) - 1;
state.state->x.next[0] = (unsigned char)c;
state.state->x.pos--;
state.state->past = 0;
return c;
}
if (state.state->x.have == (state.state->size << 1)) {
gz_error(state, Z_DATA_ERROR, "out of room to push characters");
return -1;
}
if (state.state->x.next == state.state->out) {
unsigned char *src = state.state->out + state.state->x.have;
unsigned char *dest = state.state->out + (state.state->size << 1);
while (src > state.state->out)
*--dest = *--src;
state.state->x.next = dest;
}
state.state->x.have++;
state.state->x.next--;
state.state->x.next[0] = (unsigned char)c;
state.state->x.pos--;
state.state->past = 0;
return c;
}
char * ZEXPORT gzgets(gzFile file, char *buf, int len) {
unsigned left, n;
char *str;
unsigned char *eol;
gz_statep state;
if (file == NULL || buf == NULL || len < 1)
return NULL;
state.file = file;
if (state.state->mode != GZ_READ ||
(state.state->err != Z_OK && state.state->err != Z_BUF_ERROR))
return NULL;
if (state.state->seek) {
state.state->seek = 0;
if (gz_skip(state, state.state->skip) == -1)
return NULL;
}
append a terminating zero to the string (we don't check for a zero in
the contents, let the user worry about that) */
str = buf;
left = (unsigned)len - 1;
if (left) do {
if (state.state->x.have == 0 && gz_fetch(state) == -1)
return NULL;
if (state.state->x.have == 0) {
state.state->past = 1;
break;
}
n = state.state->x.have > left ? left : state.state->x.have;
eol = (unsigned char *)memchr(state.state->x.next, '\n', n);
if (eol != NULL)
n = (unsigned)(eol - state.state->x.next) + 1;
memcpy(buf, state.state->x.next, n);
state.state->x.have -= n;
state.state->x.next += n;
state.state->x.pos += n;
left -= n;
buf += n;
} while (left && eol == NULL);
if (buf == str)
return NULL;
buf[0] = 0;
return str;
}
int ZEXPORT gzdirect(gzFile file) {
gz_statep state;
if (file == NULL)
return 0;
state.file = file;
mainly for right after a gzopen() or gzdopen()) */
if (state.state->mode == GZ_READ && state.state->how == LOOK && state.state->x.have == 0)
(void)gz_look(state);
return state.state->direct;
}
int ZEXPORT gzclose_r(gzFile file) {
int ret, err;
gz_statep state;
if (file == NULL)
return Z_STREAM_ERROR;
state.file = file;
if (state.state->mode != GZ_READ)
return Z_STREAM_ERROR;
if (state.state->size) {
inflateEnd(&(state.state->strm));
free(state.state->out);
free(state.state->in);
}
err = state.state->err == Z_BUF_ERROR ? Z_BUF_ERROR : Z_OK;
gz_error(state, Z_OK, NULL);
free(state.state->path);
ret = close(state.state->fd);
free(state.state);
return ret ? Z_ERRNO : err;
}