wireshark/ringbuffer.c
Gerald Combs 18061d6537 From Graeme Hewson:
It can sometimes happen that capturing is stopped just after Ethereal
  has switched to a new ring buffer.  The result is that no frames
  are displayed.  The patch to ringbuffer.c displays the previous ring
  buffer if the current buffer is empty on close.

  The patch to capture.c fixes a bug where an error return from
  ringbuf_wtap_dump_close was ignored, and tidies up the code around
  the call.

svn path=/trunk/; revision=6315
2002-09-22 16:17:41 +00:00

480 lines
13 KiB
C

/* ringbuffer.c
* Routines for packet capture windows
*
* $Id: ringbuffer.c,v 1.6 2002/09/22 16:17:41 gerald Exp $
*
* Ethereal - Network traffic analyzer
* By Gerald Combs <gerald@ethereal.com>
* Copyright 1998 Gerald Combs
*
* 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.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef HAVE_LIBPCAP
#ifdef HAVE_IO_H
#include <io.h>
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#ifdef NEED_SNPRINTF_H
#include "snprintf.h"
#endif
#include "wiretap/wtap.h"
#include "ringbuffer.h"
/* Win32 needs the O_BINARY flag for open() */
#ifndef O_BINARY
#define O_BINARY 0
#endif
/* Ringbuffer file structure */
typedef struct _rb_file {
gchar* name;
int fd;
time_t creation_time;
gboolean is_new;
guint16 number;
wtap_dumper* pdh;
long start_pos;
} rb_file;
/* Ringbuffer data structure */
typedef struct _ringbuf_data {
rb_file* files;
guint num_files; /* Number of ringbuffer files */
guint curr_file_num; /* Number of the current file */
gchar* fprefix; /* Filename prefix */
gchar* fsuffix; /* Filename suffix */
} ringbuf_data;
/* Create the ringbuffer data structure */
static ringbuf_data rb_data;
/*
* Initialize the ringbuffer data structure
*/
int
ringbuf_init(const char *capfile_name, guint num_files)
{
int save_file_fd;
unsigned int i;
char *pfx;
gchar *save_file;
char save_file_num[3+1];
/* just to be sure ... */
if (num_files <= RINGBUFFER_MAX_NUM_FILES) {
rb_data.num_files = num_files;
} else {
rb_data.num_files = RINGBUFFER_MAX_NUM_FILES;
}
/* Check file name */
if (capfile_name == NULL) {
/* ringbuffer does not work with temporary files! */
return -1;
}
/* Open the initial file */
save_file_fd = open(capfile_name, O_RDWR|O_BINARY|O_TRUNC|O_CREAT, 0600);
if (save_file_fd == -1) {
/* open failed */
return -1;
}
/* allocate memory */
rb_data.files = (rb_file *)calloc(num_files, sizeof(rb_file));
if (rb_data.files == NULL) {
/* could not allocate memory */
return -1;
}
/* initialize */
rb_data.fprefix = NULL;
rb_data.fsuffix = NULL;
for (i=0; i<rb_data.num_files; i++) {
rb_data.files[i].name = NULL;
rb_data.files[i].fd = -1;
}
/* get file name prefix/suffix */
save_file = g_strdup(capfile_name);
pfx = strrchr(save_file,'.');
if (pfx != NULL) {
pfx[0] = '\0';
rb_data.fprefix = g_strdup(save_file);
pfx[0] = '.'; /* restore capfile_name */
rb_data.fsuffix = g_strdup(pfx);
} else {
rb_data.fprefix = g_strdup(save_file);
rb_data.fsuffix = NULL;
}
g_free(save_file);
save_file = NULL;
#ifdef _WIN32
_tzset();
#endif
/* save the initial file parameters */
rb_data.files[0].name = g_strdup(capfile_name);
rb_data.files[0].fd = save_file_fd;
rb_data.files[0].creation_time = time(NULL);
rb_data.files[0].number = 0;
rb_data.files[0].is_new = TRUE;
/* create the other files */
for (i=1; i<rb_data.num_files; i++) {
/* create a file name */
snprintf(save_file_num,3+1,"%03d",i);
save_file = g_strconcat(capfile_name, ".", save_file_num, NULL);
/* open the file */
save_file_fd = open(save_file, O_RDWR|O_BINARY|O_TRUNC|O_CREAT, 0600);
if (save_file_fd != -1) {
rb_data.files[i].name = save_file;
rb_data.files[i].fd = save_file_fd;
rb_data.files[i].creation_time = time(NULL);
rb_data.files[i].number = i;
rb_data.files[i].is_new = TRUE;
} else {
/* could not open a file */
ringbuf_error_cleanup();
return -1;
}
}
/* done */
rb_data.curr_file_num = 0;
return rb_data.files[0].fd;
}
/*
* Calls wtap_dump_fdopen() for all ringbuffer files
*/
wtap_dumper*
ringbuf_init_wtap_dump_fdopen(int filetype, int linktype,
int snaplen, int *err)
{
unsigned int i;
FILE *fh;
for (i=0; i<rb_data.num_files; i++) {
rb_data.files[i].pdh = wtap_dump_fdopen(rb_data.files[i].fd, filetype,
linktype, snaplen, err);
if (rb_data.files[i].pdh == NULL) {
/* could not open file */
return NULL;
} else {
/*
* Flush out the capture file header, as written by "wtap_dump_fdopen()".
*
* XXX - this relies on Wiretap writing out data sequentially,
* and writing the entire capture file header when the file
* is created. That happens to be true for libpcap files,
* which are Ethereal's native capture files, and which are
* therefore the capture file types we're writing, but is not
* true for all the capture file types Wiretap can write.
*/
fh = wtap_dump_file(rb_data.files[i].pdh);
if (fflush(fh) == EOF) {
if (err != NULL) {
*err = errno;
}
return NULL;
}
if ((rb_data.files[i].start_pos = ftell(fh)) < 0) {
if (err != NULL) {
*err = errno;
}
return NULL;
}
clearerr(fh);
}
}
/* done */
rb_data.files[0].is_new = FALSE;
return rb_data.files[0].pdh;
}
/*
* Switches to the next ringbuffer file
*/
gboolean
ringbuf_switch_file(capture_file *cf, wtap_dumper **pdh, int *err)
{
int next_file_num;
FILE *fh;
gboolean err_on_next = FALSE;
/* flush the current file */
fh = wtap_dump_file(rb_data.files[rb_data.curr_file_num].pdh);
clearerr(fh);
errno = WTAP_ERR_CANT_CLOSE;
if (fflush(fh) == EOF) {
if (err != NULL) {
*err = errno;
}
return FALSE;
}
/* get the next file number */
next_file_num = (rb_data.curr_file_num + 1) % rb_data.num_files;
/* prepare the file if it was already used */
if (!rb_data.files[next_file_num].is_new) {
/* rewind to the position after the file header */
fh = wtap_dump_file(rb_data.files[next_file_num].pdh);
if (fseek(fh, rb_data.files[next_file_num].start_pos, SEEK_SET) < 0) {
*err = errno;
/* Don't return straight away: have caller report correct save_file */
err_on_next = TRUE;
}
wtap_set_bytes_dumped(rb_data.files[next_file_num].pdh,
rb_data.files[next_file_num].start_pos);
/* set the absolute file number */
rb_data.files[next_file_num].number += rb_data.num_files;
}
#ifdef _WIN32
_tzset();
#endif
rb_data.files[next_file_num].creation_time = time(NULL);
/* switch to the new file */
cf->save_file = rb_data.files[next_file_num].name;
cf->save_file_fd = rb_data.files[next_file_num].fd;
(*pdh) = rb_data.files[next_file_num].pdh;
/* mark the file as used */
rb_data.files[next_file_num].is_new = FALSE;
/* finally set the current file number */
rb_data.curr_file_num = next_file_num;
if (err_on_next)
return FALSE;
return TRUE;
}
/*
* Calls wtap_dump_close() for all ringbuffer files
*/
gboolean
ringbuf_wtap_dump_close(capture_file *cf, int *err)
{
gboolean ret_val;
gboolean data_captured = TRUE;
unsigned int i;
long curr_pos;
long curr_file_curr_pos = 0; /* Initialise to avoid GCC warning */
gchar *new_name;
char filenum[5+1];
char timestr[14+1];
FILE *fh;
/* assume success */
ret_val = TRUE;
/* close all files */
for (i=0; i<rb_data.num_files; i++) {
fh = wtap_dump_file(rb_data.files[i].pdh);
clearerr(fh);
/* Get the current file position */
if ((curr_pos = ftell(fh)) < 0) {
if (err != NULL) {
*err = errno;
}
ret_val = FALSE;
/* If the file's not a new one, remove it as it hasn't been truncated
and thus contains garbage at the end.
If the file is a new one, it contains only the dump header, so
remove it too. */
close(rb_data.files[i].fd);
unlink(rb_data.files[i].name);
/* Set name for caller's error message */
cf->save_file = rb_data.files[i].name;
continue;
}
if (i == rb_data.curr_file_num)
curr_file_curr_pos = curr_pos;
/* If buffer 0 is empty and the ring hasn't wrapped,
no data has been captured. */
if (i == 0 && curr_pos == rb_data.files[0].start_pos &&
rb_data.files[0].number == 0)
data_captured = FALSE;
/* Flush the file */
errno = WTAP_ERR_CANT_CLOSE;
if (fflush(fh) == EOF) {
if (err != NULL) {
*err = errno;
}
ret_val = FALSE;
close(rb_data.files[i].fd);
unlink(rb_data.files[i].name);
cf->save_file = rb_data.files[i].name;
continue;
}
/* Truncate the file to the current size. This must be done in order
to get rid of the 'garbage' packets at the end of the file from
previous usage */
if (!rb_data.files[i].is_new) {
if (ftruncate(rb_data.files[i].fd, curr_pos) != 0) {
/* could not truncate the file */
if (err != NULL) {
*err = errno;
}
ret_val = FALSE;
/* remove the file since it contains garbage at the end */
close(rb_data.files[i].fd);
unlink(rb_data.files[i].name);
cf->save_file = rb_data.files[i].name;
continue;
}
}
/* close the file */
if (!wtap_dump_close(rb_data.files[i].pdh, err)) {
/* error only if it is a used file */
if (curr_pos > rb_data.files[i].start_pos) {
ret_val = FALSE;
/* Don't unlink it; maybe the user can salvage it. */
cf->save_file = rb_data.files[i].name;
continue;
}
}
/* Rename buffers which have data and delete empty buffers --
except if no data at all has been captured we need to keep
the empty first buffer. */
if (curr_pos > rb_data.files[i].start_pos ||
(i == 0 && !data_captured)) {
/* rename the file */
snprintf(filenum,5+1,"%05d",rb_data.files[i].number);
strftime(timestr,14+1,"%Y%m%d%H%M%S",
localtime(&(rb_data.files[i].creation_time)));
new_name = g_strconcat(rb_data.fprefix,"_", filenum, "_", timestr,
rb_data.fsuffix, NULL);
if (rename(rb_data.files[i].name, new_name) != 0) {
/* save the latest error */
if (err != NULL) {
*err = errno;
}
ret_val = FALSE;
cf->save_file = rb_data.files[i].name;
g_free(new_name);
} else {
g_free(rb_data.files[i].name);
rb_data.files[i].name = new_name;
}
} else {
/* this file is empty - remove it */
unlink(rb_data.files[i].name);
}
}
if (ret_val) {
/* Make the current file the save file, or if it's empty apart from
the header, make the previous file the save file (assuming data
has been captured). */
if (curr_file_curr_pos ==
rb_data.files[rb_data.curr_file_num].start_pos &&
data_captured) {
if (rb_data.curr_file_num > 0)
rb_data.curr_file_num -= 1;
else
rb_data.curr_file_num = rb_data.num_files - 1;
}
cf->save_file = rb_data.files[rb_data.curr_file_num].name;
}
return ret_val;
}
/*
* Frees all memory allocated by the ringbuffer
*/
void
ringbuf_free()
{
unsigned int i;
if (rb_data.files != NULL) {
for (i=0; i < rb_data.num_files; i++) {
g_free(rb_data.files[i].name);
rb_data.files[i].name = NULL;
}
free(rb_data.files);
rb_data.files = NULL;
}
g_free(rb_data.fprefix);
g_free(rb_data.fsuffix);
}
/*
* Frees all memory allocated by the ringbuffer
*/
void
ringbuf_error_cleanup(void)
{
unsigned int i;
if (rb_data.files == NULL) {
ringbuf_free();
return;
}
for (i=0; i<rb_data.num_files; i++) {
/* try to close via wtap */
if (rb_data.files[i].pdh != NULL) {
if (wtap_dump_close(rb_data.files[i].pdh, NULL)) {
/* done */
rb_data.files[i].fd = -1;
}
}
/* close directly if still open */
if (rb_data.files[i].fd != -1) {
close(rb_data.files[i].fd);
}
/* remove the other files, the initial file will be handled
by the calling funtion */
if (rb_data.files[i].name != NULL) {
unlink(rb_data.files[i].name);
}
}
/* free the memory */
ringbuf_free();
}
#endif /* HAVE_LIBPCAP */