1024 lines
26 KiB
C
1024 lines
26 KiB
C
/* file_wrappers.c
|
|
*
|
|
* $Id$
|
|
*
|
|
* Wiretap Library
|
|
* Copyright (c) 1998 by Gilbert Ramirez <gram@alumni.rice.edu>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
/* file_access interface based heavily on zlib gzread.c and gzlib.c from zlib
|
|
* Copyright (C) 1995-2010 Jean-loup Gailly and Mark Adler
|
|
* under licence:
|
|
*
|
|
* This software is provided 'as-is', without any express or implied
|
|
* warranty. In no event will the authors be held liable for any damages
|
|
* arising from the use of this software.
|
|
*
|
|
* Permission is granted to anyone to use this software for any purpose,
|
|
* including commercial applications, and to alter it and redistribute it
|
|
* freely, subject to the following restrictions:
|
|
*
|
|
* 1. The origin of this software must not be misrepresented; you must not
|
|
* claim that you wrote the original software. If you use this software
|
|
* in a product, an acknowledgment in the product documentation would be
|
|
* appreciated but is not required.
|
|
* 2. Altered source versions must be plainly marked as such, and must not be
|
|
* misrepresented as being the original software.
|
|
* 3. This notice may not be removed or altered from any source distribution.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif /* HAVE_UNISTD_H */
|
|
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#ifdef HAVE_FCNTL_H
|
|
#include <fcntl.h>
|
|
#endif /* HAVE_FCNTL_H */
|
|
#include <string.h>
|
|
#include "wtap-int.h"
|
|
#include "file_wrappers.h"
|
|
#include <wsutil/file_util.h>
|
|
|
|
/*
|
|
* See RFC 1952 for a description of the gzip file format.
|
|
*
|
|
* Some other compressed file formats we might want to support:
|
|
*
|
|
* XZ format: http://tukaani.org/xz/
|
|
*
|
|
* Bzip2 format: http://bzip.org/
|
|
*/
|
|
|
|
/* #define GZBUFSIZE 8192 */
|
|
#define GZBUFSIZE 4096
|
|
|
|
struct wtap_reader {
|
|
int fd; /* file descriptor */
|
|
gint64 pos; /* current position in uncompressed data */
|
|
unsigned size; /* buffer size */
|
|
unsigned char *in; /* input buffer */
|
|
unsigned char *out; /* output buffer (double-sized when reading) */
|
|
unsigned char *next; /* next output data to deliver or write */
|
|
|
|
unsigned have; /* amount of output data unused at next */
|
|
int eof; /* true if end of input file reached */
|
|
gint64 start; /* where the gzip data started, for rewinding */
|
|
gint64 raw; /* where the raw data started, for seeking */
|
|
int compression; /* 0: ?, 1: uncompressed, 2: zlib */
|
|
/* seek request */
|
|
gint64 skip; /* amount to skip (already rewound if backwards) */
|
|
int seek; /* true if seek request pending */
|
|
/* error information */
|
|
int err; /* error code */
|
|
|
|
unsigned int avail_in; /* number of bytes available at next_in */
|
|
unsigned char *next_in; /* next input byte */
|
|
#ifdef HAVE_LIBZ
|
|
/* zlib inflate stream */
|
|
z_stream strm; /* stream structure in-place (not a pointer) */
|
|
#endif
|
|
};
|
|
|
|
/* values for gz_state compression */
|
|
#define UNKNOWN 0 /* look for a gzip header */
|
|
#define UNCOMPRESSED 1 /* copy input directly */
|
|
#ifdef HAVE_LIBZ
|
|
#define ZLIB 2 /* decompress a zlib stream */
|
|
#endif
|
|
|
|
static int /* gz_load */
|
|
raw_read(FILE_T state, unsigned char *buf, unsigned int count, unsigned *have)
|
|
{
|
|
int ret;
|
|
|
|
*have = 0;
|
|
do {
|
|
ret = read(state->fd, buf + *have, count - *have);
|
|
if (ret <= 0)
|
|
break;
|
|
*have += ret;
|
|
} while (*have < count);
|
|
if (ret < 0) {
|
|
state->err = errno;
|
|
return -1;
|
|
}
|
|
if (ret == 0)
|
|
state->eof = 1;
|
|
return 0;
|
|
}
|
|
|
|
static int /* gz_avail */
|
|
fill_in_buffer(FILE_T state)
|
|
{
|
|
if (state->err)
|
|
return -1;
|
|
if (state->eof == 0) {
|
|
if (raw_read(state, state->in, state->size, (unsigned *)&(state->avail_in)) == -1)
|
|
return -1;
|
|
state->next_in = state->in;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#ifdef HAVE_LIBZ
|
|
|
|
/* Get next byte from input, or -1 if end or error. */
|
|
#define NEXT() ((state->avail_in == 0 && fill_in_buffer(state) == -1) ? -1 : \
|
|
(state->avail_in == 0 ? -1 : \
|
|
(state->avail_in--, *(state->next_in)++)))
|
|
|
|
/* Get a four-byte little-endian integer and return 0 on success and the value
|
|
in *ret. Otherwise -1 is returned and *ret is not modified. */
|
|
static int
|
|
gz_next4(FILE_T state, guint32 *ret)
|
|
{
|
|
guint32 val;
|
|
int ch;
|
|
|
|
val = NEXT();
|
|
val += (unsigned)NEXT() << 8;
|
|
val += (guint32)NEXT() << 16;
|
|
ch = NEXT();
|
|
if (ch == -1)
|
|
return -1;
|
|
val += (guint32)ch << 24;
|
|
*ret = val;
|
|
return 0;
|
|
}
|
|
|
|
static int /* gz_decomp */
|
|
zlib_read(FILE_T state, unsigned char *buf, unsigned int count)
|
|
{
|
|
int ret;
|
|
guint32 crc, len;
|
|
z_streamp strm = &(state->strm);
|
|
|
|
strm->avail_out = count;
|
|
strm->next_out = buf;
|
|
|
|
/* fill output buffer up to end of deflate stream */
|
|
do {
|
|
/* get more input for inflate() */
|
|
if (state->avail_in == 0 && fill_in_buffer(state) == -1)
|
|
return -1;
|
|
if (state->avail_in == 0) {
|
|
state->err = WTAP_ERR_ZLIB + Z_DATA_ERROR;
|
|
return -1;
|
|
}
|
|
|
|
strm->avail_in = state->avail_in;
|
|
strm->next_in = state->next_in;
|
|
/* decompress and handle errors */
|
|
ret = inflate(strm, Z_NO_FLUSH);
|
|
state->avail_in = strm->avail_in;
|
|
state->next_in = strm->next_in;
|
|
if (ret == Z_STREAM_ERROR || ret == Z_NEED_DICT) {
|
|
state->err = WTAP_ERR_ZLIB + Z_STREAM_ERROR;
|
|
return -1;
|
|
}
|
|
if (ret == Z_MEM_ERROR) {
|
|
state->err = WTAP_ERR_ZLIB + Z_MEM_ERROR; /* ENOMEM? */
|
|
return -1;
|
|
}
|
|
if (ret == Z_DATA_ERROR) { /* deflate stream invalid */
|
|
state->err = WTAP_ERR_ZLIB + Z_DATA_ERROR;
|
|
return -1;
|
|
}
|
|
} while (strm->avail_out && ret != Z_STREAM_END);
|
|
|
|
/* update available output and crc check value */
|
|
state->next = buf;
|
|
state->have = count - strm->avail_out;
|
|
strm->adler = crc32(strm->adler, state->next, state->have);
|
|
|
|
/* check gzip trailer if at end of deflate stream */
|
|
if (ret == Z_STREAM_END) {
|
|
if (gz_next4(state, &crc) == -1 || gz_next4(state, &len) == -1) {
|
|
state->err = WTAP_ERR_ZLIB + Z_DATA_ERROR;
|
|
return -1;
|
|
}
|
|
if (crc != strm->adler) {
|
|
state->err = WTAP_ERR_ZLIB + Z_DATA_ERROR;
|
|
return -1;
|
|
}
|
|
if (len != (strm->total_out & 0xffffffffL)) {
|
|
state->err = WTAP_ERR_ZLIB + Z_DATA_ERROR;
|
|
return -1;
|
|
}
|
|
state->compression = UNKNOWN; /* ready for next stream, once have is 0 */
|
|
}
|
|
|
|
/* good decompression */
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int
|
|
gz_head(FILE_T state)
|
|
{
|
|
/* get some data in the input buffer */
|
|
if (state->avail_in == 0) {
|
|
if (fill_in_buffer(state) == -1)
|
|
return -1;
|
|
if (state->avail_in == 0)
|
|
return 0;
|
|
}
|
|
|
|
/* look for the gzip magic header bytes 31 and 139 */
|
|
#ifdef HAVE_LIBZ
|
|
if (state->next_in[0] == 31) {
|
|
state->avail_in--;
|
|
state->next_in++;
|
|
if (state->avail_in == 0 && fill_in_buffer(state) == -1)
|
|
return -1;
|
|
if (state->avail_in && state->next_in[0] == 139) {
|
|
unsigned len;
|
|
int flags;
|
|
|
|
/* we have a gzip header, woo hoo! */
|
|
state->avail_in--;
|
|
state->next_in++;
|
|
|
|
/* skip rest of header */
|
|
if (NEXT() != 8) { /* compression method */
|
|
state->err = WTAP_ERR_ZLIB + Z_DATA_ERROR;
|
|
return -1;
|
|
}
|
|
flags = NEXT();
|
|
if (flags & 0xe0) { /* reserved flag bits */
|
|
state->err = WTAP_ERR_ZLIB + Z_DATA_ERROR;
|
|
return -1;
|
|
}
|
|
NEXT(); /* modification time */
|
|
NEXT();
|
|
NEXT();
|
|
NEXT();
|
|
NEXT(); /* extra flags */
|
|
NEXT(); /* operating system */
|
|
if (flags & 4) { /* extra field */
|
|
len = (unsigned)NEXT();
|
|
len += (unsigned)NEXT() << 8;
|
|
while (len--)
|
|
if (NEXT() < 0)
|
|
break;
|
|
}
|
|
if (flags & 8) /* file name */
|
|
while (NEXT() > 0)
|
|
;
|
|
if (flags & 16) /* comment */
|
|
while (NEXT() > 0)
|
|
;
|
|
if (flags & 2) { /* header crc */
|
|
NEXT();
|
|
NEXT();
|
|
}
|
|
/* an unexpected end of file is not checked for here -- it will be
|
|
noticed on the first request for uncompressed data */
|
|
|
|
/* set up for decompression */
|
|
inflateReset(&(state->strm));
|
|
state->strm.adler = crc32(0L, Z_NULL, 0);
|
|
state->compression = ZLIB;
|
|
return 0;
|
|
}
|
|
else {
|
|
/* not a gzip file -- save first byte (31) and fall to raw i/o */
|
|
state->out[0] = 31;
|
|
state->have = 1;
|
|
}
|
|
}
|
|
#endif
|
|
#ifdef HAVE_LIBXZ
|
|
/* { 0xFD, '7', 'z', 'X', 'Z', 0x00 } */
|
|
/* FD 37 7A 58 5A 00 */
|
|
#endif
|
|
|
|
/* doing raw i/o, save start of raw data for seeking, copy any leftover
|
|
input to output -- this assumes that the output buffer is larger than
|
|
the input buffer, which also assures space for gzungetc() */
|
|
state->raw = state->pos;
|
|
state->next = state->out;
|
|
if (state->avail_in) {
|
|
memcpy(state->next + state->have, state->next_in, state->avail_in);
|
|
state->have += state->avail_in;
|
|
state->avail_in = 0;
|
|
}
|
|
state->compression = UNCOMPRESSED;
|
|
return 0;
|
|
}
|
|
|
|
static int /* gz_make */
|
|
fill_out_buffer(FILE_T state)
|
|
{
|
|
if (state->compression == UNKNOWN) { /* look for gzip header */
|
|
if (gz_head(state) == -1)
|
|
return -1;
|
|
if (state->have) /* got some data from gz_head() */
|
|
return 0;
|
|
}
|
|
if (state->compression == UNCOMPRESSED) { /* straight copy */
|
|
if (raw_read(state, state->out, state->size /* << 1 */, &(state->have)) == -1)
|
|
return -1;
|
|
state->next = state->out;
|
|
}
|
|
#ifdef HAVE_LIBZ
|
|
else if (state->compression == ZLIB) { /* decompress */
|
|
if (zlib_read(state, state->out, state->size << 1) == -1)
|
|
return -1;
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
gz_skip(FILE_T state, gint64 len)
|
|
{
|
|
unsigned n;
|
|
|
|
/* skip over len bytes or reach end-of-file, whichever comes first */
|
|
while (len)
|
|
/* skip over whatever is in output buffer */
|
|
if (state->have) {
|
|
n = (gint64)state->have > len ? (unsigned)len : state->have;
|
|
state->have -= n;
|
|
state->next += n;
|
|
state->pos += n;
|
|
len -= n;
|
|
}
|
|
|
|
/* output buffer empty -- return if we're at the end of the input */
|
|
else if (state->eof && state->avail_in == 0)
|
|
break;
|
|
|
|
/* need more data to skip -- load up output buffer */
|
|
else {
|
|
/* get more output, looking for header if required */
|
|
if (fill_out_buffer(state) == -1)
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
gz_reset(FILE_T state)
|
|
{
|
|
state->have = 0; /* no output data available */
|
|
state->eof = 0; /* not at end of file */
|
|
state->compression = UNKNOWN; /* look for gzip header */
|
|
|
|
state->seek = 0; /* no seek request pending */
|
|
state->err = 0; /* clear error */
|
|
state->pos = 0; /* no uncompressed data yet */
|
|
state->avail_in = 0; /* no input data yet */
|
|
}
|
|
|
|
FILE_T
|
|
filed_open(int fd)
|
|
{
|
|
#ifdef _STATBUF_ST_BLKSIZE /* XXX, _STATBUF_ST_BLKSIZE portable? */
|
|
struct stat st;
|
|
#endif
|
|
int want = GZBUFSIZE;
|
|
FILE_T state;
|
|
|
|
if (fd == -1)
|
|
return NULL;
|
|
|
|
/* allocate gzFile structure to return */
|
|
state = g_try_malloc(sizeof *state);
|
|
if (state == NULL)
|
|
return NULL;
|
|
|
|
/* open the file with the appropriate mode (or just use fd) */
|
|
state->fd = fd;
|
|
|
|
/* save the current position for rewinding (only if reading) */
|
|
state->start = ws_lseek64(state->fd, 0, SEEK_CUR);
|
|
if (state->start == -1) state->start = 0;
|
|
|
|
/* initialize stream */
|
|
gz_reset(state);
|
|
|
|
#ifdef _STATBUF_ST_BLKSIZE
|
|
if (fstat(fd, &st) >= 0) {
|
|
want = st.st_blksize;
|
|
/* XXX, verify result? */
|
|
}
|
|
#endif
|
|
|
|
/* allocate buffers */
|
|
state->in = g_try_malloc(want);
|
|
state->out = g_try_malloc(want << 1);
|
|
state->size = want;
|
|
if (state->in == NULL || state->out == NULL) {
|
|
g_free(state->out);
|
|
g_free(state->in);
|
|
g_free(state);
|
|
errno = ENOMEM;
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef HAVE_LIBZ
|
|
/* allocate inflate memory */
|
|
state->strm.zalloc = Z_NULL;
|
|
state->strm.zfree = Z_NULL;
|
|
state->strm.opaque = Z_NULL;
|
|
state->strm.avail_in = 0;
|
|
state->strm.next_in = Z_NULL;
|
|
if (inflateInit2(&(state->strm), -15) != Z_OK) { /* raw inflate */
|
|
g_free(state->out);
|
|
g_free(state->in);
|
|
g_free(state);
|
|
errno = ENOMEM;
|
|
return NULL;
|
|
}
|
|
#endif
|
|
gz_head(state); /* read first chunk */
|
|
|
|
/* return stream */
|
|
return state;
|
|
}
|
|
|
|
FILE_T
|
|
file_open(const char *path)
|
|
{
|
|
int fd;
|
|
FILE_T ft;
|
|
int oflag;
|
|
|
|
/* O_LARGEFILE? */
|
|
oflag = O_RDONLY;
|
|
#ifdef _WIN32
|
|
oflag |= O_BINARY;
|
|
#endif
|
|
|
|
/* open file and do correct filename conversions */
|
|
if ((fd = ws_open(path, oflag, 0666)) == -1)
|
|
return NULL;
|
|
|
|
/* open file handle */
|
|
ft = filed_open(fd);
|
|
if (ft == NULL) {
|
|
ws_close(fd);
|
|
return NULL;
|
|
}
|
|
|
|
return ft;
|
|
}
|
|
|
|
gint64
|
|
file_seek(FILE_T file, gint64 offset, int whence, int *err)
|
|
{
|
|
unsigned n;
|
|
|
|
/* check that there's no error */
|
|
if (file->err) {
|
|
*err = file->err;
|
|
return -1;
|
|
}
|
|
|
|
/* can only seek from start or relative to current position */
|
|
if (whence != SEEK_SET && whence != SEEK_CUR) {
|
|
g_assert_not_reached();
|
|
/*
|
|
*err = EINVAL;
|
|
return -1;
|
|
*/
|
|
}
|
|
|
|
/* normalize offset to a SEEK_CUR specification */
|
|
if (whence == SEEK_SET)
|
|
offset -= file->pos;
|
|
else if (file->seek)
|
|
offset += file->skip;
|
|
file->seek = 0;
|
|
|
|
/* if within raw area while reading, just go there */
|
|
if (file->compression == UNCOMPRESSED && file->pos + offset >= file->raw) {
|
|
if (ws_lseek64(file->fd, offset - file->have, SEEK_CUR) == -1) {
|
|
*err = errno;
|
|
return -1;
|
|
}
|
|
file->have = 0;
|
|
file->eof = 0;
|
|
file->seek = 0;
|
|
file->err = 0;
|
|
file->avail_in = 0;
|
|
file->pos += offset;
|
|
return file->pos;
|
|
}
|
|
|
|
/* calculate skip amount, rewinding if needed for back seek when reading */
|
|
if (offset < 0) {
|
|
offset += file->pos;
|
|
if (offset < 0) { /* before start of file! */
|
|
/* *err = ???; */
|
|
return -1;
|
|
}
|
|
/* rewind, then skip to offset */
|
|
|
|
/* back up and start over */
|
|
if (ws_lseek64(file->fd, file->start, SEEK_SET) == -1) {
|
|
*err = errno;
|
|
return -1;
|
|
}
|
|
gz_reset(file);
|
|
}
|
|
|
|
/* skip what's in output buffer (one less gzgetc() check) */
|
|
n = (gint64)file->have > offset ? (unsigned)offset : file->have;
|
|
file->have -= n;
|
|
file->next += n;
|
|
file->pos += n;
|
|
offset -= n;
|
|
|
|
/* request skip (if not zero) */
|
|
if (offset) {
|
|
file->seek = 1;
|
|
file->skip = offset;
|
|
}
|
|
return file->pos + offset;
|
|
}
|
|
|
|
gint64
|
|
file_tell(FILE_T stream)
|
|
{
|
|
/* return position */
|
|
return stream->pos + (stream->seek ? stream->skip : 0);
|
|
}
|
|
|
|
int
|
|
file_read(void *buf, unsigned int len, FILE_T file)
|
|
{
|
|
unsigned got, n;
|
|
|
|
/* check that we're reading and that there's no error */
|
|
if (file->err)
|
|
return -1;
|
|
|
|
/* if len is zero, avoid unnecessary operations */
|
|
if (len == 0)
|
|
return 0;
|
|
|
|
/* process a skip request */
|
|
if (file->seek) {
|
|
file->seek = 0;
|
|
if (gz_skip(file, file->skip) == -1)
|
|
return -1;
|
|
}
|
|
|
|
/* get len bytes to buf, or less than len if at the end */
|
|
got = 0;
|
|
do {
|
|
/* first just try copying data from the output buffer */
|
|
if (file->have) {
|
|
n = file->have > len ? len : file->have;
|
|
memcpy(buf, file->next, n);
|
|
file->next += n;
|
|
file->have -= n;
|
|
}
|
|
|
|
/* output buffer empty -- return if we're at the end of the input */
|
|
else if (file->eof && file->avail_in == 0)
|
|
break;
|
|
|
|
/* need output data -- for small len or new stream load up our output buffer */
|
|
else if (file->compression == UNKNOWN || len < (file->size << 1)) {
|
|
/* get more output, looking for header if required */
|
|
if (fill_out_buffer(file) == -1)
|
|
return -1;
|
|
continue; /* no progress yet -- go back to memcpy() above */
|
|
|
|
} else if (file->compression == UNCOMPRESSED) { /* large len -- read directly into user buffer */
|
|
if (raw_read(file, buf, len, &n) == -1)
|
|
return -1;
|
|
}
|
|
#ifdef HAVE_LIBZ
|
|
/* large len -- decompress directly into user buffer */
|
|
else { /* file->compression == ZLIB */
|
|
if (zlib_read(file, buf, len) == -1)
|
|
return -1;
|
|
n = file->have;
|
|
file->have = 0;
|
|
}
|
|
#endif
|
|
/* update progress */
|
|
len -= n;
|
|
buf = (char *)buf + n;
|
|
got += n;
|
|
file->pos += n;
|
|
} while (len);
|
|
|
|
return (int)got;
|
|
}
|
|
|
|
int
|
|
file_getc(FILE_T file)
|
|
{
|
|
unsigned char buf[1];
|
|
int ret;
|
|
|
|
/* check that we're reading and that there's no error */
|
|
if (file->err)
|
|
return -1;
|
|
|
|
/* try output buffer (no need to check for skip request) */
|
|
if (file->have) {
|
|
file->have--;
|
|
file->pos++;
|
|
return *(file->next)++;
|
|
}
|
|
|
|
ret = file_read(buf, 1, file);
|
|
return ret < 1 ? -1 : buf[0];
|
|
}
|
|
|
|
char *
|
|
file_gets(char *buf, int len, FILE_T file)
|
|
{
|
|
unsigned left, n;
|
|
char *str;
|
|
unsigned char *eol;
|
|
|
|
/* check parameters */
|
|
if (buf == NULL || len < 1)
|
|
return NULL;
|
|
|
|
/* check that there's no error */
|
|
if (file->err)
|
|
return NULL;
|
|
|
|
/* process a skip request */
|
|
if (file->seek) {
|
|
file->seek = 0;
|
|
if (gz_skip(file, file->skip) == -1)
|
|
return NULL;
|
|
}
|
|
|
|
/* copy output bytes up to new line or len - 1, whichever comes first --
|
|
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 {
|
|
/* assure that something is in the output buffer */
|
|
if (file->have == 0) {
|
|
if (fill_out_buffer(file) == -1)
|
|
return NULL; /* error */
|
|
if (file->have == 0) { /* end of file */
|
|
if (buf == str) /* got bupkus */
|
|
return NULL;
|
|
break; /* got something -- return it */
|
|
}
|
|
}
|
|
|
|
/* look for end-of-line in current output buffer */
|
|
n = file->have > left ? left : file->have;
|
|
eol = memchr(file->next, '\n', n);
|
|
if (eol != NULL)
|
|
n = (unsigned)(eol - file->next) + 1;
|
|
|
|
/* copy through end-of-line, or remainder if not found */
|
|
memcpy(buf, file->next, n);
|
|
file->have -= n;
|
|
file->next += n;
|
|
file->pos += n;
|
|
left -= n;
|
|
buf += n;
|
|
} while (left && eol == NULL);
|
|
|
|
/* found end-of-line or out of space -- terminate string and return it */
|
|
buf[0] = 0;
|
|
return str;
|
|
}
|
|
|
|
int
|
|
file_eof(FILE_T file)
|
|
{
|
|
/* return end-of-file state */
|
|
return (file->eof && file->avail_in == 0 && file->have == 0);
|
|
}
|
|
|
|
/*
|
|
* Routine to return a Wiretap error code (0 for no error, an errno
|
|
* for a file error, or a WTAP_ERR_ code for other errors) for an
|
|
* I/O stream.
|
|
*/
|
|
int
|
|
file_error(FILE_T fh)
|
|
{
|
|
return fh->err;
|
|
}
|
|
|
|
void
|
|
file_clearerr(FILE_T stream)
|
|
{
|
|
/* clear error and end-of-file */
|
|
stream->err = 0;
|
|
stream->eof = 0;
|
|
}
|
|
|
|
int
|
|
file_close(FILE_T file)
|
|
{
|
|
int fd = file->fd;
|
|
|
|
/* free memory and close file */
|
|
if (file->size) {
|
|
#ifdef HAVE_LIBZ
|
|
inflateEnd(&(file->strm));
|
|
#endif
|
|
g_free(file->out);
|
|
g_free(file->in);
|
|
}
|
|
file->err = 0;
|
|
g_free(file);
|
|
return close(fd);
|
|
}
|
|
|
|
#ifdef HAVE_LIBZ
|
|
/* internal gzip file state data structure for writing */
|
|
struct wtap_writer {
|
|
int fd; /* file descriptor */
|
|
gint64 pos; /* current position in uncompressed data */
|
|
unsigned size; /* buffer size, zero if not allocated yet */
|
|
unsigned want; /* requested buffer size, default is GZBUFSIZE */
|
|
unsigned char *in; /* input buffer */
|
|
unsigned char *out; /* output buffer (double-sized when reading) */
|
|
unsigned char *next; /* next output data to deliver or write */
|
|
int level; /* compression level */
|
|
int strategy; /* compression strategy */
|
|
int err; /* error code */
|
|
/* zlib deflate stream */
|
|
z_stream strm; /* stream structure in-place (not a pointer) */
|
|
};
|
|
|
|
GZWFILE_T
|
|
gzwfile_open(const char *path)
|
|
{
|
|
int fd;
|
|
GZWFILE_T state;
|
|
int save_errno;
|
|
|
|
fd = ws_open(path, O_BINARY|O_WRONLY|O_CREAT|O_TRUNC, 0666);
|
|
if (fd == -1)
|
|
return NULL;
|
|
state = gzwfile_fdopen(fd);
|
|
if (state == NULL) {
|
|
save_errno = errno;
|
|
close(fd);
|
|
save_errno = errno;
|
|
}
|
|
return state;
|
|
}
|
|
|
|
GZWFILE_T
|
|
gzwfile_fdopen(int fd)
|
|
{
|
|
GZWFILE_T state;
|
|
|
|
/* allocate wtap_writer structure to return */
|
|
state = g_try_malloc(sizeof *state);
|
|
if (state == NULL)
|
|
return NULL;
|
|
state->fd = fd;
|
|
state->size = 0; /* no buffers allocated yet */
|
|
state->want = GZBUFSIZE; /* requested buffer size */
|
|
|
|
state->level = Z_DEFAULT_COMPRESSION;
|
|
state->strategy = Z_DEFAULT_STRATEGY;
|
|
|
|
/* initialize stream */
|
|
state->err = Z_OK; /* clear error */
|
|
state->pos = 0; /* no uncompressed data yet */
|
|
state->strm.avail_in = 0; /* no input data yet */
|
|
|
|
/* return stream */
|
|
return state;
|
|
}
|
|
|
|
/* Initialize state for writing a gzip file. Mark initialization by setting
|
|
state->size to non-zero. Return -1, and set state->err, on failure;
|
|
return 0 on success. */
|
|
static int
|
|
gz_init(GZWFILE_T state)
|
|
{
|
|
int ret;
|
|
z_streamp strm = &(state->strm);
|
|
|
|
/* allocate input and output buffers */
|
|
state->in = g_try_malloc(state->want);
|
|
state->out = g_try_malloc(state->want);
|
|
if (state->in == NULL || state->out == NULL) {
|
|
if (state->out != NULL)
|
|
g_free(state->out);
|
|
if (state->in != NULL)
|
|
g_free(state->in);
|
|
state->err = WTAP_ERR_ZLIB + Z_MEM_ERROR; /* ENOMEM? */
|
|
return -1;
|
|
}
|
|
|
|
/* allocate deflate memory, set up for gzip compression */
|
|
strm->zalloc = Z_NULL;
|
|
strm->zfree = Z_NULL;
|
|
strm->opaque = Z_NULL;
|
|
ret = deflateInit2(strm, state->level, Z_DEFLATED,
|
|
15 + 16, 8, state->strategy);
|
|
if (ret != Z_OK) {
|
|
g_free(state->in);
|
|
state->err = WTAP_ERR_ZLIB + Z_MEM_ERROR; /* ENOMEM? */
|
|
return -1;
|
|
}
|
|
|
|
/* mark state as initialized */
|
|
state->size = state->want;
|
|
|
|
/* initialize write buffer */
|
|
strm->avail_out = state->size;
|
|
strm->next_out = state->out;
|
|
state->next = strm->next_out;
|
|
return 0;
|
|
}
|
|
|
|
/* Compress whatever is at avail_in and next_in and write to the output file.
|
|
Return -1, and set state->err, if there is an error writing to the output
|
|
file; return 0 on success.
|
|
flush is assumed to be a valid deflate() flush value. If flush is Z_FINISH,
|
|
then the deflate() state is reset to start a new gzip stream. */
|
|
static int
|
|
gz_comp(GZWFILE_T state, int flush)
|
|
{
|
|
int ret, got;
|
|
unsigned have;
|
|
z_streamp strm = &(state->strm);
|
|
|
|
/* allocate memory if this is the first time through */
|
|
if (state->size == 0 && gz_init(state) == -1)
|
|
return -1;
|
|
|
|
/* run deflate() on provided input until it produces no more output */
|
|
ret = Z_OK;
|
|
do {
|
|
/* write out current buffer contents if full, or if flushing, but if
|
|
doing Z_FINISH then don't write until we get to Z_STREAM_END */
|
|
if (strm->avail_out == 0 || (flush != Z_NO_FLUSH &&
|
|
(flush != Z_FINISH || ret == Z_STREAM_END))) {
|
|
have = (unsigned)(strm->next_out - state->next);
|
|
if (have) {
|
|
got = write(state->fd, state->next, have);
|
|
if (got < 0) {
|
|
state->err = errno;
|
|
return -1;
|
|
}
|
|
if ((unsigned)got != have) {
|
|
state->err = WTAP_ERR_SHORT_WRITE;
|
|
return -1;
|
|
}
|
|
}
|
|
if (strm->avail_out == 0) {
|
|
strm->avail_out = state->size;
|
|
strm->next_out = state->out;
|
|
}
|
|
state->next = strm->next_out;
|
|
}
|
|
|
|
/* compress */
|
|
have = strm->avail_out;
|
|
ret = deflate(strm, flush);
|
|
if (ret == Z_STREAM_ERROR) {
|
|
state->err = WTAP_ERR_ZLIB + Z_STREAM_ERROR;
|
|
return -1;
|
|
}
|
|
have -= strm->avail_out;
|
|
} while (have);
|
|
|
|
/* if that completed a deflate stream, allow another to start */
|
|
if (flush == Z_FINISH)
|
|
deflateReset(strm);
|
|
|
|
/* all done, no errors */
|
|
return 0;
|
|
}
|
|
|
|
/* Write out len bytes from buf. Return 0, and set state->err, on
|
|
failure or on an attempt to write 0 bytes (in which case state->err
|
|
is Z_OK); return the number of bytes written on success. */
|
|
unsigned
|
|
gzwfile_write(GZWFILE_T state, const void *buf, unsigned len)
|
|
{
|
|
unsigned put = len;
|
|
unsigned n;
|
|
z_streamp strm;
|
|
|
|
strm = &(state->strm);
|
|
|
|
/* check that there's no error */
|
|
if (state->err != Z_OK)
|
|
return 0;
|
|
|
|
/* if len is zero, avoid unnecessary operations */
|
|
if (len == 0)
|
|
return 0;
|
|
|
|
/* allocate memory if this is the first time through */
|
|
if (state->size == 0 && gz_init(state) == -1)
|
|
return 0;
|
|
|
|
/* for small len, copy to input buffer, otherwise compress directly */
|
|
if (len < state->size) {
|
|
/* copy to input buffer, compress when full */
|
|
do {
|
|
if (strm->avail_in == 0)
|
|
strm->next_in = state->in;
|
|
n = state->size - strm->avail_in;
|
|
if (n > len)
|
|
n = len;
|
|
memcpy(strm->next_in + strm->avail_in, buf, n);
|
|
strm->avail_in += n;
|
|
state->pos += n;
|
|
buf = (char *)buf + n;
|
|
len -= n;
|
|
if (len && gz_comp(state, Z_NO_FLUSH) == -1)
|
|
return 0;
|
|
} while (len);
|
|
}
|
|
else {
|
|
/* consume whatever's left in the input buffer */
|
|
if (strm->avail_in && gz_comp(state, Z_NO_FLUSH) == -1)
|
|
return 0;
|
|
|
|
/* directly compress user buffer to file */
|
|
strm->avail_in = len;
|
|
strm->next_in = (voidp)buf;
|
|
state->pos += len;
|
|
if (gz_comp(state, Z_NO_FLUSH) == -1)
|
|
return 0;
|
|
}
|
|
|
|
/* input was all buffered or compressed (put will fit in int) */
|
|
return (int)put;
|
|
}
|
|
|
|
/* Flush out what we've written so far. Returns -1, and sets state->err,
|
|
on failure; returns 0 on success. */
|
|
int
|
|
gzwfile_flush(GZWFILE_T state)
|
|
{
|
|
/* check that there's no error */
|
|
if (state->err != Z_OK)
|
|
return -1;
|
|
|
|
/* compress remaining data with Z_SYNC_FLUSH */
|
|
gz_comp(state, Z_SYNC_FLUSH);
|
|
if (state->err != Z_OK)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
/* Flush out all data written, and close the file. Returns a Wiretap
|
|
error on failure; returns 0 on success. */
|
|
int
|
|
gzwfile_close(GZWFILE_T state)
|
|
{
|
|
int ret = 0;
|
|
|
|
/* flush, free memory, and close file */
|
|
if (gz_comp(state, Z_FINISH) == -1 && ret == 0)
|
|
ret = state->err;
|
|
(void)deflateEnd(&(state->strm));
|
|
g_free(state->out);
|
|
g_free(state->in);
|
|
state->err = Z_OK;
|
|
if (close(state->fd) == -1 && ret == 0)
|
|
ret = errno;
|
|
g_free(state);
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
gzwfile_geterr(GZWFILE_T state)
|
|
{
|
|
return state->err;
|
|
}
|
|
#endif
|