/* * 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 /* * 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); }