/*
* This file is part of tcpflow by Jeremy Elson <jelson@circlemud.org>
* Initial Release: 7 April 1999.
*
* This source code is under the GNU Public License (GPL). See
* LICENSE for details.
*
* $Id: flow.c,v 1.6 1999/04/13 01:38:11 jelson Exp $
*
* $Log: flow.c,v $
* Revision 1.6 1999/04/13 01:38:11 jelson
* Added portability features with 'automake' and 'autoconf'. Added AUTHORS,
* NEWS, README, etc files (currently empty) to conform to GNU standards.
*
* Various portability fixes, including the FGETPOS/FSETPOS macros; detection
* of header files using autoconf; restructuring of debugging code to not
* need vsnprintf.
*
*/
static char *cvsid = "$Id: flow.c,v 1.6 1999/04/13 01:38:11 jelson Exp $";
#include "tcpflow.h"
static int max_fds;
static int next_slot;
static int current_time;
static flow_state_t **fd_ring;
static flow_state_t *flow_hash[HASH_SIZE];
/* Initialize our structures */
void init_flow_state()
{
int i;
/* Find out how many files we can have open safely...subtract 4 for
* stdin, stdout, stderr, and the packet filter; one for breathing
* room (we open new files before closing old ones), and one more to
* be safe. */
max_fds = get_max_fds() - NUM_RESERVED_FDS;
fd_ring = MALLOC(flow_state_t *, max_fds);
for (i = 0; i < max_fds; i++)
fd_ring[i] = NULL;
for (i = 0; i < HASH_SIZE; i++)
flow_hash[i] = NULL;
next_slot = -1;
current_time = 0;
}
/* Create a new flow state structure, initialize its contents, and add
* it to its hash bucket. It is prepended to the hash bucket because
* 1) doing so is fast (requiring constant time regardless of bucket
* size; and 2) it'll tend to make lookups faster for more recently
* added state, which will probably be more often used state.
*
* Returns a pointer to the new state. */
flow_state_t *create_flow_state(flow_t flow, tcp_seq isn)
{
/* create space for the new state */
flow_state_t *new_flow = MALLOC(flow_state_t, 1);
/* determine where in the hash this goes */
int index = HASH_FLOW(flow);
/* link it in to the hash bucket at the beginning */
new_flow->next = flow_hash[index];
flow_hash[index] = new_flow;
/* initialize contents of the state structure */
new_flow->flow = flow;
new_flow->isn = isn;
new_flow->fp = NULL;
new_flow->pos = 0;
new_flow->flags = 0;
new_flow->last_access = current_time++;
DEBUG(5) ("%s: new flow", flow_filename(flow));
return new_flow;
}
/* Find previously a previously created flow state structure by
* jumping to its hash bucket, and linearly searching everything in
* the bucket. Returns NULL if the state is not found. */
flow_state_t *find_flow_state(flow_t flow)
{
flow_state_t *ptr;
int index = HASH_FLOW(flow);
for (ptr = flow_hash[index]; ptr != NULL; ptr = ptr->next)
if (!memcmp((char *) &flow, (char *) &(ptr->flow), sizeof(flow))) {
ptr->last_access = current_time++;
return ptr;
}
return NULL;
}
FILE *attempt_fopen(flow_state_t *flow_state, char *filename)
{
/* If we've opened this file already, reopen it. Otherwise create a
* new file. We purposefully overwrite files from previous runs of
* the program. */
if (IS_SET(flow_state->flags, FLOW_FILE_EXISTS)) {
DEBUG(5) ("%s: re-opening output file", filename);
flow_state->fp = fopen(filename, "r+");
} else {
DEBUG(5) ("%s: opening new output file", filename);
flow_state->fp = fopen(filename, "w");
}
return flow_state->fp;
}
FILE *open_file(flow_state_t *flow_state)
{
char *filename = flow_filename(flow_state->flow);
int done;
/* This shouldn't be called if the file is already open */
if (flow_state->fp) {
DEBUG(20) ("huh -- trying to open already open file!");
return flow_state->fp;
}
/* Now try and open the file */
do {
if (attempt_fopen(flow_state, filename) != NULL) {
/* open succeeded... great */
done = 1;
} else {
if (errno == ENFILE || errno == EMFILE) {
/* open failed because too many files are open... close one
and try again */
contract_fd_ring();
DEBUG(5) ("too many open files -- contracting FD ring to %d", max_fds);
done = 0;
} else {
/* open failed for some other reason... give up */
done = 1;
}
}
} while (!done);
/* If the file isn't open at this point, there's a problem */
if (flow_state->fp == NULL) {
/* we had some problem opening the file -- set FINISHED so we
* don't keep trying over and over again to reopen it */
SET_BIT(flow_state->flags, FLOW_FINISHED);
perror(filename);
return NULL;
}
/* Now we decide which FD slot we use, and close the file that's
* there (if any). Note that even if flow_state is not NULL, its
* associated file pointer may already be closed. Note well that we
* DO NOT free the state that we find in our slot; the state stays
* around forever (pointed to by the hash table). This table only
* keeps a pointer to state structures that have open files so that
* we can close them later.
*
* We are putting the close after the open so that we don't bother
* closing files if the open fails. (For this, we pay a price of
* needing to keep a spare, idle FD around.) */
if (++next_slot == max_fds) {
/* take this opportunity to sort from oldest to newest --
* optimally we'd like to do this before every close, but that
* might take too long. */
sort_fds();
next_slot = 0;
}
/* close the next one in line */
if (fd_ring[next_slot] != NULL)
close_file(fd_ring[next_slot]);
/* put ourslves in its place */
fd_ring[next_slot] = flow_state;
/* set flags and remember where in the file we are */
SET_BIT(flow_state->flags, FLOW_FILE_EXISTS);
FGETPOS(flow_state->fp, &(flow_state->pos));
return flow_state->fp;
}
/* Closes the file belonging to a flow -- returns 1 if a file was
* actually closed, 0 otherwise (if it was already closed) */
int close_file(flow_state_t *flow_state)
{
if (flow_state->fp == NULL)
return 0;
DEBUG(5) ("%s: closing file", flow_filename(flow_state->flow));
/* close the file and remember that it's closed */
fclose(flow_state->fp);
flow_state->fp = NULL;
flow_state->pos = 0;
return 1;
}
/* This comparison function puts flows first in the array, and nulls
* last. Within the flows, they are ordered from least recently
* accessed at the front, and most recently accessed at the end. */
int flow_state_compare(const void *a, const void *b)
{
flow_state_t **x = (flow_state_t **)a;
flow_state_t **y = (flow_state_t **)b;
if (*x == NULL && *y == NULL)
return 0;
if (*x == NULL)
return 1;
if (*y == NULL)
return -1;
return ((*x)->last_access - (*y)->last_access);
}
/* Sort FDs in the fd_table according to the comparison function (see
* comment above) */
void sort_fds()
{
qsort(fd_ring, max_fds, sizeof(struct flow_state_t *), flow_state_compare);
#if 0
/* code to dump the table - for debugging */
{
int i;
for(i = 0; i < max_fds; i++) {
if (fd_ring[i] == NULL)
continue;
else
fprintf(stderr, "fd_slot %d: %s (lasttime=%d)\n", i,
flow_filename(fd_ring[i]->flow), fd_ring[i]->last_access);
}
}
#endif
}
/* We need to reduce the size of the fd ring by one FD. We will
* sort the FD ring, close the oldest (i.e. first) file descriptor,
* shift everything down by one, and set max_fds to reflect the new
* size. */
void contract_fd_ring()
{
int i;
/* sort */
sort_fds();
/* make sure we're sane */
if (fd_ring[0] == NULL) {
die("we seem to be completely out of file descriptors");
}
/* close the oldest FD */
close_file(fd_ring[0]);
/* shift everything forward by one and count */
for (i = 1; i < max_fds && fd_ring[i] != NULL; i++)
fd_ring[i-1] = fd_ring[i];
/* remember that the ring is smaller now */
max_fds = i-1;
/* start at 0 (by setting to -1, since we're going to increment it) */
next_slot = -1;
}
syntax highlighted by Code2HTML, v. 0.9.1