#include "cvs.h"
#include "buffer.h"
#include "pagealign_alloc.h"
#if defined (SERVER_SUPPORT) || defined (CLIENT_SUPPORT)
#if HAVE_ZLIB_H
# include <zlib.h>
#else
# include "zlib.h"
#endif
#if !defined (EIO)
#define EIO EBADPOS
#endif
struct compress_buffer
{
struct buffer *buf;
z_stream zstr;
int level;
};
static void compress_error (int, int, z_stream *, const char *);
static int compress_buffer_input (void *, char *, size_t, size_t, size_t *);
static int compress_buffer_output (void *, const char *, size_t, size_t *);
static int compress_buffer_flush (void *);
static int compress_buffer_block (void *, bool);
static int compress_buffer_get_fd (void *);
static int compress_buffer_shutdown_input (struct buffer *);
static int compress_buffer_shutdown_output (struct buffer *);
static void
compress_error (int status, int zstatus, z_stream *zstr, const char *msg)
{
int hold_errno;
const char *zmsg;
char buf[100];
hold_errno = errno;
zmsg = zstr->msg;
if (zmsg == NULL)
{
sprintf (buf, "error %d", zstatus);
zmsg = buf;
}
error (status,
zstatus == Z_ERRNO ? hold_errno : 0,
"%s: %s", msg, zmsg);
}
struct buffer *
compress_buffer_initialize (struct buffer *buf, int input, int level,
void (*memory) (struct buffer *))
{
struct compress_buffer *n;
int zstatus;
n = xmalloc (sizeof *n);
memset (n, 0, sizeof *n);
n->buf = buf;
n->level = level;
if (input)
zstatus = inflateInit (&n->zstr);
else
zstatus = deflateInit (&n->zstr, level);
if (zstatus != Z_OK)
compress_error (1, zstatus, &n->zstr, "compression initialization");
assert (! input || buf->data == NULL || buf->data->next == NULL);
return buf_initialize (input ? compress_buffer_input : NULL,
input ? NULL : compress_buffer_output,
input ? NULL : compress_buffer_flush,
compress_buffer_block, compress_buffer_get_fd,
(input
? compress_buffer_shutdown_input
: compress_buffer_shutdown_output),
memory,
n);
}
static int
compress_buffer_input (void *closure, char *data, size_t need, size_t size,
size_t *got)
{
struct compress_buffer *cb = closure;
struct buffer_data *bd;
assert (cb->buf->input);
bd = cb->buf->data;
if (bd == NULL)
{
bd = xmalloc (sizeof (struct buffer_data));
if (bd == NULL)
return -2;
bd->text = pagealign_xalloc (BUFFER_DATA_SIZE);
if (bd->text == NULL)
{
free (bd);
return -2;
}
bd->bufp = bd->text;
bd->size = 0;
cb->buf->data = bd;
}
cb->zstr.avail_out = size;
cb->zstr.next_out = (Bytef *) data;
while (1)
{
int zstatus, sofar, status;
size_t nread;
cb->zstr.avail_in = bd->size;
cb->zstr.next_in = (Bytef *) bd->bufp;
do
{
zstatus = inflate (&cb->zstr, Z_NO_FLUSH);
if (zstatus == Z_STREAM_END)
break;
if (zstatus != Z_OK && zstatus != Z_BUF_ERROR)
{
compress_error (0, zstatus, &cb->zstr, "inflate");
return EIO;
}
} while (cb->zstr.avail_in > 0
&& cb->zstr.avail_out > 0);
bd->size = cb->zstr.avail_in;
bd->bufp = (char *) cb->zstr.next_in;
sofar = size - cb->zstr.avail_out;
if (zstatus == Z_STREAM_END)
{
if (sofar > 0) break;
return -1;
}
if (sofar > 0 && sofar >= need)
break;
assert (bd->size == 0);
status = (*cb->buf->input) (cb->buf->closure, bd->text,
need ? 1 : 0, BUFFER_DATA_SIZE, &nread);
if (status == -2)
return status;
if (status != 0)
{
if (sofar > 0) break;
return status;
}
if (nread == 0)
{
assert (need == 0);
break;
}
bd->bufp = bd->text;
bd->size = nread;
}
*got = size - cb->zstr.avail_out;
return 0;
}
extern int gzip_level;
static int
compress_buffer_output (void *closure, const char *data, size_t have,
size_t *wrote)
{
struct compress_buffer *cb = closure;
static char *buffer = NULL;
if (!buffer)
buffer = pagealign_xalloc (BUFFER_DATA_SIZE);
if (cb->level != gzip_level)
{
cb->level = gzip_level;
deflateParams (&cb->zstr, gzip_level, Z_DEFAULT_STRATEGY);
}
cb->zstr.avail_in = have;
cb->zstr.next_in = (unsigned char *) data;
while (cb->zstr.avail_in > 0)
{
int zstatus;
cb->zstr.avail_out = BUFFER_DATA_SIZE;
cb->zstr.next_out = (unsigned char *) buffer;
zstatus = deflate (&cb->zstr, Z_NO_FLUSH);
if (zstatus != Z_OK)
{
compress_error (0, zstatus, &cb->zstr, "deflate");
return EIO;
}
if (cb->zstr.avail_out != BUFFER_DATA_SIZE)
buf_output (cb->buf, buffer,
BUFFER_DATA_SIZE - cb->zstr.avail_out);
}
*wrote = have;
return buf_send_output (cb->buf);
}
static int
compress_buffer_flush (void *closure)
{
struct compress_buffer *cb = closure;
static char *buffer = NULL;
if (!buffer)
buffer = pagealign_xalloc (BUFFER_DATA_SIZE);
cb->zstr.avail_in = 0;
cb->zstr.next_in = NULL;
while (1)
{
int zstatus;
cb->zstr.avail_out = BUFFER_DATA_SIZE;
cb->zstr.next_out = (unsigned char *) buffer;
zstatus = deflate (&cb->zstr, Z_SYNC_FLUSH);
if (zstatus == Z_BUF_ERROR)
break;
if (zstatus != Z_OK)
{
compress_error (0, zstatus, &cb->zstr, "deflate flush");
return EIO;
}
if (cb->zstr.avail_out != BUFFER_DATA_SIZE)
buf_output (cb->buf, buffer,
BUFFER_DATA_SIZE - cb->zstr.avail_out);
if (cb->zstr.avail_out > 0)
break;
}
return buf_flush (cb->buf, 0);
}
static int
compress_buffer_block (void *closure, bool block)
{
struct compress_buffer *cb = closure;
if (block)
return set_block (cb->buf);
else
return set_nonblock (cb->buf);
}
static int
compress_buffer_get_fd (void *closure)
{
struct compress_buffer *cb = closure;
return buf_get_fd (cb->buf);
}
static int
compress_buffer_shutdown_input (struct buffer *buf)
{
struct compress_buffer *cb = buf->closure;
int zstatus;
zstatus = inflateEnd (&cb->zstr);
if (zstatus != Z_OK)
{
compress_error (0, zstatus, &cb->zstr, "inflateEnd");
return EIO;
}
return buf_shutdown (cb->buf);
}
static int
compress_buffer_shutdown_output (struct buffer *buf)
{
struct compress_buffer *cb = buf->closure;
int zstatus, status;
static char *buffer = NULL;
if (!buffer)
buffer = pagealign_xalloc (BUFFER_DATA_SIZE);
do
{
cb->zstr.avail_out = BUFFER_DATA_SIZE;
cb->zstr.next_out = (unsigned char *) buffer;
zstatus = deflate (&cb->zstr, Z_FINISH);
if (zstatus != Z_OK && zstatus != Z_STREAM_END)
{
compress_error (0, zstatus, &cb->zstr, "deflate finish");
return EIO;
}
if (cb->zstr.avail_out != BUFFER_DATA_SIZE)
buf_output (cb->buf, buffer,
BUFFER_DATA_SIZE - cb->zstr.avail_out);
} while (zstatus != Z_STREAM_END);
zstatus = deflateEnd (&cb->zstr);
if (zstatus != Z_OK)
{
compress_error (0, zstatus, &cb->zstr, "deflateEnd");
return EIO;
}
status = buf_flush (cb->buf, 1);
if (status != 0)
return status;
return buf_shutdown (cb->buf);
}
#define GZIP_ID1 31
#define GZIP_ID2 139
#define GZIP_CDEFLATE 8
#define GZIP_FTEXT 1
#define GZIP_FHCRC 2
#define GZIP_FEXTRA 4
#define GZIP_FNAME 8
#define GZIP_FCOMMENT 16
int
gunzip_and_write (int fd, const char *fullname, unsigned char *buf,
size_t size)
{
size_t pos;
z_stream zstr;
int zstatus;
unsigned char outbuf[32768];
unsigned long crc;
if (size < 10)
{
error (0, 0, "gzipped data too small - lacks complete header");
return 1;
}
if (buf[0] != GZIP_ID1 || buf[1] != GZIP_ID2)
{
error (0, 0, "gzipped data does not start with gzip identification");
return 1;
}
if (buf[2] != GZIP_CDEFLATE)
{
error (0, 0, "only the deflate compression method is supported");
return 1;
}
pos = 10;
if (buf[3] & GZIP_FEXTRA)
{
if (pos + 2 >= size)
{
error (0, 0, "%s lacks proper gzip XLEN field", fullname);
return 1;
}
pos += buf[pos] + (buf[pos + 1] << 8) + 2;
if (pos > size)
{
error (0, 0, "%s lacks proper gzip \"extra field\"", fullname);
return 1;
}
}
if (buf[3] & GZIP_FNAME)
{
unsigned char *p = memchr(buf + pos, '\0', size - pos);
if (p == NULL)
{
error (0, 0, "%s has bad gzip filename field", fullname);
return 1;
}
pos = p - buf + 1;
}
if (buf[3] & GZIP_FCOMMENT)
{
unsigned char *p = memchr(buf + pos, '\0', size - pos);
if (p == NULL)
{
error (0, 0, "%s has bad gzip comment field", fullname);
return 1;
}
pos = p - buf + 1;
}
if (buf[3] & GZIP_FHCRC)
{
pos += 2;
if (pos > size)
{
error (0, 0, "%s has bad gzip CRC16 field", fullname);
return 1;
}
}
if (pos >= size)
{
error (0, 0, "gzip data incomplete for %s (no data)", fullname);
return 1;
}
memset (&zstr, 0, sizeof zstr);
zstatus = inflateInit2 (&zstr, -15);
if (zstatus != Z_OK)
compress_error (1, zstatus, &zstr, fullname);
zstr.avail_in = size - pos;
zstr.next_in = buf + pos;
crc = crc32 (0, NULL, 0);
do
{
zstr.avail_out = sizeof (outbuf);
zstr.next_out = outbuf;
zstatus = inflate (&zstr, Z_NO_FLUSH);
if (zstatus != Z_STREAM_END && zstatus != Z_OK)
{
compress_error (0, zstatus, &zstr, fullname);
return 1;
}
if (write (fd, outbuf, sizeof (outbuf) - zstr.avail_out) < 0)
{
error (0, errno, "writing decompressed file %s", fullname);
return 1;
}
crc = crc32 (crc, outbuf, sizeof (outbuf) - zstr.avail_out);
} while (zstatus != Z_STREAM_END);
zstatus = inflateEnd (&zstr);
if (zstatus != Z_OK)
compress_error (0, zstatus, &zstr, fullname);
pos += zstr.total_in;
if (size - pos != 8)
{
error (0, 0, "gzip data incomplete for %s (no trailer)", fullname);
return 1;
}
if (crc != ((unsigned long)buf[pos]
+ ((unsigned long)buf[pos + 1] << 8)
+ ((unsigned long)buf[pos + 2] << 16)
+ ((unsigned long)buf[pos + 3] << 24)))
{
error (0, 0, "CRC error uncompressing %s", fullname);
return 1;
}
if (zstr.total_out != ((unsigned long)buf[pos + 4]
+ ((unsigned long)buf[pos + 5] << 8)
+ ((unsigned long)buf[pos + 6] << 16)
+ ((unsigned long)buf[pos + 7] << 24)))
{
error (0, 0, "invalid length uncompressing %s", fullname);
return 1;
}
return 0;
}
int
read_and_gzip (int fd, const char *fullname, unsigned char **buf, size_t *size,
size_t *len, int level)
{
z_stream zstr;
int zstatus;
unsigned char inbuf[8192];
int nread;
unsigned long crc;
if (*size < 1024)
{
unsigned char *newbuf;
*size = 1024;
newbuf = xrealloc (*buf, *size);
if (newbuf == NULL)
{
error (0, 0, "out of memory");
return 1;
}
*buf = newbuf;
}
(*buf)[0] = GZIP_ID1;
(*buf)[1] = GZIP_ID2;
(*buf)[2] = GZIP_CDEFLATE;
(*buf)[3] = 0;
(*buf)[4] = (*buf)[5] = (*buf)[6] = (*buf)[7] = 0;
(*buf)[8] = 0;
(*buf)[9] = 255;
memset (&zstr, 0, sizeof zstr);
zstatus = deflateInit2 (&zstr, level, Z_DEFLATED, -15, 8,
Z_DEFAULT_STRATEGY);
crc = crc32 (0, NULL, 0);
if (zstatus != Z_OK)
{
compress_error (0, zstatus, &zstr, fullname);
return 1;
}
zstr.total_out = 10;
zstr.avail_out = *size - 10;
zstr.next_out = *buf + 10;
while (1)
{
int finish = 0;
nread = read (fd, inbuf, sizeof inbuf);
if (nread < 0)
{
error (0, errno, "cannot read %s", fullname);
return 1;
}
else if (nread == 0)
finish = 1;
crc = crc32 (crc, inbuf, nread);
zstr.next_in = inbuf;
zstr.avail_in = nread;
do
{
if (zstr.avail_out < 4096)
{
unsigned char *newbuf;
assert(zstr.avail_out + zstr.total_out == *size);
assert(zstr.next_out == *buf + zstr.total_out);
*size *= 2;
newbuf = xrealloc (*buf, *size);
if (newbuf == NULL)
{
error (0, 0, "out of memory");
return 1;
}
*buf = newbuf;
zstr.next_out = *buf + zstr.total_out;
zstr.avail_out = *size - zstr.total_out;
assert(zstr.avail_out + zstr.total_out == *size);
assert(zstr.next_out == *buf + zstr.total_out);
}
zstatus = deflate (&zstr, finish ? Z_FINISH : 0);
if (zstatus == Z_STREAM_END)
goto done;
else if (zstatus != Z_OK)
compress_error (0, zstatus, &zstr, fullname);
} while (zstr.avail_out == 0);
}
done:
if (zstr.avail_out < 8)
{
unsigned char *newbuf;
assert(zstr.avail_out + zstr.total_out == *size);
assert(zstr.next_out == *buf + zstr.total_out);
*size += 8 - zstr.avail_out;
newbuf = realloc (*buf, *size);
if (newbuf == NULL)
{
error (0, 0, "out of memory");
return 1;
}
*buf = newbuf;
zstr.next_out = *buf + zstr.total_out;
zstr.avail_out = *size - zstr.total_out;
assert(zstr.avail_out + zstr.total_out == *size);
assert(zstr.next_out == *buf + zstr.total_out);
}
*zstr.next_out++ = (unsigned char)(crc & 0xff);
*zstr.next_out++ = (unsigned char)((crc >> 8) & 0xff);
*zstr.next_out++ = (unsigned char)((crc >> 16) & 0xff);
*zstr.next_out++ = (unsigned char)((crc >> 24) & 0xff);
*zstr.next_out++ = (unsigned char)(zstr.total_in & 0xff);
*zstr.next_out++ = (unsigned char)((zstr.total_in >> 8) & 0xff);
*zstr.next_out++ = (unsigned char)((zstr.total_in >> 16) & 0xff);
*zstr.next_out++ = (unsigned char)((zstr.total_in >> 24) & 0xff);
zstr.total_out += 8;
zstr.avail_out -= 8;
assert(zstr.avail_out + zstr.total_out == *size);
assert(zstr.next_out == *buf + zstr.total_out);
*len = zstr.total_out;
zstatus = deflateEnd (&zstr);
if (zstatus != Z_OK)
compress_error (0, zstatus, &zstr, fullname);
return 0;
}
#endif