/*
* Copyright (c) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001,
* 2002, 2003, 2004
* Ohio University.
*
* ---
*
* Starting with the release of tcptrace version 6 in 2001, tcptrace
* is licensed under the GNU General Public License (GPL). We believe
* that, among the available licenses, the GPL will do the best job of
* allowing tcptrace to continue to be a valuable, freely-available
* and well-maintained tool for the networking community.
*
* Previous versions of tcptrace were released under a license that
* was much less restrictive with respect to how tcptrace could be
* used in commercial products. Because of this, I am willing to
* consider alternate license arrangements as allowed in Section 10 of
* the GNU GPL. Before I would consider licensing tcptrace under an
* alternate agreement with a particular individual or company,
* however, I would have to be convinced that such an alternative
* would be to the greater benefit of the networking community.
*
* ---
*
* This file is part of Tcptrace.
*
* Tcptrace was originally written and continues to be maintained by
* Shawn Ostermann with the help of a group of devoted students and
* users (see the file 'THANKS'). The work on tcptrace has been made
* possible over the years through the generous support of NASA GRC,
* the National Science Foundation, and Sun Microsystems.
*
* Tcptrace 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.
*
* Tcptrace 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 Tcptrace (in the file 'COPYING'); if not, write to the
* Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*
* Author: Shawn Ostermann
* School of Electrical Engineering and Computer Science
* Ohio University
* Athens, OH
* ostermann@cs.ohiou.edu
* http://www.tcptrace.org/
*/
#include "tcptrace.h"
static char const GCC_UNUSED copyright[] =
"@(#)Copyright (c) 2004 -- Ohio University.\n";
static char const GCC_UNUSED rcsid[] =
"$Header: /usr/local/cvs/tcptrace/compress.c,v 5.9 2004/10/01 21:42:34 mramadas Exp $";
#include "compress.h"
#include <sys/wait.h>
/*
* OK, this stuff is a little complicated. Here's why:
* 1) the routines that examine the file to see if it's of
* a particular type want a real file that they can do
* a "seek" on. Seeking backwards won't work on a stream
* 2) What I do for compressed files is to decompress twice:
* - The first time I just save the first COMP_HDR_SIZE bytes
* into a temporary file and then stop the decompression.
* I then use that file to determine that file type
* - After I know the file type, I restart the decompression
* and reconnect the decompress pipe to stdin
* 3) If the "file" input _IS_ standard input, then it's harder,
* because I can't restart it. In that case, I use a helper process
* that reads the rest of the header file and then starts reading
* the rest of the data from standard input. It's slightly inefficient
* because of the extra process, but I don't know a way around...
*/
/* local routines */
static char *FindBinary(char *binname);
static struct comp_formats *WhichFormat(char *filename);
static FILE *CompSaveHeader(char *filename, struct comp_formats *pf);
static int CompOpenPipe(char *filename, struct comp_formats *pf);
static FILE *PipeHelper(void);
static void PipeFitting(FILE *f_pipe, FILE *f_header, FILE *f_stdin);
/* local globals */
static int header_length = -1;
static Bool is_compressed = FALSE;
static FILE * f_orig_stdin = NULL;
static int child_pid = -1;
static char *tempfile;
int posn;
static char *FindBinary(
char *binname)
{
char *path;
char *pch;
char *pch_colon;
static char abspath[256];
/* quick check for absolute path */
if (*binname == '/') {
if (access(binname,X_OK) == 0) {
if (debug>1)
fprintf(stderr,"FindBinary: abs path '%s' is OK\n", binname);
return(binname);
} else {
if (debug>1)
fprintf(stderr,"FindBinary: abs path '%s' not found\n", binname);
return(NULL);
}
}
path = getenv("PATH");
if (path == NULL) {
if (debug)
fprintf(stderr,"FindBinary: couldn't get PATH envariable\n");
return(NULL);
}
path = strdup(path);
pch = path;
while (pch && *pch) {
pch_colon = strchr(pch,':');
if (pch_colon)
*pch_colon = '\00';
snprintf(abspath,sizeof(abspath),"%s/%s",pch,binname);
if (debug>1)
fprintf(stderr,"Checking for binary '%s'\n", abspath);
if (access(abspath,X_OK) == 0) {
if (debug>1)
fprintf(stderr,"FindBinary: found binary '%s'\n", abspath);
return(abspath);
}
if (pch_colon)
pch = pch_colon+1;
else
pch = NULL;
}
if (debug)
fprintf(stderr,"FindBinary: couldn't find binary '%s' in PATH\n",
binname);
return(NULL);
}
static struct comp_formats *
WhichFormat(
char *filename)
{
static struct comp_formats *pf_cache = NULL;
static char *pf_file_cache = NULL;
int len;
int lens;
int i;
/* check the "cache" :-) */
if (pf_file_cache && (strcmp(filename,pf_file_cache) == 0)) {
return(pf_cache);
}
len = strlen(filename);
for (i=0; i < NUM_COMP_FORMATS; ++i) {
struct comp_formats *pf = &supported_comp_formats[i];
if (debug>1)
fprintf(stderr,"Checking for suffix match '%s' against '%s' (%s)\n",
filename,pf->comp_suffix,pf->comp_bin);
/* check for suffix match */
lens = strlen(pf->comp_suffix);
if (strcmp(filename+len-lens, pf->comp_suffix) == 0) {
if (debug>1)
fprintf(stderr,"Suffix match! '%s' against '%s'\n",
filename,pf->comp_suffix);
/* stick it in the cache */
pf_file_cache = strdup(filename);
pf_cache = pf;
is_compressed = TRUE;
/* and tell the world */
return(pf);
}
}
pf_file_cache = strdup(filename);
pf_cache = NULL;
is_compressed = FALSE;
if (debug)
fprintf(stderr,"WhichFormat: failed to find compression format for file '%s'\n",
filename);
return(NULL);
}
static FILE *
CompReopenFile(
char *filename)
{
char buf[COMP_HDR_SIZE];
struct comp_formats *pf = WhichFormat(filename);
int len;
int fd;
long pos;
if (debug>1)
fprintf(stderr,"CompReopenFile('%s') called\n", filename);
/* we need to switch from the header file to a pipe connected */
/* to a process. Find out how far we've read from the file */
/* so far... */
pos = ftell(stdin);
if (debug>1)
fprintf(stderr,"CompReopenFile: current file position is %ld\n", pos);
/* open a pipe to the original (compressed) file */
fd = CompOpenPipe(filename,pf);
if (fd == -1)
return(NULL);
/* erase the file buffer and reposition to the front */
#ifdef HAVE_FPURGE
/* needed for NetBSD and FreeBSD (at least) */
fpurge(stdin); /* discard input buffer */
#else /* HAVE_FPURGE */
fflush(stdin); /* discard input buffer */
#endif /* HAVE_FPURGE */
rewind(stdin);
/* yank the FD out from under stdin and point to the pipe */
dup2(fd,0);
/* skip forward in the stream to the same place that we were in */
/* for the header file */
len = fread(buf,1,pos,stdin);
if ((len == 0) && ferror(stdin)) {
perror("read forward in stdin");
exit(-1);
}
/* OK, I guess we're all set... */
return(stdin);
}
static FILE *
CompSaveHeader(
char *filename,
struct comp_formats *pf)
{
FILE *f_stream;
FILE *f_file;
char buf[COMP_HDR_SIZE];
int len;
int fd;
fd = CompOpenPipe(filename,pf);
if (fd == -1)
return(NULL);
#ifdef HAVE_MKSTEMP
{
/* From Mallman, supposed to be "safer" */
int fd;
extern int mkstemp(char *template);
/* grab a writable string to keep picky compilers happy */
tempfile = strdup("/tmp/trace_hdrXXXXXXXX");
/* create a temporary file name and open it */
if ((fd = mkstemp(tempfile)) == -1) {
perror("template");
exit(-1);
}
/* convert to a stream */
f_file = fdopen(fd,"w");
}
#else /* HAVE_MKSTEMP */
/* get a name for a temporary file to store the header in */
tempfile = tempnam("/tmp/","trace_hdr");
/* open the file */
if ((f_file = fopen(tempfile,"w+")) == NULL) {
perror(tempfile);
exit(-1);
}
#endif /* HAVE_MKSTEMP */
/* connect a stdio stream to the pipe */
if ((f_stream = fdopen(fd,"r")) == NULL) {
perror("open pipe stream for header");
exit(-1);
}
/* just grab the first X bytes and stuff into a temp file */
len = fread(buf,1,COMP_HDR_SIZE,f_stream);
if ((len == 0) && ferror(f_stream)) {
perror("read pipe stream for header");
exit(-1);
}
if (len == 0) {
/* EOF, failure */
return(NULL);
}
header_length = len;
if (debug>1)
fprintf(stderr,"Saved %d bytes from stream into temp header file '%s'\n",
len, tempfile);
/* save the header into a temp file */
len = fwrite(buf,1,len,f_file);
if ((len == 0) && ferror(f_file)) {
perror("write file stream for header");
exit(-1);
}
if (debug>1)
fprintf(stderr,"Saved the file header into temp file '%s'\n",
tempfile);
/* OK, we have the header, close the file */
fclose(f_file);
/* if it's stdin, make a copy for later */
if (FileIsStdin(filename)) {
f_orig_stdin = f_stream; /* remember where it is */
} else {
fclose(f_stream);
}
/* re-open the file as stdin */
if ((freopen(tempfile,"r",stdin)) == NULL) {
perror("tempfile");
exit(-1);
}
return(stdin);
}
static int
CompOpenPipe(
char *filename,
struct comp_formats *pf)
{
int fdpipe[2];
char *abspath;
int i;
char *args[COMP_MAX_ARGS];
if (debug>1)
fprintf(stderr,"CompOpenPipe('%s') called\n", filename);
/* short hand if it's just reading from standard input */
if (FileIsStdin(filename)) {
return(dup(0)); /* 0: standard input */
}
abspath = FindBinary(pf->comp_bin);
if (!abspath) {
fprintf(stderr,
"Compression: failed to find binary for '%s' needed to uncompress file\n",
pf->comp_bin);
fprintf(stderr,
"According to my configuration, I need '%s' to decode files of type\n",
pf->comp_bin);
fprintf(stderr, "%s\n", pf->comp_descr);
exit(-1);
}
/* save the path for later... */
pf->comp_bin = strdup(abspath);
/* filter args */
for (i=0; i < COMP_MAX_ARGS; ++i) {
args[i] = pf->comp_args[i];
if (!args[i])
break;
if (strcmp(pf->comp_args[i],"%s") == 0)
args[i] = filename;
}
if (Mfpipe(fdpipe) == -1) {
perror("pipe");
exit(-1);
}
#ifdef __VMS
child_pid = vfork();
#else
child_pid = fork();
#endif
if (child_pid == -1) {
perror("fork");
exit(-1);
}
if (child_pid == 0) {
/* child */
dup2(fdpipe[1],1); /* redirect child's stdout to pipe */
/* close all other FDs - lazy, but close enough for our purposes :-) */
for (i=3; i < 100; ++i) close(i);
if (debug>1) {
fprintf(stderr,"Execing %s", abspath);
for (i=1; args[i]; ++i)
fprintf(stderr," %s", args[i]);
fprintf(stderr,"\n");
}
execv(abspath,args);
fprintf(stderr,"Exec of '%s' failed\n", abspath);
perror(abspath);
exit(-1);
}
close(fdpipe[1]);
return(fdpipe[0]);
}
FILE *
CompOpenHeader(
char *filename)
{
FILE *f;
struct comp_formats *pf;
/* short hand if it's just reading from standard input */
if (FileIsStdin(filename)) {
is_compressed = TRUE; /* pretend that it's compressed */
return(CompSaveHeader(filename,NULL));
}
/* see if it's a supported compression file */
pf = WhichFormat(filename);
#ifdef __WIN32
if(pf != NULL) {
fprintf(stderr, "\nError: windows version of tcptrace does not support\nreading compressed dump files. Uncompress the file\nmanually and try again. Sorry!\n");
return((FILE *)-1);
}
return(NULL);
#endif /* __WIN32 */
/* if no compression found, just open the file */
if (pf == NULL) {
if (freopen(filename,"r",stdin) == NULL) {
perror(filename);
return(NULL);
}
return(stdin);
}
/* open the file through compression */
if (debug>1)
printf("Decompressing file of type '%s' using program '%s'\n",
pf->comp_descr, pf->comp_bin);
else if (debug)
printf("Decompressing file using '%s'\n", pf->comp_bin);
f = CompSaveHeader(filename,pf);
if (!f) {
fprintf(stderr,"Decompression failed for file '%s'\n", filename);
exit(-1);
}
return(f);
}
FILE *
CompOpenFile(
char *filename)
{
if (debug>1)
fprintf(stderr,"CompOpenFile('%s') called\n", filename);
/* if it isn't compressed, just leave it at stdin */
if (!is_compressed)
return(stdin);
/* if the header we already saved is the whole file, it must be */
/* short, so just read from the file */
if (header_length < COMP_HDR_SIZE) {
if (debug>1)
fprintf(stderr,"CompOpenFile: still using header file, short file...\n");
return(stdin);
}
/* if we're just reading from standard input, we'll need some help because */
/* part of the input is in a file and the rest is still stuck in a pipe */
if (FileIsStdin(filename)) {
posn=ftell(stdin);
if (posn < 0) {
perror("CompOpenFile : ftell failed");
exit(-1);
}
return(PipeHelper());
}
/* otherwise, there's more than we saved, we need to re-open the pipe */
/* and re-attach it to stdin */
return(CompReopenFile(filename));
}
/* return a FILE * that fill come from a helper process */
FILE *
PipeHelper(void)
{
int fdpipe[2];
FILE *f_return;
/* On coming in, here's what's in the FDs: */
/* stdin: has the header file open */
/* f_stdin_file: holds the rest of the stream */
if (Mfpipe(fdpipe) == -1) {
perror("pipe");
exit(-1);
}
/* remember: fdpipe[0] is for reading, fdpipe[1] is for writing */
#ifdef __VMS
child_pid = vfork();
#else
child_pid = fork();
#endif
if (child_pid == -1) {
perror("fork");
exit(-1);
}
if (child_pid == 0) {
/* be the helper process */
FILE *f_pipe;
/* attach a stream to the pipe connection */
f_pipe = fdopen(fdpipe[1],"w");
if (f_pipe == NULL) {
perror("fdopen on pipe for writing");
exit(-1);
}
/* connect the header file and stream to the pipe */
PipeFitting(f_pipe, stdin, f_orig_stdin);
/* OK, both empty, we're done */
if (debug>1)
fprintf(stderr,
"PipeHelper(%d): all done, exiting\n", (int)getpid());
exit(0);
}
/* I'm still the parent */
if (debug>1)
fprintf(stderr,
"PipeHelper: forked off child %d to deal with stdin\n",
child_pid);
/* clean up the fd's */
close(fdpipe[1]);
// Now, we shall purge our old STDIN stream buffer, and point it to the
// read end of the pipe, fdpipe[0]
#ifdef HAVE_FPURGE
fpurge(stdin); // needed for NetBSD/FreeBSD
#else
fflush(stdin);
#endif
clearerr(stdin);
if (dup2(fdpipe[0],0)==-1) {
perror("PipeHelper : dup2 failed in parent");
exit(-1);
}
/* make a stream attached to the PIPE and return it */
f_return = fdopen(fdpipe[0],"r");
if (f_return == NULL) {
perror("PipeHelper : fdopen on pipe for reading");
exit(-1);
}
return(f_return);
}
static void
PipeFitting(
FILE *f_pipe,
FILE *f_header,
FILE *f_orig_stdin)
{
char buf[COMP_HDR_SIZE]; /* just a big buffer */
int len;
// Fix the file synchronization problems and undefined behavior exhibited
// by fread() in managing its buffers, when stdin is opened by both the
// parent and child processes.
// In the child process (where we are currently executing), close and
// re-open the temporary file currently opened as stdin, in which the
// first COMP_HDR_SIZE bytes of data were stored. The current file pointer
// position in the file was stored in the global variable posn.
if (fclose(f_header)<0)
perror("PipeFitting : fclose failed");
if ((f_header=fopen(tempfile,"r"))==NULL) {
perror("PipeFitting : fopen of tempfile failed");
exit(-1);
}
if (fread(buf,1,posn,f_header)!=posn) {
perror("PipeFitting : fread failed");
exit(-1);
}
/* read from f_header (the file) until empty */
while (1) {
/* read some more data */
len = fread(buf,1,sizeof(buf),f_header);
if (len == 0)
break;
if (len < 0) {
perror("fread from f_header");
exit(0);
}
if (debug>1)
fprintf(stderr,
"PipeFitting: read %d bytes from header file\n", len);
/* send those bytes to the pipe */
if (fwrite(buf,1,len,f_pipe) != len) {
perror("fwrite on pipe");
exit(-1);
}
}
// We are done with the temporary file. Time to close and unlink it.
if (fclose(f_header)<0)
perror("PipeFitting : fclose failed");
if (unlink(tempfile)<0)
perror("PipeFitting : unlink of tempfile failed");
if (debug>1)
fprintf(stderr,
"PipeFitting: header file empty, switching to old stdin\n");
/* OK, the file is empty, switch back to the stdin stream */
while (1) {
/* read some more data */
len = fread(buf,1,sizeof(buf),f_orig_stdin);
if (len == 0)
break;
if (len < 0) {
perror("fread from f_orig_stdin");
exit(0);
}
if (debug>1)
fprintf(stderr,
"PipeFitting: read %d bytes from f_orig_stdin\n", len);
/* send those bytes to the pipe */
if (fwrite(buf,1,len,f_pipe) != len) {
perror("fwrite on pipe");
exit(-1);
}
}
}
void
CompCloseFile(
char *filename)
{
/* Hmmm... this was commented out, I wonder why? */
/* fclose(stdin); */
/* if we have a child, make sure it's dead */
if (child_pid != -1) {
kill(child_pid,SIGTERM);
child_pid = -1;
}
/* in case we have children child still in the background */
while (wait(0) != -1)
; /* nothing */
/* zero out some globals */
header_length = -1;
}
int
CompIsCompressed(void)
{
return(is_compressed);
}
void
CompFormats(void)
{
int i;
fprintf(stderr,"Supported Compression Formats:\n");
fprintf(stderr,"\tSuffix Description Uncompress Command\n");
fprintf(stderr,"\t------ -------------------- --------------------------\n");
for (i=0; i < NUM_COMP_FORMATS; ++i) {
int arg;
struct comp_formats *pf = &supported_comp_formats[i];
fprintf(stderr,"\t%6s %-20s %s",
pf->comp_suffix,
pf->comp_descr,
pf->comp_bin);
for (arg=1; pf->comp_args[arg]; ++arg)
fprintf(stderr," %s", pf->comp_args[arg]);
fprintf(stderr,"\n");
}
}
/* does the file name "filename" refer to stdin rather than a real file? */
/* (in case I need to extend this definition someday) */
Bool
FileIsStdin(
char *filename)
{
if (strcmp(filename,"stdin") == 0)
return(1);
if (strcmp(filename,"stdin.gz") == 0)
return(1);
return(0);
}
syntax highlighted by Code2HTML, v. 0.9.1