De-orbit burn tcpslice.

Reviewed by:	sam, bmah
This commit is contained in:
Bruce M Simpson 2006-09-24 09:18:36 +00:00
parent 81fbc3d1f3
commit 790ca1147e
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=162596
7 changed files with 0 additions and 1702 deletions

View File

@ -1,23 +0,0 @@
# @(#)Makefile 0.1 (RWGrimes) 3/24/93
# $FreeBSD$
TCPDUMP_DISTDIR?= ${.CURDIR}/../../../contrib/tcpdump
PROG= tcpslice
SRCS= gwtm2secs.c search.c tcpslice.c util.c version.c version.h
CLEANFILES= version.c version.h
CFLAGS+= -I.
DPADD= ${LIBPCAP}
LDADD= -lpcap
.ORDER: version.c version.h
version.c version.h: ${TCPDUMP_DISTDIR}/VERSION
rm -f version.c ; \
sed 's/.*/char version[] = "&";/' ${TCPDUMP_DISTDIR}/VERSION > version.c
set `sed 's/\([0-9]*\)\.\([0-9]*\).*/\1 \2/' ${TCPDUMP_DISTDIR}/VERSION` ; \
{ echo '#define VERSION_MAJOR' $$1 ; \
echo '#define VERSION_MINOR' $$2 ; } > version.h
.include <bsd.prog.mk>

View File

@ -1,79 +0,0 @@
/*
* Copyright (c) 1990 The Regents of the University of California.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that: (1) source code distributions
* retain the above copyright notice and this paragraph in its entirety, (2)
* distributions including binary code include the above copyright notice and
* this paragraph in its entirety in the documentation or other materials
* provided with the distribution, and (3) all advertising materials mentioning
* features or use of this software display the following acknowledgement:
* ``This product includes software developed by the University of California,
* Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
* the University nor the names of its contributors may be used to endorse
* or promote products derived from this software without specific prior
* written permission.
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
#if !defined(lint) && !defined(__GNUC__)
static char rcsid[] =
"@(#)$FreeBSD$ (LBL)";
#endif
/*
* gwtm2secs.c - convert "tm" structs for Greenwich time to Unix timestamp
*/
#include "tcpslice.h"
static int days_in_month[] =
/* Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec */
{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
#define IS_LEAP_YEAR(year) \
(year % 4 == 0 && (year % 100 != 0 || year % 400 == 0))
time_t gwtm2secs( struct tm *tm )
{
int i, days, year;
year = tm->tm_year;
/* Allow for year being specified with either 2 digits or 4 digits.
* 2-digit years are either 19xx or 20xx - a simple heuristic
* distinguishes them, since we can't represent any time < 1970.
*/
if ( year < 100 )
if ( year >= 70 )
year += 1900;
else
year += 2000;
/* Make sure our year is still >= 1970. We fix 3-digit years
* this way, because localtime(3) can return tm_year >= 100,
* starting in year 2000.
*/
if ( year < 1970 )
year += 1900;
days = 0;
for ( i = 1970; i < year; ++i )
{
days += 365;
if ( IS_LEAP_YEAR(i) )
++days;
}
for ( i = 0; i < tm->tm_mon; ++i )
days += days_in_month[i];
if ( IS_LEAP_YEAR(year) && tm->tm_mon > 1 ) /* 1 is February */
++days;
days += tm->tm_mday - 1; /* -1 since days are numbered starting at 1 */
return days * 86400 + tm->tm_hour * 3600 + tm->tm_min * 60 + tm->tm_sec;
}

View File

@ -1,566 +0,0 @@
/*
* Copyright (c) 1990, 1991, 1992, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that: (1) source code distributions
* retain the above copyright notice and this paragraph in its entirety, (2)
* distributions including binary code include the above copyright notice and
* this paragraph in its entirety in the documentation or other materials
* provided with the distribution, and (3) all advertising materials mentioning
* features or use of this software display the following acknowledgement:
* ``This product includes software developed by the University of California,
* Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
* the University nor the names of its contributors may be used to endorse
* or promote products derived from this software without specific prior
* written permission.
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
#if !defined(lint) && !defined(__GNUC__)
static char rcsid[] =
"@(#)$FreeBSD$ (LBL)";
#endif
/*
* search.c - supports fast searching through tcpdump files for timestamps
*/
#include "tcpslice.h"
/* Maximum number of seconds that we can conceive of a dump file spanning. */
#define MAX_REASONABLE_FILE_SPAN (3600*24*366) /* one year */
/* Maximum packet length we ever expect to see. */
#define MAX_REASONABLE_PACKET_LENGTH 65535
/* Size of a packet header in bytes; easier than typing the sizeof() all
* the time ...
*/
#define PACKET_HDR_LEN (sizeof( struct pcap_pkthdr ))
extern int snaplen;
/* The maximum size of a packet, including its header. */
#define MAX_PACKET_SIZE (PACKET_HDR_LEN + snaplen)
/* Number of contiguous bytes from a dumpfile in which there's guaranteed
* to be enough information to find a "definite" header if one exists
* therein. This takes 3 full packets - the first to be just misaligned
* (one byte short of a full packet), missing its timestamp; the second
* to have the legitimate timestamp; and the third to provide confirmation
* that the second is legit, making it a "definite" header. We could
* scrimp a bit here since not the entire third packet is required, but
* it doesn't seem worth it
*/
#define MAX_BYTES_FOR_DEFINITE_HEADER (3 * MAX_PACKET_SIZE)
/* Maximum number of seconds that might reasonably separate two headers. */
#define MAX_REASONABLE_HDR_SEPARATION (3600 * 24 * 7) /* one week */
/* When searching a file for a packet, if we think we're within this many
* bytes of the packet we just search linearly. Since linear searches are
* probably much faster than random ones (random ones require searching for
* the beginning of the packet, which may be unaligned in memory), we make
* this value pretty hefty.
*/
#define STRAIGHT_SCAN_THRESHOLD (100 * MAX_PACKET_SIZE)
/* Given a header and an acceptable first and last time stamp, returns non-zero
* if the header looks reasonable and zero otherwise.
*/
static int
reasonable_header( struct pcap_pkthdr *hdr, long first_time, long last_time )
{
if ( last_time == 0 )
last_time = first_time + MAX_REASONABLE_FILE_SPAN;
return hdr->ts.tv_sec >= first_time &&
hdr->ts.tv_sec <= last_time &&
hdr->len > 0 &&
hdr->len <= MAX_REASONABLE_PACKET_LENGTH &&
hdr->caplen > 0 &&
hdr->caplen <= MAX_REASONABLE_PACKET_LENGTH;
}
#define SWAPLONG(y) \
((((y)&0xff)<<24) | (((y)&0xff00)<<8) | (((y)&0xff0000)>>8) | (((y)>>24)&0xff))
#define SWAPSHORT(y) \
( (((y)&0xff)<<8) | (((y)&0xff00)>>8) )
/* Given a buffer, extracts a (properly aligned) packet header from it. */
static void
extract_header( pcap_t *p, u_char *buf, struct pcap_pkthdr *hdr )
{
bcopy((char *) buf, (char *) hdr, sizeof(struct pcap_pkthdr));
if ( pcap_is_swapped( p ) )
{
hdr->ts.tv_sec = SWAPLONG(hdr->ts.tv_sec);
hdr->ts.tv_usec = SWAPLONG(hdr->ts.tv_usec);
hdr->len = SWAPLONG(hdr->len);
hdr->caplen = SWAPLONG(hdr->caplen);
}
/*
* From bpf/libpcap/savefile.c:
*
* We interchanged the caplen and len fields at version 2.3,
* in order to match the bpf header layout. But unfortunately
* some files were written with version 2.3 in their headers
* but without the interchanged fields.
*/
if ( pcap_minor_version( p ) < 3 ||
(pcap_minor_version( p ) == 3 && hdr->caplen > hdr->len) )
{
int t = hdr->caplen;
hdr->caplen = hdr->len;
hdr->len = t;
}
}
/* Search a buffer to locate the first header within it. Return values
* are HEADER_NONE, HEADER_CLASH, HEADER_PERHAPS, and HEADER_DEFINITELY.
* The first indicates that no evidence of a header was found; the second
* that two or more possible headers were found, neither more convincing
* than the other(s); the third that exactly one "possible" header was
* found; and the fourth that exactly one "definite" header was found.
*
* Headers are detected by looking for positions in the buffer which have
* reasonable timestamps and lengths. If there is enough room in the buffer
* for another header to follow a candidate header, a check is made for
* that following header. If it is present then the header is *definite*
* (unless another "perhaps" or "definite" header is found); if not, then
* the header is discarded. If there is not enough room in the buffer for
* another header then the candidate is *perhaps* (unless another header
* is subsequently found). A "tie" between a "definite" header and a
* "perhaps" header is resolved in favor of the definite header. Any
* other tie leads to HEADER_CLASH.
*
* The buffer position of the header is returned in hdrpos_addr and
* for convenience the corresponding header in return_hdr.
*
* first_time is the earliest possible acceptable timestamp in the
* header. last_time, if non-zero, is the last such timestamp. If
* zero, then up to MAX_REASONABLE_FILE_SPAN seconds after first_time
* is acceptable.
*/
#define HEADER_NONE 0
#define HEADER_CLASH 1
#define HEADER_PERHAPS 2
#define HEADER_DEFINITELY 3
static int
find_header( pcap_t *p, u_char *buf, int buf_len,
long first_time, long last_time,
u_char **hdrpos_addr, struct pcap_pkthdr *return_hdr )
{
u_char *bufptr, *bufend, *last_pos_to_try;
struct pcap_pkthdr hdr, hdr2;
int status = HEADER_NONE;
int saw_PERHAPS_clash = 0;
/* Initially, try each buffer position to see whether it looks like
* a valid packet header. We may later restrict the positions we look
* at to avoid seeing a sequence of legitimate headers as conflicting
* with one another.
*/
bufend = buf + buf_len;
last_pos_to_try = bufend - PACKET_HDR_LEN;
for ( bufptr = buf; bufptr < last_pos_to_try; ++bufptr )
{
extract_header( p, bufptr, &hdr );
if ( reasonable_header( &hdr, first_time, last_time ) )
{
u_char *next_header = bufptr + PACKET_HDR_LEN + hdr.caplen;
if ( next_header + PACKET_HDR_LEN < bufend )
{ /* check for another good header */
extract_header( p, next_header, &hdr2 );
if ( reasonable_header( &hdr2, hdr.ts.tv_sec,
hdr.ts.tv_sec + MAX_REASONABLE_HDR_SEPARATION ) )
{ /* a confirmed header */
switch ( status )
{
case HEADER_NONE:
case HEADER_PERHAPS:
status = HEADER_DEFINITELY;
*hdrpos_addr = bufptr;
*return_hdr = hdr;
/* Make sure we don't demote this "definite"
* to a "clash" if we stumble across its
* successor.
*/
last_pos_to_try = next_header - PACKET_HDR_LEN;
break;
case HEADER_DEFINITELY:
return HEADER_CLASH;
default:
error( "bad status in find_header()" );
}
}
/* ... else the header is bogus - we've verified that it's
* not followed by a reasonable header.
*/
}
else
{ /* can't check for another good header */
switch ( status )
{
case HEADER_NONE:
status = HEADER_PERHAPS;
*hdrpos_addr = bufptr;
*return_hdr = hdr;
break;
case HEADER_PERHAPS:
/* We don't immediately turn this into a
* clash because perhaps we'll later see a
* "definite" which will save us ...
*/
saw_PERHAPS_clash = 1;
break;
case HEADER_DEFINITELY:
/* Keep the definite in preference to this one. */
break;
default:
error( "bad status in find_header()" );
}
}
}
}
if ( status == HEADER_PERHAPS && saw_PERHAPS_clash )
status = HEADER_CLASH;
return status;
}
/* Positions the sf_readfile stream such that the next sf_read() will
* read the final full packet in the file. Returns non-zero if
* successful, zero if unsuccessful. If successful, returns the
* timestamp of the last packet in last_timestamp.
*
* Note that this routine is a special case of sf_find_packet(). In
* order to use sf_find_packet(), one first must use this routine in
* order to give sf_find_packet() an upper bound on the timestamps
* present in the dump file.
*/
int
sf_find_end( pcap_t *p, struct timeval *first_timestamp,
struct timeval *last_timestamp )
{
long first_time = first_timestamp->tv_sec;
u_int num_bytes;
u_char *buf, *bufpos, *bufend;
u_char *hdrpos;
struct pcap_pkthdr hdr, successor_hdr;
int status;
/* Allow enough room for at least two full (untruncated) packets,
* perhaps followed by a truncated packet, so we have a shot at
* finding a "definite" header and following its chain to the
* end of the file.
*/
num_bytes = MAX_BYTES_FOR_DEFINITE_HEADER;
if ( fseeko( pcap_file( p ), (off_t)-num_bytes, 2 ) < 0 )
return 0;
buf = (u_char *)malloc((u_int) num_bytes);
if ( ! buf )
return 0;
status = 0;
bufpos = buf;
bufend = buf + num_bytes;
if ( fread( (char *) bufpos, num_bytes, 1, pcap_file( p ) ) != 1 )
goto done;
if ( find_header( p, bufpos, num_bytes,
first_time, 0L, &hdrpos, &hdr ) != HEADER_DEFINITELY )
goto done;
/* Okay, we have a definite header in our hands. Follow its
* chain till we find the last valid packet in the file ...
*/
for ( ; ; )
{
/* move to the next header position */
bufpos = hdrpos + PACKET_HDR_LEN + hdr.caplen;
/* bufpos now points to a candidate packet, which if valid
* should replace the current packet pointed to by hdrpos as
* the last valid packet ...
*/
if ( bufpos >= bufend - PACKET_HDR_LEN )
/* not enough room for another header */
break;
extract_header( p, bufpos, &successor_hdr );
first_time = hdr.ts.tv_sec;
if ( ! reasonable_header( &successor_hdr, first_time, 0L ) )
/* this bodes ill - it means bufpos is perhaps a
* bogus packet header after all ...
*/
break;
/* Note that the following test is for whether the next
* packet starts at a position > bufend, *not* for a
* position >= bufend. If this is the last packet in the
* file and there isn't a subsequent partial packet, then
* we expect the first buffer position beyond this packet
* to be just beyond the end of the buffer, i.e., at bufend
* itself.
*/
if ( bufpos + PACKET_HDR_LEN + successor_hdr.caplen > bufend )
/* the packet is truncated */
break;
/* Accept this packet as fully legit. */
hdrpos = bufpos;
hdr = successor_hdr;
}
/* Success! Last valid packet is at hdrpos. */
*last_timestamp = hdr.ts;
status = 1;
/* Seek so that the next read will start at last valid packet. */
if ( fseeko( pcap_file( p ), (off_t) -(bufend - hdrpos), 2 ) < 0 )
error( "final fseeko() failed in sf_find_end()" );
done:
free( (char *) buf );
return status;
}
/* Takes two timeval's and returns the difference, tv2 - tv1, as a double. */
static double
timeval_diff( struct timeval *tv1, struct timeval *tv2 )
{
double result = (tv2->tv_sec - tv1->tv_sec);
result += (tv2->tv_usec - tv1->tv_usec) / 1000000.0;
return result;
}
/* Returns true if timestamp t1 is chronologically less than timestamp t2. */
int
sf_timestamp_less_than( struct timeval *t1, struct timeval *t2 )
{
return t1->tv_sec < t2->tv_sec ||
(t1->tv_sec == t2->tv_sec &&
t1->tv_usec < t2->tv_usec);
}
/* Given two timestamps on either side of desired_time and their positions,
* returns the interpolated position of the desired_time packet. Returns a
* negative value if the desired_time is outside the given range.
*/
static long
interpolated_position( struct timeval *min_time, long min_pos,
struct timeval *max_time, long max_pos,
struct timeval *desired_time )
{
double full_span = timeval_diff( max_time, min_time );
double desired_span = timeval_diff( desired_time, min_time );
long full_span_pos = max_pos - min_pos;
double fractional_offset = desired_span / full_span;
if ( fractional_offset < 0.0 || fractional_offset > 1.0 )
return -1;
return min_pos + (long) (fractional_offset * (double) full_span_pos);
}
/* Reads packets linearly until one with a time >= the given desired time
* is found; positions the dump file so that the next read will start
* at the given packet. Returns non-zero on success, 0 if an EOF was
* first encountered.
*/
static int
read_up_to( pcap_t *p, struct timeval *desired_time )
{
struct pcap_pkthdr hdr;
const u_char *buf;
fpos_t pos;
int status;
for ( ; ; )
{
struct timeval *timestamp;
fgetpos( pcap_file( p ), &pos );
buf = pcap_next( p, &hdr );
if ( buf == 0 )
{
if ( feof( pcap_file( p ) ) )
{
status = 0;
clearerr( pcap_file( p ) );
break;
}
error( "bad status in read_up_to()" );
}
timestamp = &hdr.ts;
if ( ! sf_timestamp_less_than( timestamp, desired_time ) )
{
status = 1;
break;
}
}
if ( fsetpos( pcap_file( p ), &pos ) < 0 )
error( "fsetpos() failed in read_up_to()" );
return (status);
}
/* Positions the sf_readfile stream so that the next sf_read() will
* return the first packet with a time greater than or equal to
* desired_time. desired_time must be greater than min_time and less
* than max_time, which should correspond to actual packets in the
* file. min_pos is the file position (byte offset) corresponding to
* the min_time packet and max_pos is the same for the max_time packet.
*
* Returns non-zero on success, 0 if the given position is beyond max_pos.
*
* NOTE: when calling this routine, the sf_readfile stream *must* be
* already aligned so that the next call to sf_next_packet() will yield
* a valid packet.
*/
int
sf_find_packet( pcap_t *p,
struct timeval *min_time, long min_pos,
struct timeval *max_time, long max_pos,
struct timeval *desired_time )
{
int status = 1;
struct timeval min_time_copy, max_time_copy;
u_int num_bytes = MAX_BYTES_FOR_DEFINITE_HEADER;
int num_bytes_read;
fpos_t desired_pos, present_pos;
u_char *buf, *hdrpos;
struct pcap_pkthdr hdr;
buf = (u_char *) malloc( num_bytes );
if ( ! buf )
error( "malloc() failured in sf_find_packet()" );
min_time_copy = *min_time;
min_time = &min_time_copy;
max_time_copy = *max_time;
max_time = &max_time_copy;
for ( ; ; ) /* loop until positioned correctly */
{
desired_pos =
interpolated_position( min_time, min_pos,
max_time, max_pos,
desired_time );
if ( desired_pos < 0 )
{
status = 0;
break;
}
fgetpos( pcap_file( p ), &present_pos );
if ( present_pos <= desired_pos &&
desired_pos - present_pos < STRAIGHT_SCAN_THRESHOLD )
{ /* we're close enough to just blindly read ahead */
status = read_up_to( p, desired_time );
break;
}
/* Undershoot the target a little bit - it's much easier to
* then scan straight forward than to try to read backwards ...
*/
desired_pos -= STRAIGHT_SCAN_THRESHOLD / 2;
if ( desired_pos < min_pos )
desired_pos = min_pos;
if ( fsetpos( pcap_file( p ), &desired_pos ) < 0 )
error( "fsetpos() failed in sf_find_packet()" );
num_bytes_read =
fread( (char *) buf, 1, num_bytes, pcap_file( p ) );
if ( num_bytes_read == 0 )
/* This shouldn't ever happen because we try to
* undershoot, unless the dump file has only a
* couple packets in it ...
*/
error( "fread() failed in sf_find_packet()" );
if ( find_header( p, buf, num_bytes, min_time->tv_sec,
max_time->tv_sec, &hdrpos, &hdr ) !=
HEADER_DEFINITELY )
error( "can't find header at position %ld in dump file",
desired_pos );
/* Correct desired_pos to reflect beginning of packet. */
desired_pos += (hdrpos - buf);
/* Seek to the beginning of the header. */
if ( fsetpos( pcap_file( p ), &desired_pos ) < 0 )
error( "fsetpos() failed in sf_find_packet()" );
if ( sf_timestamp_less_than( &hdr.ts, desired_time ) )
{ /* too early in the file */
*min_time = hdr.ts;
min_pos = desired_pos;
}
else if ( sf_timestamp_less_than( desired_time, &hdr.ts ) )
{ /* too late in the file */
*max_time = hdr.ts;
max_pos = desired_pos;
}
else
/* got it! */
break;
}
free( (char *) buf );
return status;
}

View File

@ -1,293 +0,0 @@
.\" Copyright (c) 1988-1990 The Regents of the University of California.
.\" All rights reserved.
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that: (1) source code distributions
.\" retain the above copyright notice and this paragraph in its entirety, (2)
.\" distributions including binary code include the above copyright notice and
.\" this paragraph in its entirety in the documentation or other materials
.\" provided with the distribution, and (3) all advertising materials mentioning
.\" features or use of this software display the following acknowledgement:
.\" ``This product includes software developed by the University of California,
.\" Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
.\" the University nor the names of its contributors may be used to endorse
.\" or promote products derived from this software without specific prior
.\" written permission.
.\" THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
.\" WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
.\" MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
.\"
.\" $FreeBSD$
.\"
.Dd October 14, 1991
.Dt TCPSLICE 1
.Os
.Sh NAME
.Nm tcpslice
.Nd extract pieces of and/or glue together tcpdump files
.Sh SYNOPSIS
.Nm
.Op Fl dRrt
.Op Fl w Ar file
.Op Ar start-time Op end-time
.Ar
.Sh DESCRIPTION
The
.Nm
utility extracts portions of packet-trace files generated using
.Xr tcpdump 1 Ns 's
.Fl w
flag.
It can also be used to glue together several such files, as discussed
below.
.Pp
The basic operation of
.Nm
is to copy to
.Pa stdout
all packets from its input file(s) whose timestamps fall
within a given range.
The starting and ending times of the range
may be specified on the command line.
All ranges are inclusive.
The starting time defaults
to the time of the first packet in the first input file; we call
this the
.Em first time .
The ending time defaults to ten years after the starting time.
Thus, the command
.Nm
.Ar trace-file
simply copies
.Ar trace-file
to
.Pa stdout
(assuming the file does not include more than
ten years' worth of data).
.Pp
There are a number of ways to specify times.
The first is using
Unix timestamps of the form
.Em sssssssss.uuuuuu
(this is the format specified by
.Xr tcpdump 1 Ns 's
.Fl tt
flag).
For example,
.Em 654321098.7654
specifies 38 seconds and 765,400 microseconds
after 8:51PM PDT, Sept.\& 25, 1990.
.Pp
All examples in this manual are given
for PDT times, but when displaying times and interpreting times symbolically
as discussed below,
.Nm
uses the local timezone, regardless of the timezone in which the
.Xr tcpdump 1
file was generated.
The daylight-savings setting used is that which is
appropriate for the local timezone at the date in question.
For example,
times associated with summer months will usually include daylight-savings
effects, and those with winter months will not.
.Pp
Times may also be specified relative
to either the
.Em first time
(when specifying a starting time)
or the starting time (when specifying an ending time)
by preceding a numeric value in seconds with a `+'.
For example, a starting time of
.Em +200
indicates 200 seconds after the
.Em first time ,
and the two arguments
.Em +200 +300
indicate from 200 seconds after the
.Em first time
through 500 seconds after the
.Em first time .
.Pp
Times may also be specified in terms of years (y), months (m), days (d),
hours (h), minutes (m), seconds (s), and microseconds(u).
For example,
the Unix timestamp 654321098.7654 discussed above could also be expressed
as
.Em 90y9m25d20h51m38s765400u .
.Pp
When specifying times using this style, fields that are omitted default
as follows.
If the omitted field is a unit
.Em greater
than that of the first specified field, then its value defaults to
the corresponding value taken from either
.Em first time
(if the starting time is being specified) or the starting time
(if the ending time is being specified).
If the omitted field is a unit
.Em less
than that of the first specified field, then it defaults to zero.
For example, suppose that the input file has a
.Em first time
of the Unix timestamp mentioned above, i.e., 38 seconds and 765,400 microseconds
after 8:51PM PDT, Sept.\& 25, 1990.
To specify 9:36PM PDT (exactly) on the
same date we could use
.Em 21h36m .
To specify a range from 9:36PM PDT through 1:54AM PDT the next day we
could use
.Em 21h36m 26d1h54m .
.Pp
Relative times can also be specified when using the
.Em ymdhmsu
format.
Omitted fields then default to 0 if the unit of the field is
.Em greater
than that of the first specified field, and to the corresponding value
taken from either the
.Em first time
or the starting time if the omitted field's unit is
.Em less
than that of the first specified field.
Given a
.Em first time
of the Unix timestamp mentioned above,
.Em 22h +1h10m
specifies a range from 10:00PM PDT on that date through 11:10PM PDT, and
.Em +1h +1h10m
specifies a range from 38.7654 seconds after 9:51PM PDT through 38.7654
seconds after 11:01PM PDT.
The first hour of the file could be extracted
using
.Em +0 +1h .
.Pp
Note that with the
.Em ymdhmsu
format there is an ambiguity between using
.Em m
for `month' or for `minute'.
The ambiguity is resolved as follows: if an
.Em m
field is followed by a
.Em d
field then it is interpreted as specifying months; otherwise it
specifies minutes.
.Pp
If more than one input file is specified then
.Nm
first copies packets lying in the given range from the first file; it
then increases the starting time of the range to lie just beyond the
timestamp of the last packet in the first file, repeats the process
with the second file, and so on.
Thus files with interleaved packets
are
.Em not
merged.
For a given file, only packets that are newer than any in the
preceding files will be considered.
This mechanism avoids any possibility
of a packet occurring more than once in the output.
.Sh OPTIONS
If any of
.Fl R ,
.Fl r
or
.Fl t
are specified then
.Nm
reports the timestamps of the first and last packets in each input file
and exits.
Only one of these three options may be specified.
.Pp
The following options are available:
.Bl -tag -width indent
.It Fl d
Dump the start and end times specified by the given range and
exit.
This option is useful for checking that the given range actually
specifies the times you think it does.
If one of
.Fl R ,
.Fl r
or
.Fl t
has been specified then the times are dumped in the corresponding
format; otherwise, raw format
.Pq Fl R
is used.
.It Fl R
Dump the timestamps of the first and last packets in each input file
as raw timestamps (i.e., in the form
.Em sssssssss.uuuuuu ) .
.It Fl r
Same as
.Fl R
except the timestamps are dumped in human-readable format, similar
to that used by
.Xr date 1 .
.It Fl t
Same as
.Fl R
except the timestamps are dumped in
.Nm
format, i.e., in the
.Em ymdhmsu
format discussed above.
.It Fl w Ar file
Direct the output to
.Ar file
rather than
.Pa stdout .
.El
.Sh SEE ALSO
.Xr tcpdump 1
.Sh AUTHORS
.An Vern Paxson Aq vern@ee.lbl.gov ,
of Lawrence Berkeley Laboratory, University of California, Berkeley, CA.
.Sh BUGS
An input filename that beings with a digit or a `+' can be confused
with a start/end time.
Such filenames can be specified with a
leading `./'; for example, specify the file `04Jul76.trace' as
`./04Jul76.trace'.
.Pp
The
.Nm
utility cannot read its input from
.Pa stdin ,
since it uses random-access
to rummage through its input files.
.Pp
The
.Nm
utility refuses to write to its output if it is a terminal
(as indicated by
.Xr isatty 3 ) .
This is not a bug but a feature,
to prevent it from spraying binary data to the user's terminal.
Note that this means you must either redirect
.Pa stdout
or specify an
output file via
.Fl w .
.Pp
The
.Nm
utility will not work properly on
.Xr tcpdump 1
files spanning more than one year;
with files containing portions of packets whose original length was
more than 65,535 bytes; nor with files containing fewer than three packets.
Such files result in
the error message: `couldn't find final packet in file'.
These problems
are due to the interpolation scheme used by
.Nm
to greatly speed up its processing when dealing with large trace files.
Note that
.Nm
can efficiently extract slices from the middle of trace files of any
size, and can also work with truncated trace files (i.e., the final packet
in the file is only partially present, typically due to
.Xr tcpdump 1
being ungracefully killed).

View File

@ -1,626 +0,0 @@
/*
* Copyright (c) 1987-1990 The Regents of the University of California.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that: (1) source code distributions
* retain the above copyright notice and this paragraph in its entirety, (2)
* distributions including binary code include the above copyright notice and
* this paragraph in its entirety in the documentation or other materials
* provided with the distribution, and (3) all advertising materials mentioning
* features or use of this software display the following acknowledgement:
* ``This product includes software developed by the University of California,
* Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
* the University nor the names of its contributors may be used to endorse
* or promote products derived from this software without specific prior
* written permission.
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
#ifndef lint
static const char copyright[] =
"@(#) Copyright (c) 1987-1990\n\
The Regents of the University of California. All rights reserved.\n";
#endif /* not lint */
#ifndef lint
static const char rcsid[] =
"$FreeBSD$";
#endif /* not lint */
/*
* tcpslice - extract pieces of and/or glue together tcpdump files
*/
#include <err.h>
#include "tcpslice.h"
int tflag = 0; /* global that util routines are sensitive to */
int fddipad; /* XXX: libpcap needs this global */
/* Style in which to print timestamps; RAW is "secs.usecs"; READABLE is
* ala the Unix "date" tool; and PARSEABLE is tcpslice's custom format,
* designed to be easy to parse. The default is RAW.
*/
enum stamp_styles { TIMESTAMP_RAW, TIMESTAMP_READABLE, TIMESTAMP_PARSEABLE };
enum stamp_styles timestamp_style = TIMESTAMP_RAW;
#ifndef __FreeBSD__
extern int getopt( int argc, char **argv, char *optstring );
#endif
int is_timestamp( char *str );
long local_time_zone(long timestamp);
struct timeval parse_time(char *time_string, struct timeval base_time);
void fill_tm(char *time_string, int is_delta, struct tm *t, time_t *usecs_addr);
void get_file_range( char filename[], pcap_t **p,
struct timeval *first_time, struct timeval *last_time );
struct timeval first_packet_time(char filename[], pcap_t **p_addr);
void extract_slice(char filename[], char write_file_name[],
struct timeval *start_time, struct timeval *stop_time);
char *timestamp_to_string(struct timeval *timestamp);
void dump_times(pcap_t **p, char filename[]);
static void usage(void);
pcap_dumper_t *dumper = 0;
int
main(int argc, char **argv)
{
int op;
int dump_flag = 0;
int report_times = 0;
char *start_time_string = 0;
char *stop_time_string = 0;
char *write_file_name = "-"; /* default is stdout */
struct timeval first_time, start_time, stop_time;
pcap_t *pcap;
opterr = 0;
while ((op = getopt(argc, argv, "dRrtw:")) != -1)
switch (op) {
case 'd':
dump_flag = 1;
break;
case 'R':
++report_times;
timestamp_style = TIMESTAMP_RAW;
break;
case 'r':
++report_times;
timestamp_style = TIMESTAMP_READABLE;
break;
case 't':
++report_times;
timestamp_style = TIMESTAMP_PARSEABLE;
break;
case 'w':
write_file_name = optarg;
break;
default:
usage();
/* NOTREACHED */
}
if ( report_times > 1 )
error( "only one of -R, -r, or -t can be specified" );
if (optind < argc)
/* See if the next argument looks like a possible
* start time, and if so assume it is one.
*/
if (isdigit(argv[optind][0]) || argv[optind][0] == '+')
start_time_string = argv[optind++];
if (optind < argc)
if (isdigit(argv[optind][0]) || argv[optind][0] == '+')
stop_time_string = argv[optind++];
if (optind >= argc)
error("at least one input file must be given");
first_time = first_packet_time(argv[optind], &pcap);
pcap_close(pcap);
if (start_time_string)
start_time = parse_time(start_time_string, first_time);
else
start_time = first_time;
if (stop_time_string)
stop_time = parse_time(stop_time_string, start_time);
else
{
stop_time = start_time;
stop_time.tv_sec += 86400*3660; /* + 10 years; "forever" */
}
if (report_times) {
for (; optind < argc; ++optind)
dump_times(&pcap, argv[optind]);
}
if (dump_flag) {
printf( "start\t%s\nstop\t%s\n",
timestamp_to_string( &start_time ),
timestamp_to_string( &stop_time ) );
}
if (! report_times && ! dump_flag) {
if ( ! strcmp( write_file_name, "-" ) &&
isatty( fileno(stdout) ) )
error("stdout is a terminal; redirect or use -w");
for (; optind < argc; ++optind)
extract_slice(argv[optind], write_file_name,
&start_time, &stop_time);
}
return 0;
}
/* Returns non-zero if a string matches the format for a timestamp,
* 0 otherwise.
*/
int is_timestamp( char *str )
{
while ( isdigit(*str) || *str == '.' )
++str;
return *str == '\0';
}
/* Return the correction in seconds for the local time zone with respect
* to Greenwich time.
*/
long local_time_zone(long timestamp)
{
struct timeval now;
struct timezone tz;
long localzone;
time_t t = _long_to_time(timestamp);
if (gettimeofday(&now, &tz) < 0)
err(1, "gettimeofday");
localzone = tz.tz_minuteswest * -60;
if (localtime(&t)->tm_isdst)
localzone += 3600;
return localzone;
}
/* Given a string specifying a time (or a time offset) and a "base time"
* from which to compute offsets and fill in defaults, returns a timeval
* containing the specified time.
*/
struct timeval
parse_time(char *time_string, struct timeval base_time)
{
time_t tt = _long_to_time(base_time.tv_sec);
struct tm *bt = localtime(&tt);
struct tm t;
struct timeval result;
time_t usecs = 0;
int is_delta = (time_string[0] == '+');
if ( is_delta )
++time_string; /* skip over '+' sign */
if ( is_timestamp( time_string ) )
{ /* interpret as a raw timestamp or timestamp offset */
char *time_ptr;
result.tv_sec = atoi( time_string );
time_ptr = strchr( time_string, '.' );
if ( time_ptr )
{ /* microseconds are specified, too */
int num_digits = strlen( time_ptr + 1 );
result.tv_usec = atoi( time_ptr + 1 );
/* turn 123.456 into 123 seconds plus 456000 usec */
while ( num_digits++ < 6 )
result.tv_usec *= 10;
}
else
result.tv_usec = 0;
if ( is_delta )
{
result.tv_sec += base_time.tv_sec;
result.tv_usec += base_time.tv_usec;
if ( result.tv_usec >= 1000000 )
{
result.tv_usec -= 1000000;
++result.tv_sec;
}
}
return result;
}
if (is_delta) {
t = *bt;
usecs = base_time.tv_usec;
} else {
/* Zero struct (easy way around lack of tm_gmtoff/tm_zone
* under older systems) */
bzero((char *)&t, sizeof(t));
/* Set values to "not set" flag so we can later identify
* and default them.
*/
t.tm_sec = t.tm_min = t.tm_hour = t.tm_mday = t.tm_mon =
t.tm_year = -1;
}
fill_tm(time_string, is_delta, &t, &usecs);
/* Now until we reach a field that was specified, fill in the
* missing fields from the base time.
*/
#define CHECK_FIELD(field_name) \
if (t.field_name < 0) \
t.field_name = bt->field_name; \
else \
break
do { /* bogus do-while loop so "break" in CHECK_FIELD will work */
CHECK_FIELD(tm_year);
CHECK_FIELD(tm_mon);
CHECK_FIELD(tm_mday);
CHECK_FIELD(tm_hour);
CHECK_FIELD(tm_min);
CHECK_FIELD(tm_sec);
} while ( 0 );
/* Set remaining unspecified fields to 0. */
#define ZERO_FIELD_IF_NOT_SET(field_name,zero_val) \
if (t.field_name < 0) \
t.field_name = zero_val
if (! is_delta) {
ZERO_FIELD_IF_NOT_SET(tm_year,90); /* should never happen */
ZERO_FIELD_IF_NOT_SET(tm_mon,0);
ZERO_FIELD_IF_NOT_SET(tm_mday,1);
ZERO_FIELD_IF_NOT_SET(tm_hour,0);
ZERO_FIELD_IF_NOT_SET(tm_min,0);
ZERO_FIELD_IF_NOT_SET(tm_sec,0);
}
result.tv_sec = gwtm2secs(&t);
result.tv_sec -= local_time_zone(result.tv_sec);
result.tv_usec = usecs;
return result;
}
/* Fill in (or add to, if is_delta is true) the time values in the
* tm struct "t" as specified by the time specified in the string
* "time_string". "usecs_addr" is updated with the specified number
* of microseconds, if any.
*/
void
fill_tm(char *time_string, int is_delta, struct tm *t, time_t *usecs_addr)
{
char *t_start, *t_stop, format_ch;
int val;
#define SET_VAL(lhs,rhs) \
if (is_delta) \
lhs += rhs; \
else \
lhs = rhs
/* Loop through the time string parsing one specification at
* a time. Each specification has the form <number><letter>
* where <number> indicates the amount of time and <letter>
* the units.
*/
for (t_stop = t_start = time_string; *t_start; t_start = ++t_stop) {
if (! isdigit(*t_start))
error("bad date format %s, problem starting at %s",
time_string, t_start);
while (isdigit(*t_stop))
++t_stop;
if (! t_stop)
error("bad date format %s, problem starting at %s",
time_string, t_start);
val = atoi(t_start);
format_ch = *t_stop;
if ( isupper( format_ch ) )
format_ch = tolower( format_ch );
switch (format_ch) {
case 'y':
if ( val >= 1900 )
val -= 1900;
else if (val < 100 && !is_delta) {
if (val < 69) /* Same hack as date */
val += 100;
}
SET_VAL(t->tm_year, val);
break;
case 'm':
if (strchr(t_stop+1, 'D') ||
strchr(t_stop+1, 'd'))
/* it's months */
SET_VAL(t->tm_mon, val - 1);
else /* it's minutes */
SET_VAL(t->tm_min, val);
break;
case 'd':
SET_VAL(t->tm_mday, val);
break;
case 'h':
SET_VAL(t->tm_hour, val);
break;
case 's':
SET_VAL(t->tm_sec, val);
break;
case 'u':
SET_VAL(*usecs_addr, val);
break;
default:
error(
"bad date format %s, problem starting at %s",
time_string, t_start);
}
}
}
/* Return in first_time and last_time the timestamps of the first and
* last packets in the given file.
*/
void
get_file_range( char filename[], pcap_t **p,
struct timeval *first_time, struct timeval *last_time )
{
*first_time = first_packet_time( filename, p );
if ( ! sf_find_end( *p, first_time, last_time ) )
error( "couldn't find final packet in file %s", filename );
}
int snaplen;
/* Returns the timestamp of the first packet in the given tcpdump save
* file, which as a side-effect is initialized for further save-file
* reading.
*/
struct timeval
first_packet_time(char filename[], pcap_t **p_addr)
{
struct pcap_pkthdr hdr;
pcap_t *p;
char errbuf[PCAP_ERRBUF_SIZE];
p = *p_addr = pcap_open_offline(filename, errbuf);
if (! p)
error( "bad tcpdump file %s: %s", filename, errbuf );
snaplen = pcap_snapshot( p );
if (pcap_next(p, &hdr) == 0)
error( "bad status reading first packet in %s", filename );
return hdr.ts;
}
/* Extract from the given file all packets with timestamps between
* the two time values given (inclusive). These packets are written
* to the save file given by write_file_name.
*
* Upon return, start_time is adjusted to reflect a time just after
* that of the last packet written to the output.
*/
void
extract_slice(char filename[], char write_file_name[],
struct timeval *start_time, struct timeval *stop_time)
{
off_t start_pos, stop_pos;
struct timeval file_start_time, file_stop_time;
struct pcap_pkthdr hdr;
pcap_t *p;
char errbuf[PCAP_ERRBUF_SIZE];
p = pcap_open_offline(filename, errbuf);
if (! p)
error( "bad tcpdump file %s: %s", filename, errbuf );
snaplen = pcap_snapshot( p );
fgetpos( pcap_file( p ), &start_pos );
if ( ! dumper )
{
dumper = pcap_dump_open(p, write_file_name);
if ( ! dumper )
error( "error creating output file %s: ",
write_file_name, pcap_geterr( p ) );
}
if (pcap_next(p, &hdr) == 0)
error( "error reading packet in %s: ",
filename, pcap_geterr( p ) );
file_start_time = hdr.ts;
if ( ! sf_find_end( p, &file_start_time, &file_stop_time ) )
error( "problems finding end packet of file %s",
filename );
fgetpos( pcap_file( p ), &stop_pos );
/* sf_find_packet() requires that the time it's passed as its last
* argument be in the range [min_time, max_time], so we enforce
* that constraint here.
*/
if ( sf_timestamp_less_than( start_time, &file_start_time ) )
*start_time = file_start_time;
if ( sf_timestamp_less_than( &file_stop_time, start_time ) )
return; /* there aren't any packets of interest in the file */
sf_find_packet( p, &file_start_time, start_pos,
&file_stop_time, stop_pos,
start_time );
for ( ; ; )
{
struct timeval *timestamp;
const u_char *pkt = pcap_next( p, &hdr );
if ( pkt == 0 )
{
#ifdef notdef
int status;
if ( status != SFERR_EOF )
error( "bad status %d reading packet in %s",
status, filename );
#endif
break;
}
timestamp = &hdr.ts;
if ( ! sf_timestamp_less_than( timestamp, start_time ) )
{ /* packet is recent enough */
if ( sf_timestamp_less_than( stop_time, timestamp ) )
/* We've gone beyond the end of the region
* of interest ... We're done with this file.
*/
break;
pcap_dump((u_char *) dumper, &hdr, pkt);
*start_time = *timestamp;
/* We know that each packet is guaranteed to have
* a unique timestamp, so we push forward the
* allowed minimum time to weed out duplicate
* packets.
*/
++start_time->tv_usec;
}
}
pcap_close( p );
}
/* Translates a timestamp to the time format specified by the user.
* Returns a pointer to the translation residing in a static buffer.
* There are two such buffers, which are alternated on subseqeuent
* calls, so two calls may be made to this routine without worrying
* about the results of the first call being overwritten by the
* results of the second.
*/
char *
timestamp_to_string(struct timeval *timestamp)
{
struct tm *t;
time_t tt;
#define NUM_BUFFERS 2
static char buffers[NUM_BUFFERS][128];
static int buffer_to_use = 0;
char *buf;
buf = buffers[buffer_to_use];
buffer_to_use = (buffer_to_use + 1) % NUM_BUFFERS;
switch ( timestamp_style )
{
case TIMESTAMP_RAW:
sprintf(buf, "%lu.%06lu", timestamp->tv_sec, timestamp->tv_usec);
break;
case TIMESTAMP_READABLE:
tt = _long_to_time(timestamp->tv_sec);
t = localtime(&tt);
strcpy( buf, asctime( t ) );
buf[24] = '\0'; /* nuke final newline */
break;
case TIMESTAMP_PARSEABLE:
tt = _long_to_time(timestamp->tv_sec);
t = localtime(&tt);
if (t->tm_year >= 100)
t->tm_year += 1900;
sprintf( buf, "%02dy%02dm%02dd%02dh%02dm%02ds%06ldu",
t->tm_year, t->tm_mon + 1, t->tm_mday, t->tm_hour,
t->tm_min, t->tm_sec, timestamp->tv_usec );
break;
}
return buf;
}
/* Given a tcpdump save filename, reports on the times of the first
* and last packets in the file.
*/
void
dump_times(pcap_t **p, char filename[])
{
struct timeval first_time, last_time;
get_file_range( filename, p, &first_time, &last_time );
printf( "%s\t%s\t%s\n",
filename,
timestamp_to_string( &first_time ),
timestamp_to_string( &last_time ) );
}
static void
usage(void)
{
(void)fprintf(stderr, "tcpslice for tcpdump version %d.%d\n",
VERSION_MAJOR, VERSION_MINOR);
(void)fprintf(stderr,
"usage: tcpslice [-dRrt] [-w file] [start-time [end-time]] file ... \n");
exit(1);
}

View File

@ -1,59 +0,0 @@
/*
* Copyright (c) 1987-1990 The Regents of the University of California.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that: (1) source code distributions
* retain the above copyright notice and this paragraph in its entirety, (2)
* distributions including binary code include the above copyright notice and
* this paragraph in its entirety in the documentation or other materials
* provided with the distribution, and (3) all advertising materials mentioning
* features or use of this software display the following acknowledgement:
* ``This product includes software developed by the University of California,
* Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
* the University nor the names of its contributors may be used to endorse
* or promote products derived from this software without specific prior
* written permission.
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
#include <sys/types.h>
#include <sys/time.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <net/bpf.h>
#include <ctype.h>
#ifdef SOLARIS
#include <fcntl.h>
#endif
#ifdef __STDC__
#include <stdlib.h>
#endif
#include <stdio.h>
#if __STDC__
#include <stdarg.h>
#else
#include <varargs.h>
#endif
#include <string.h>
#include <unistd.h>
#include "pcap.h"
#include "version.h"
time_t gwtm2secs( struct tm *tm );
int sf_find_end( struct pcap *p, struct timeval *first_timestamp,
struct timeval *last_timestamp );
int sf_timestamp_less_than( struct timeval *t1, struct timeval *t2 );
int sf_find_packet( struct pcap *p,
struct timeval *min_time, long min_pos,
struct timeval *max_time, long max_pos,
struct timeval *desired_time );
void error(const char *fmt, ...);

View File

@ -1,56 +0,0 @@
/*
* Copyright (c) 1988-1990 The Regents of the University of California.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that: (1) source code distributions
* retain the above copyright notice and this paragraph in its entirety, (2)
* distributions including binary code include the above copyright notice and
* this paragraph in its entirety in the documentation or other materials
* provided with the distribution, and (3) all advertising materials mentioning
* features or use of this software display the following acknowledgement:
* ``This product includes software developed by the University of California,
* Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
* the University nor the names of its contributors may be used to endorse
* or promote products derived from this software without specific prior
* written permission.
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
#if !defined(lint) && !defined(__GNUC__)
static char rcsid[] =
"@(#) $FreeBSD$ (LBL)";
#endif
#include "tcpslice.h"
/* VARARGS */
void
#if __STDC__
error(const char *fmt, ...)
#else
error(fmt, va_alist)
char *fmt;
va_dcl
#endif
{
va_list ap;
(void)fprintf(stderr, "tcpslice: ");
#if __STDC__
va_start(ap, fmt);
#else
va_start(ap);
#endif
(void)vfprintf(stderr, fmt, ap);
va_end(ap);
if (*fmt) {
fmt += strlen(fmt);
if (fmt[-1] != '\n')
(void)fputc('\n', stderr);
}
exit(1);
/* NOTREACHED */
}