1/*
2 * Copyright (c) 1993, 1994, 1995, 1996, 1997
3 *	The Regents of the University of California.  All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that: (1) source code distributions
7 * retain the above copyright notice and this paragraph in its entirety, (2)
8 * distributions including binary code include the above copyright notice and
9 * this paragraph in its entirety in the documentation or other materials
10 * provided with the distribution, and (3) all advertising materials mentioning
11 * features or use of this software display the following acknowledgement:
12 * ``This product includes software developed by the University of California,
13 * Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
14 * the University nor the names of its contributors may be used to endorse
15 * or promote products derived from this software without specific prior
16 * written permission.
17 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
18 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
19 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
20 *
21 * sf-pcap.c - libpcap-file-format-specific code from savefile.c
22 *	Extraction/creation by Jeffrey Mogul, DECWRL
23 *	Modified by Steve McCanne, LBL.
24 *
25 * Used to save the received packet headers, after filtering, to
26 * a file, and then read them later.
27 * The first record in the file contains saved values for the machine
28 * dependent values so we can print the dump file on any architecture.
29 */
30
31#ifndef lint
32static const char rcsid[] _U_ =
33    "@(#) $Header$ (LBL)";
34#endif
35
36#ifdef HAVE_CONFIG_H
37#include "config.h"
38#endif
39
40#ifdef WIN32
41#include <pcap-stdinc.h>
42#else /* WIN32 */
43#if HAVE_INTTYPES_H
44#include <inttypes.h>
45#elif HAVE_STDINT_H
46#include <stdint.h>
47#endif
48#ifdef HAVE_SYS_BITYPES_H
49#include <sys/bitypes.h>
50#endif
51#include <sys/types.h>
52#endif /* WIN32 */
53
54#include <errno.h>
55#include <memory.h>
56#include <stdio.h>
57#include <stdlib.h>
58#include <string.h>
59
60#include "pcap-int.h"
61
62#include "pcap-common.h"
63
64#ifdef HAVE_OS_PROTO_H
65#include "os-proto.h"
66#endif
67
68#include "sf-pcap.h"
69
70/*
71 * Setting O_BINARY on DOS/Windows is a bit tricky
72 */
73#if defined(WIN32)
74  #define SET_BINMODE(f)  _setmode(_fileno(f), _O_BINARY)
75#elif defined(MSDOS)
76  #if defined(__HIGHC__)
77  #define SET_BINMODE(f)  setmode(f, O_BINARY)
78  #else
79  #define SET_BINMODE(f)  setmode(fileno(f), O_BINARY)
80  #endif
81#endif
82
83/*
84 * Standard libpcap format.
85 */
86#define TCPDUMP_MAGIC		0xa1b2c3d4
87
88/*
89 * Alexey Kuznetzov's modified libpcap format.
90 */
91#define KUZNETZOV_TCPDUMP_MAGIC	0xa1b2cd34
92
93/*
94 * Reserved for Francisco Mesquita <francisco.mesquita@radiomovel.pt>
95 * for another modified format.
96 */
97#define FMESQUITA_TCPDUMP_MAGIC	0xa1b234cd
98
99/*
100 * Navtel Communcations' format, with nanosecond timestamps,
101 * as per a request from Dumas Hwang <dumas.hwang@navtelcom.com>.
102 */
103#define NAVTEL_TCPDUMP_MAGIC	0xa12b3c4d
104
105/*
106 * Normal libpcap format, except for seconds/nanoseconds timestamps,
107 * as per a request by Ulf Lamping <ulf.lamping@web.de>
108 */
109#define NSEC_TCPDUMP_MAGIC	0xa1b23c4d
110
111/*
112 * Mechanism for storing information about a capture in the upper
113 * 6 bits of a linktype value in a capture file.
114 *
115 * LT_LINKTYPE_EXT(x) extracts the additional information.
116 *
117 * The rest of the bits are for a value describing the link-layer
118 * value.  LT_LINKTYPE(x) extracts that value.
119 */
120#define LT_LINKTYPE(x)		((x) & 0x03FFFFFF)
121#define LT_LINKTYPE_EXT(x)	((x) & 0xFC000000)
122
123static int pcap_next_packet(pcap_t *p, struct pcap_pkthdr *hdr, u_char **datap);
124
125/*
126 * Private data for reading pcap savefiles.
127 */
128typedef enum {
129	NOT_SWAPPED,
130	SWAPPED,
131	MAYBE_SWAPPED
132} swapped_type_t;
133
134typedef enum {
135	PASS_THROUGH,
136	SCALE_UP,
137	SCALE_DOWN
138} tstamp_scale_type_t;
139
140struct pcap_sf {
141	size_t hdrsize;
142	swapped_type_t lengths_swapped;
143	tstamp_scale_type_t scale_type;
144};
145
146/*
147 * Check whether this is a pcap savefile and, if it is, extract the
148 * relevant information from the header.
149 */
150pcap_t *
151pcap_check_header(bpf_u_int32 magic, FILE *fp, u_int precision, char *errbuf,
152    int *err)
153{
154	struct pcap_file_header hdr;
155	size_t amt_read;
156	pcap_t *p;
157	int swapped = 0;
158	struct pcap_sf *ps;
159
160	/*
161	 * Assume no read errors.
162	 */
163	*err = 0;
164
165	/*
166	 * Check whether the first 4 bytes of the file are the magic
167	 * number for a pcap savefile, or for a byte-swapped pcap
168	 * savefile.
169	 */
170	if (magic != TCPDUMP_MAGIC && magic != KUZNETZOV_TCPDUMP_MAGIC &&
171	    magic != NSEC_TCPDUMP_MAGIC) {
172		magic = SWAPLONG(magic);
173		if (magic != TCPDUMP_MAGIC && magic != KUZNETZOV_TCPDUMP_MAGIC &&
174		    magic != NSEC_TCPDUMP_MAGIC)
175			return (NULL);	/* nope */
176		swapped = 1;
177	}
178
179	/*
180	 * They are.  Put the magic number in the header, and read
181	 * the rest of the header.
182	 */
183	hdr.magic = magic;
184	amt_read = fread(((char *)&hdr) + sizeof hdr.magic, 1,
185	    sizeof(hdr) - sizeof(hdr.magic), fp);
186	if (amt_read != sizeof(hdr) - sizeof(hdr.magic)) {
187		if (ferror(fp)) {
188			snprintf(errbuf, PCAP_ERRBUF_SIZE,
189			    "error reading dump file: %s",
190			    pcap_strerror(errno));
191		} else {
192			snprintf(errbuf, PCAP_ERRBUF_SIZE,
193			    "truncated dump file; tried to read %lu file header bytes, only got %lu",
194			    (unsigned long)sizeof(hdr),
195			    (unsigned long)amt_read);
196		}
197		*err = 1;
198		return (NULL);
199	}
200
201	/*
202	 * If it's a byte-swapped capture file, byte-swap the header.
203	 */
204	if (swapped) {
205		hdr.version_major = SWAPSHORT(hdr.version_major);
206		hdr.version_minor = SWAPSHORT(hdr.version_minor);
207		hdr.thiszone = SWAPLONG(hdr.thiszone);
208		hdr.sigfigs = SWAPLONG(hdr.sigfigs);
209		hdr.snaplen = SWAPLONG(hdr.snaplen);
210		hdr.linktype = SWAPLONG(hdr.linktype);
211	}
212
213	if (hdr.version_major < PCAP_VERSION_MAJOR) {
214		snprintf(errbuf, PCAP_ERRBUF_SIZE,
215		    "archaic pcap savefile format");
216		*err = 1;
217		return (NULL);
218	}
219
220	/*
221	 * OK, this is a good pcap file.
222	 * Allocate a pcap_t for it.
223	 */
224	p = pcap_open_offline_common(errbuf, sizeof (struct pcap_sf));
225	if (p == NULL) {
226		/* Allocation failed. */
227		*err = 1;
228		return (NULL);
229	}
230	p->swapped = swapped;
231	p->version_major = hdr.version_major;
232	p->version_minor = hdr.version_minor;
233	p->tzoff = hdr.thiszone;
234	p->snapshot = hdr.snaplen;
235	p->linktype = linktype_to_dlt(LT_LINKTYPE(hdr.linktype));
236	p->linktype_ext = LT_LINKTYPE_EXT(hdr.linktype);
237
238	p->next_packet_op = pcap_next_packet;
239
240	ps = p->priv;
241
242	p->opt.tstamp_precision = precision;
243
244	/*
245	 * Will we need to scale the timestamps to match what the
246	 * user wants?
247	 */
248	switch (precision) {
249
250	case PCAP_TSTAMP_PRECISION_MICRO:
251		if (magic == NSEC_TCPDUMP_MAGIC) {
252			/*
253			 * The file has nanoseconds, the user
254			 * wants microseconds; scale the
255			 * precision down.
256			 */
257			ps->scale_type = SCALE_DOWN;
258		} else {
259			/*
260			 * The file has microseconds, the
261			 * user wants microseconds; nothing to do.
262			 */
263			ps->scale_type = PASS_THROUGH;
264		}
265		break;
266
267	case PCAP_TSTAMP_PRECISION_NANO:
268		if (magic == NSEC_TCPDUMP_MAGIC) {
269			/*
270			 * The file has nanoseconds, the
271			 * user wants nanoseconds; nothing to do.
272			 */
273			ps->scale_type = PASS_THROUGH;
274		} else {
275			/*
276			 * The file has microoseconds, the user
277			 * wants nanoseconds; scale the
278			 * precision up.
279			 */
280			ps->scale_type = SCALE_UP;
281		}
282		break;
283
284	default:
285		snprintf(errbuf, PCAP_ERRBUF_SIZE,
286		    "unknown time stamp resolution %u", precision);
287		free(p);
288		*err = 1;
289		return (NULL);
290	}
291
292	/*
293	 * We interchanged the caplen and len fields at version 2.3,
294	 * in order to match the bpf header layout.  But unfortunately
295	 * some files were written with version 2.3 in their headers
296	 * but without the interchanged fields.
297	 *
298	 * In addition, DG/UX tcpdump writes out files with a version
299	 * number of 543.0, and with the caplen and len fields in the
300	 * pre-2.3 order.
301	 */
302	switch (hdr.version_major) {
303
304	case 2:
305		if (hdr.version_minor < 3)
306			ps->lengths_swapped = SWAPPED;
307		else if (hdr.version_minor == 3)
308			ps->lengths_swapped = MAYBE_SWAPPED;
309		else
310			ps->lengths_swapped = NOT_SWAPPED;
311		break;
312
313	case 543:
314		ps->lengths_swapped = SWAPPED;
315		break;
316
317	default:
318		ps->lengths_swapped = NOT_SWAPPED;
319		break;
320	}
321
322	if (magic == KUZNETZOV_TCPDUMP_MAGIC) {
323		/*
324		 * XXX - the patch that's in some versions of libpcap
325		 * changes the packet header but not the magic number,
326		 * and some other versions with this magic number have
327		 * some extra debugging information in the packet header;
328		 * we'd have to use some hacks^H^H^H^H^Hheuristics to
329		 * detect those variants.
330		 *
331		 * Ethereal does that, but it does so by trying to read
332		 * the first two packets of the file with each of the
333		 * record header formats.  That currently means it seeks
334		 * backwards and retries the reads, which doesn't work
335		 * on pipes.  We want to be able to read from a pipe, so
336		 * that strategy won't work; we'd have to buffer some
337		 * data ourselves and read from that buffer in order to
338		 * make that work.
339		 */
340		ps->hdrsize = sizeof(struct pcap_sf_patched_pkthdr);
341
342		if (p->linktype == DLT_EN10MB) {
343			/*
344			 * This capture might have been done in raw mode
345			 * or cooked mode.
346			 *
347			 * If it was done in cooked mode, p->snapshot was
348			 * passed to recvfrom() as the buffer size, meaning
349			 * that the most packet data that would be copied
350			 * would be p->snapshot.  However, a faked Ethernet
351			 * header would then have been added to it, so the
352			 * most data that would be in a packet in the file
353			 * would be p->snapshot + 14.
354			 *
355			 * We can't easily tell whether the capture was done
356			 * in raw mode or cooked mode, so we'll assume it was
357			 * cooked mode, and add 14 to the snapshot length.
358			 * That means that, for a raw capture, the snapshot
359			 * length will be misleading if you use it to figure
360			 * out why a capture doesn't have all the packet data,
361			 * but there's not much we can do to avoid that.
362			 */
363			p->snapshot += 14;
364		}
365	} else
366		ps->hdrsize = sizeof(struct pcap_sf_pkthdr);
367
368	/*
369	 * Allocate a buffer for the packet data.
370	 */
371	p->bufsize = p->snapshot;
372	if (p->bufsize <= 0) {
373		/*
374		 * Bogus snapshot length; use 64KiB as a fallback.
375		 */
376		p->bufsize = 65536;
377	}
378	p->buffer = malloc(p->bufsize);
379	if (p->buffer == NULL) {
380		snprintf(errbuf, PCAP_ERRBUF_SIZE, "out of memory");
381		free(p);
382		*err = 1;
383		return (NULL);
384	}
385
386	p->cleanup_op = sf_cleanup;
387
388	return (p);
389}
390
391/*
392 * Read and return the next packet from the savefile.  Return the header
393 * in hdr and a pointer to the contents in data.  Return 0 on success, 1
394 * if there were no more packets, and -1 on an error.
395 */
396static int
397pcap_next_packet(pcap_t *p, struct pcap_pkthdr *hdr, u_char **data)
398{
399	struct pcap_sf *ps = p->priv;
400	struct pcap_sf_patched_pkthdr sf_hdr;
401	FILE *fp = p->rfile;
402	size_t amt_read;
403	bpf_u_int32 t;
404
405	/*
406	 * Read the packet header; the structure we use as a buffer
407	 * is the longer structure for files generated by the patched
408	 * libpcap, but if the file has the magic number for an
409	 * unpatched libpcap we only read as many bytes as the regular
410	 * header has.
411	 */
412	amt_read = fread(&sf_hdr, 1, ps->hdrsize, fp);
413	if (amt_read != ps->hdrsize) {
414		if (ferror(fp)) {
415			snprintf(p->errbuf, PCAP_ERRBUF_SIZE,
416			    "error reading dump file: %s",
417			    pcap_strerror(errno));
418			return (-1);
419		} else {
420			if (amt_read != 0) {
421				snprintf(p->errbuf, PCAP_ERRBUF_SIZE,
422				    "truncated dump file; tried to read %lu header bytes, only got %lu",
423				    (unsigned long)ps->hdrsize,
424				    (unsigned long)amt_read);
425				return (-1);
426			}
427			/* EOF */
428			return (1);
429		}
430	}
431
432	if (p->swapped) {
433		/* these were written in opposite byte order */
434		hdr->caplen = SWAPLONG(sf_hdr.caplen);
435		hdr->len = SWAPLONG(sf_hdr.len);
436		hdr->ts.tv_sec = SWAPLONG(sf_hdr.ts.tv_sec);
437		hdr->ts.tv_usec = SWAPLONG(sf_hdr.ts.tv_usec);
438	} else {
439		hdr->caplen = sf_hdr.caplen;
440		hdr->len = sf_hdr.len;
441		hdr->ts.tv_sec = sf_hdr.ts.tv_sec;
442		hdr->ts.tv_usec = sf_hdr.ts.tv_usec;
443	}
444
445	switch (ps->scale_type) {
446
447	case PASS_THROUGH:
448		/*
449		 * Just pass the time stamp through.
450		 */
451		break;
452
453	case SCALE_UP:
454		/*
455		 * File has microseconds, user wants nanoseconds; convert
456		 * it.
457		 */
458		hdr->ts.tv_usec = hdr->ts.tv_usec * 1000;
459		break;
460
461	case SCALE_DOWN:
462		/*
463		 * File has nanoseconds, user wants microseconds; convert
464		 * it.
465		 */
466		hdr->ts.tv_usec = hdr->ts.tv_usec / 1000;
467		break;
468	}
469
470	/* Swap the caplen and len fields, if necessary. */
471	switch (ps->lengths_swapped) {
472
473	case NOT_SWAPPED:
474		break;
475
476	case MAYBE_SWAPPED:
477		if (hdr->caplen <= hdr->len) {
478			/*
479			 * The captured length is <= the actual length,
480			 * so presumably they weren't swapped.
481			 */
482			break;
483		}
484		/* FALLTHROUGH */
485
486	case SWAPPED:
487		t = hdr->caplen;
488		hdr->caplen = hdr->len;
489		hdr->len = t;
490		break;
491	}
492
493	if (hdr->caplen > p->bufsize) {
494		/*
495		 * This can happen due to Solaris 2.3 systems tripping
496		 * over the BUFMOD problem and not setting the snapshot
497		 * correctly in the savefile header.  If the caplen isn't
498		 * grossly wrong, try to salvage.
499		 */
500		static u_char *tp = NULL;
501		static size_t tsize = 0;
502
503		if (hdr->caplen > 65535) {
504			snprintf(p->errbuf, PCAP_ERRBUF_SIZE,
505			    "bogus savefile header");
506			return (-1);
507		}
508
509		if (tsize < hdr->caplen) {
510			tsize = ((hdr->caplen + 1023) / 1024) * 1024;
511			if (tp != NULL)
512				free((u_char *)tp);
513			tp = (u_char *)malloc(tsize);
514			if (tp == NULL) {
515				tsize = 0;
516				snprintf(p->errbuf, PCAP_ERRBUF_SIZE,
517				    "BUFMOD hack malloc");
518				return (-1);
519			}
520		}
521		amt_read = fread((char *)tp, 1, hdr->caplen, fp);
522		if (amt_read != hdr->caplen) {
523			if (ferror(fp)) {
524				snprintf(p->errbuf, PCAP_ERRBUF_SIZE,
525				    "error reading dump file: %s",
526				    pcap_strerror(errno));
527			} else {
528				snprintf(p->errbuf, PCAP_ERRBUF_SIZE,
529				    "truncated dump file; tried to read %u captured bytes, only got %lu",
530				    hdr->caplen, (unsigned long)amt_read);
531			}
532			return (-1);
533		}
534		/*
535		 * We can only keep up to p->bufsize bytes.  Since
536		 * caplen > p->bufsize is exactly how we got here,
537		 * we know we can only keep the first p->bufsize bytes
538		 * and must drop the remainder.  Adjust caplen accordingly,
539		 * so we don't get confused later as to how many bytes we
540		 * have to play with.
541		 */
542		hdr->caplen = p->bufsize;
543		memcpy(p->buffer, (char *)tp, p->bufsize);
544	} else {
545		/* read the packet itself */
546		amt_read = fread(p->buffer, 1, hdr->caplen, fp);
547		if (amt_read != hdr->caplen) {
548			if (ferror(fp)) {
549				snprintf(p->errbuf, PCAP_ERRBUF_SIZE,
550				    "error reading dump file: %s",
551				    pcap_strerror(errno));
552			} else {
553				snprintf(p->errbuf, PCAP_ERRBUF_SIZE,
554				    "truncated dump file; tried to read %u captured bytes, only got %lu",
555				    hdr->caplen, (unsigned long)amt_read);
556			}
557			return (-1);
558		}
559	}
560	*data = p->buffer;
561
562	if (p->swapped)
563		swap_pseudo_headers(p->linktype, hdr, *data);
564
565	return (0);
566}
567
568static int
569sf_write_header(pcap_t *p, FILE *fp, int linktype, int thiszone, int snaplen)
570{
571	struct pcap_file_header hdr;
572
573	hdr.magic = p->opt.tstamp_precision == PCAP_TSTAMP_PRECISION_NANO ? NSEC_TCPDUMP_MAGIC : TCPDUMP_MAGIC;
574	hdr.version_major = PCAP_VERSION_MAJOR;
575	hdr.version_minor = PCAP_VERSION_MINOR;
576
577	hdr.thiszone = thiszone;
578	hdr.snaplen = snaplen;
579	hdr.sigfigs = 0;
580	hdr.linktype = linktype;
581
582	if (fwrite((char *)&hdr, sizeof(hdr), 1, fp) != 1)
583		return (-1);
584
585	return (0);
586}
587
588/*
589 * Output a packet to the initialized dump file.
590 */
591void
592pcap_dump(u_char *user, const struct pcap_pkthdr *h, const u_char *sp)
593{
594	register FILE *f;
595	struct pcap_sf_pkthdr sf_hdr;
596
597	f = (FILE *)user;
598	sf_hdr.ts.tv_sec  = h->ts.tv_sec;
599	sf_hdr.ts.tv_usec = h->ts.tv_usec;
600	sf_hdr.caplen     = h->caplen;
601	sf_hdr.len        = h->len;
602	/* XXX we should check the return status */
603	(void)fwrite(&sf_hdr, sizeof(sf_hdr), 1, f);
604	(void)fwrite(sp, h->caplen, 1, f);
605}
606
607static pcap_dumper_t *
608pcap_setup_dump(pcap_t *p, int linktype, FILE *f, const char *fname)
609{
610
611#if defined(WIN32) || defined(MSDOS)
612	/*
613	 * If we're writing to the standard output, put it in binary
614	 * mode, as savefiles are binary files.
615	 *
616	 * Otherwise, we turn off buffering.
617	 * XXX - why?  And why not on the standard output?
618	 */
619	if (f == stdout)
620		SET_BINMODE(f);
621	else
622		setbuf(f, NULL);
623#endif
624	if (sf_write_header(p, f, linktype, p->tzoff, p->snapshot) == -1) {
625		snprintf(p->errbuf, PCAP_ERRBUF_SIZE, "Can't write to %s: %s",
626		    fname, pcap_strerror(errno));
627		if (f != stdout)
628			(void)fclose(f);
629		return (NULL);
630	}
631	return ((pcap_dumper_t *)f);
632}
633
634/*
635 * Initialize so that sf_write() will output to the file named 'fname'.
636 */
637pcap_dumper_t *
638pcap_dump_open(pcap_t *p, const char *fname)
639{
640	FILE *f;
641	int linktype;
642
643	/*
644	 * If this pcap_t hasn't been activated, it doesn't have a
645	 * link-layer type, so we can't use it.
646	 */
647	if (!p->activated) {
648		snprintf(p->errbuf, PCAP_ERRBUF_SIZE,
649		    "%s: not-yet-activated pcap_t passed to pcap_dump_open",
650		    fname);
651		return (NULL);
652	}
653	linktype = dlt_to_linktype(p->linktype);
654	if (linktype == -1) {
655		snprintf(p->errbuf, PCAP_ERRBUF_SIZE,
656		    "%s: link-layer type %d isn't supported in savefiles",
657		    fname, p->linktype);
658		return (NULL);
659	}
660	linktype |= p->linktype_ext;
661
662	if (fname[0] == '-' && fname[1] == '\0') {
663		f = stdout;
664		fname = "standard output";
665	} else {
666#if !defined(WIN32) && !defined(MSDOS)
667		f = fopen(fname, "w");
668#else
669		f = fopen(fname, "wb");
670#endif
671		if (f == NULL) {
672			snprintf(p->errbuf, PCAP_ERRBUF_SIZE, "%s: %s",
673			    fname, pcap_strerror(errno));
674			return (NULL);
675		}
676	}
677	return (pcap_setup_dump(p, linktype, f, fname));
678}
679
680/*
681 * Initialize so that sf_write() will output to the given stream.
682 */
683pcap_dumper_t *
684pcap_dump_fopen(pcap_t *p, FILE *f)
685{
686	int linktype;
687
688	linktype = dlt_to_linktype(p->linktype);
689	if (linktype == -1) {
690		snprintf(p->errbuf, PCAP_ERRBUF_SIZE,
691		    "stream: link-layer type %d isn't supported in savefiles",
692		    p->linktype);
693		return (NULL);
694	}
695	linktype |= p->linktype_ext;
696
697	return (pcap_setup_dump(p, linktype, f, "stream"));
698}
699
700FILE *
701pcap_dump_file(pcap_dumper_t *p)
702{
703	return ((FILE *)p);
704}
705
706long
707pcap_dump_ftell(pcap_dumper_t *p)
708{
709	return (ftell((FILE *)p));
710}
711
712int
713pcap_dump_flush(pcap_dumper_t *p)
714{
715
716	if (fflush((FILE *)p) == EOF)
717		return (-1);
718	else
719		return (0);
720}
721
722void
723pcap_dump_close(pcap_dumper_t *p)
724{
725
726#ifdef notyet
727	if (ferror((FILE *)p))
728		return-an-error;
729	/* XXX should check return from fclose() too */
730#endif
731	(void)fclose((FILE *)p);
732}
733