In a Network Monitor capture file, get the starting offsets of frames
[obnox/wireshark/wip.git] / wiretap / netmon.c
1 /* netmon.c
2  *
3  * $Id: netmon.c,v 1.26 2000/03/22 07:06:54 guy Exp $
4  *
5  * Wiretap Library
6  * Copyright (c) 1998 by Gilbert Ramirez <gram@xiexie.org>
7  * 
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License
10  * as published by the Free Software Foundation; either version 2
11  * of the License, or (at your option) any later version.
12  * 
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  * 
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
21  *
22  */
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26 #include <errno.h>
27 #include <time.h>
28 #include <string.h>
29 #include "wtap.h"
30 #include "file_wrappers.h"
31 #include "buffer.h"
32 #include "netmon.h"
33
34 /* The file at
35  *
36  *      ftp://ftp.microsoft.com/developr/drg/cifs/cifs/Bhfile.zip
37  *
38  * contains "STRUCT.H", which declares the typedef CAPTUREFILE_HEADER
39  * for the header of a Microsoft Network Monitor capture file.
40  */
41
42 /* Capture file header, *including* magic number, is padded to 128 bytes. */
43 #define CAPTUREFILE_HEADER_SIZE 128
44
45 /* Magic number in Network Monitor 1.x files. */
46 static const char netmon_1_x_magic[] = {
47         'R', 'T', 'S', 'S'
48 };
49
50 /* Magic number in Network Monitor 2.x files. */
51 static const char netmon_2_x_magic[] = {
52         'G', 'M', 'B', 'U'
53 };
54
55 /* Network Monitor file header (minus magic number). */
56 struct netmon_hdr {
57         guint8  ver_minor;      /* minor version number */
58         guint8  ver_major;      /* major version number */
59         guint16 network;        /* network type */
60         guint16 ts_year;        /* year of capture start */
61         guint16 ts_month;       /* month of capture start (January = 1) */
62         guint16 ts_dow;         /* day of week of capture start (Sun = 0) */
63         guint16 ts_day;         /* day of month of capture start */
64         guint16 ts_hour;        /* hour of capture start */
65         guint16 ts_min;         /* minute of capture start */
66         guint16 ts_sec;         /* second of capture start */
67         guint16 ts_msec;        /* millisecond of capture start */
68         guint32 frametableoffset;       /* frame index table offset */
69         guint32 frametablelength;       /* frame index table size */
70         guint32 userdataoffset;         /* user data offset */
71         guint32 userdatalength;         /* user data size */
72         guint32 commentdataoffset;      /* comment data offset */
73         guint32 commentdatalength;      /* comment data size */
74         guint32 statisticsoffset;       /* offset to statistics structure */
75         guint32 statisticslength;       /* length of statistics structure */
76         guint32 networkinfooffset;      /* offset to network info structure */
77         guint32 networkinfolength;      /* length of network info structure */
78 };
79
80 /* Network Monitor 1.x record header; not defined in STRUCT.H, but deduced by
81  * looking at capture files. */
82 struct netmonrec_1_x_hdr {
83         guint32 ts_delta;       /* time stamp - msecs since start of capture */
84         guint16 orig_len;       /* actual length of packet */
85         guint16 incl_len;       /* number of octets captured in file */
86 };
87
88 /* Network Monitor 2.x record header; not defined in STRUCT.H, but deduced by
89  * looking at capture files. */
90 struct netmonrec_2_x_hdr {
91         guint32 ts_delta_lo;    /* time stamp - usecs since start of capture */
92         guint32 ts_delta_hi;    /* time stamp - usecs since start of capture */
93         guint32 orig_len;       /* actual length of packet */
94         guint32 incl_len;       /* number of octets captured in file */
95 };
96
97 static int netmon_read(wtap *wth, int *err);
98 static void netmon_close(wtap *wth);
99 static gboolean netmon_dump(wtap_dumper *wdh, const struct wtap_pkthdr *phdr,
100     const u_char *pd, int *err);
101 static gboolean netmon_dump_close(wtap_dumper *wdh, int *err);
102
103 int netmon_open(wtap *wth, int *err)
104 {
105         int bytes_read;
106         char magic[sizeof netmon_1_x_magic];
107         struct netmon_hdr hdr;
108         int file_type;
109         static const int netmon_encap[] = {
110                 WTAP_ENCAP_UNKNOWN,
111                 WTAP_ENCAP_ETHERNET,
112                 WTAP_ENCAP_TR,
113                 WTAP_ENCAP_FDDI_BITSWAPPED,
114                 WTAP_ENCAP_UNKNOWN,     /* WAN */
115                 WTAP_ENCAP_UNKNOWN,     /* LocalTalk */
116                 WTAP_ENCAP_UNKNOWN,     /* "DIX" - should not occur */
117                 WTAP_ENCAP_UNKNOWN,     /* ARCNET raw */
118                 WTAP_ENCAP_UNKNOWN,     /* ARCNET 878.2 */
119                 WTAP_ENCAP_UNKNOWN,     /* ATM */
120                 WTAP_ENCAP_UNKNOWN,     /* Wireless WAN */
121                 WTAP_ENCAP_UNKNOWN      /* IrDA */
122         };
123         #define NUM_NETMON_ENCAPS (sizeof netmon_encap / sizeof netmon_encap[0])
124         struct tm tm;
125         int frame_table_offset;
126         guint32 frame_table_length;
127
128         /* Read in the string that should be at the start of a Network
129          * Monitor file */
130         file_seek(wth->fh, 0, SEEK_SET);
131         errno = WTAP_ERR_CANT_READ;
132         bytes_read = file_read(magic, 1, sizeof magic, wth->fh);
133         if (bytes_read != sizeof magic) {
134                 *err = file_error(wth->fh);
135                 if (*err != 0)
136                         return -1;
137                 return 0;
138         }
139
140         if (memcmp(magic, netmon_1_x_magic, sizeof netmon_1_x_magic) != 0
141          && memcmp(magic, netmon_2_x_magic, sizeof netmon_1_x_magic) != 0) {
142                 return 0;
143         }
144
145         /* Read the rest of the header. */
146         errno = WTAP_ERR_CANT_READ;
147         bytes_read = file_read(&hdr, 1, sizeof hdr, wth->fh);
148         if (bytes_read != sizeof hdr) {
149                 *err = file_error(wth->fh);
150                 if (*err != 0)
151                         return -1;
152                 return 0;
153         }
154
155         switch (hdr.ver_major) {
156
157         case 1:
158                 file_type = WTAP_FILE_NETMON_1_x;
159                 break;
160
161         case 2:
162                 file_type = WTAP_FILE_NETMON_2_x;
163                 break;
164
165         default:
166                 g_message("netmon: major version %u unsupported", hdr.ver_major);
167                 *err = WTAP_ERR_UNSUPPORTED;
168                 return -1;
169         }
170
171         hdr.network = pletohs(&hdr.network);
172         if (hdr.network >= NUM_NETMON_ENCAPS
173             || netmon_encap[hdr.network] == WTAP_ENCAP_UNKNOWN) {
174                 g_message("netmon: network type %u unknown or unsupported",
175                     hdr.network);
176                 *err = WTAP_ERR_UNSUPPORTED_ENCAP;
177                 return -1;
178         }
179
180         /* This is a netmon file */
181         wth->file_type = file_type;
182         wth->capture.netmon = g_malloc(sizeof(netmon_t));
183         wth->subtype_read = netmon_read;
184         wth->subtype_close = netmon_close;
185         wth->file_encap = netmon_encap[hdr.network];
186         wth->snapshot_length = 16384;   /* XXX - not available in header */
187         /*
188          * Convert the time stamp to a "time_t" and a number of
189          * milliseconds.
190          */
191         tm.tm_year = pletohs(&hdr.ts_year) - 1900;
192         tm.tm_mon = pletohs(&hdr.ts_month) - 1;
193         tm.tm_mday = pletohs(&hdr.ts_day);
194         tm.tm_hour = pletohs(&hdr.ts_hour);
195         tm.tm_min = pletohs(&hdr.ts_min);
196         tm.tm_sec = pletohs(&hdr.ts_sec);
197         tm.tm_isdst = -1;
198         wth->capture.netmon->start_secs = mktime(&tm);
199         /*
200          * XXX - what if "secs" is -1?  Unlikely, but if the capture was
201          * done in a time zone that switches between standard and summer
202          * time sometime other than when we do, and thus the time was one
203          * that doesn't exist here because a switch from standard to summer
204          * time zips over it, it could happen.
205          *
206          * On the other hand, if the capture was done in a different time
207          * zone, this won't work right anyway; unfortunately, the time
208          * zone isn't stored in the capture file (why the hell didn't
209          * they stuff a FILETIME, which is the number of 100-nanosecond
210          * intervals since 1601-01-01 00:00:00 "UTC", there, instead
211          * of stuffing a SYSTEMTIME, which is time-zone-dependent, there?).
212          */
213         wth->capture.netmon->start_usecs = pletohs(&hdr.ts_msec)*1000;
214
215         wth->capture.netmon->version_major = hdr.ver_major;
216
217         /*
218          * Get the offset of the frame index table.
219          */
220         frame_table_offset = pletohl(&hdr.frametableoffset);
221
222         /*
223          * It appears that some NetMon 2.x files don't have the
224          * first packet starting exactly 128 bytes into the file.
225          *
226          * Furthermore, it also appears that there are "holes" in
227          * the file, i.e. frame N+1 doesn't always follow immediately
228          * after frame N.
229          *
230          * Therefore, we must read the frame table, and use the offsets
231          * in it as the offsets of the frames.
232          */
233         frame_table_length = pletohl(&hdr.frametablelength);
234         wth->capture.netmon->frame_table_size = frame_table_length / sizeof (guint32);
235         if ((wth->capture.netmon->frame_table_size * sizeof (guint32)) != frame_table_length) {
236                 g_message("netmon: frame table length is %u, which is not a multiple of the size of an entry",
237                     frame_table_length);
238                 *err = WTAP_ERR_UNSUPPORTED;
239                 return -1;
240         }
241         if (wth->capture.netmon->frame_table_size == 0) {
242                 g_message("netmon: frame table length is %u, which means it's less than one entry in size",
243                     frame_table_length);
244                 *err = WTAP_ERR_UNSUPPORTED;
245                 return -1;
246         }
247         wth->capture.netmon->frame_table = g_malloc(frame_table_length);
248         errno = WTAP_ERR_CANT_READ;
249         file_seek(wth->fh, frame_table_offset, SEEK_SET);
250         bytes_read = file_read(wth->capture.netmon->frame_table, 1,
251             frame_table_length, wth->fh);
252         if (bytes_read != frame_table_length) {
253                 *err = file_error(wth->fh);
254                 if (*err != 0)
255                         return -1;
256                 return 0;
257         }
258
259         /* Set up to start reading at the first frame. */
260         wth->capture.netmon->current_frame = 0;
261
262         return 1;
263 }
264
265 /* Read the next packet */
266 static int netmon_read(wtap *wth, int *err)
267 {
268         netmon_t *netmon = wth->capture.netmon;
269         guint32 packet_size = 0;
270         int     bytes_read;
271         union {
272                 struct netmonrec_1_x_hdr hdr_1_x;
273                 struct netmonrec_2_x_hdr hdr_2_x;
274         }       hdr;
275         int     hdr_size = 0;
276         int     data_offset;
277         time_t  secs;
278         guint32 usecs;
279         double  t;
280
281         /* Have we reached the end of the packet data? */
282         if (netmon->current_frame >= netmon->frame_table_size) {
283                 /* Yes. */
284                 return 0;
285         }
286
287         /* Seek to the beginning of the current record, if we're
288            not there already (seeking to the current position
289            may still cause a seek and a read of the underlying file,
290            so we don't want to do it unconditionally). */
291         data_offset = netmon->frame_table[netmon->current_frame];
292         if (wth->data_offset != data_offset) {
293                 wth->data_offset = data_offset;
294                 file_seek(wth->fh, wth->data_offset, SEEK_SET);
295         }
296         netmon->current_frame++;
297
298         /* Read record header. */
299         switch (netmon->version_major) {
300
301         case 1:
302                 hdr_size = sizeof (struct netmonrec_1_x_hdr);
303                 break;
304
305         case 2:
306                 hdr_size = sizeof (struct netmonrec_2_x_hdr);
307                 break;
308         }
309         errno = WTAP_ERR_CANT_READ;
310
311         bytes_read = file_read(&hdr, 1, hdr_size, wth->fh);
312         if (bytes_read != hdr_size) {
313                 *err = file_error(wth->fh);
314                 if (*err != 0)
315                         return -1;
316                 if (bytes_read != 0) {
317                         *err = WTAP_ERR_SHORT_READ;
318                         return -1;
319                 }
320                 return 0;
321         }
322         wth->data_offset += hdr_size;
323
324         switch (netmon->version_major) {
325
326         case 1:
327                 packet_size = pletohs(&hdr.hdr_1_x.incl_len);
328                 break;
329
330         case 2:
331                 packet_size = pletohl(&hdr.hdr_2_x.incl_len);
332                 break;
333         }
334         if (packet_size > WTAP_MAX_PACKET_SIZE) {
335                 /*
336                  * Probably a corrupt capture file; don't blow up trying
337                  * to allocate space for an immensely-large packet.
338                  */
339                 g_message("netmon: File has %u-byte packet, bigger than maximum of %u",
340                     packet_size, WTAP_MAX_PACKET_SIZE);
341                 *err = WTAP_ERR_BAD_RECORD;
342                 return -1;
343         }
344         buffer_assure_space(wth->frame_buffer, packet_size);
345         data_offset = wth->data_offset;
346         errno = WTAP_ERR_CANT_READ;
347         bytes_read = file_read(buffer_start_ptr(wth->frame_buffer), 1,
348                         packet_size, wth->fh);
349
350         if (bytes_read != packet_size) {
351                 *err = file_error(wth->fh);
352                 if (*err == 0)
353                         *err = WTAP_ERR_SHORT_READ;
354                 return -1;
355         }
356         wth->data_offset += packet_size;
357
358         t = (double)netmon->start_usecs;
359         switch (netmon->version_major) {
360
361         case 1:
362                 t += ((double)pletohl(&hdr.hdr_1_x.ts_delta))*1000;
363                 break;
364
365         case 2:
366                 t += (double)pletohl(&hdr.hdr_2_x.ts_delta_lo)
367                     + (double)pletohl(&hdr.hdr_2_x.ts_delta_hi)*4294967296.0;
368                 break;
369         }
370         secs = (time_t)(t/1000000);
371         usecs = (guint32)(t - secs*1000000);
372         wth->phdr.ts.tv_sec = netmon->start_secs + secs;
373         wth->phdr.ts.tv_usec = usecs;
374         wth->phdr.caplen = packet_size;
375         switch (netmon->version_major) {
376
377         case 1:
378                 wth->phdr.len = pletohs(&hdr.hdr_1_x.orig_len);
379                 break;
380
381         case 2:
382                 wth->phdr.len = pletohl(&hdr.hdr_2_x.orig_len);
383                 break;
384         }
385         wth->phdr.pkt_encap = wth->file_encap;
386
387         return data_offset;
388 }
389
390 static void
391 netmon_close(wtap *wth)
392 {
393         g_free(wth->capture.netmon->frame_table);
394         g_free(wth->capture.netmon);
395 }
396
397 static const int wtap_encap[] = {
398         -1,             /* WTAP_ENCAP_UNKNOWN -> unsupported */
399         1,              /* WTAP_ENCAP_ETHERNET -> NDIS Ethernet */
400         2,              /* WTAP_ENCAP_TR -> NDIS Token Ring */
401         -1,             /* WTAP_ENCAP_SLIP -> unsupported */
402         -1,             /* WTAP_ENCAP_PPP -> unsupported */
403         3,              /* WTAP_ENCAP_FDDI -> NDIS FDDI */
404         3,              /* WTAP_ENCAP_FDDI_BITSWAPPED -> NDIS FDDI */
405         -1,             /* WTAP_ENCAP_RAW_IP -> unsupported */
406         -1,             /* WTAP_ENCAP_ARCNET -> unsupported */
407         -1,             /* WTAP_ENCAP_ATM_RFC1483 -> unsupported */
408         -1,             /* WTAP_ENCAP_LINUX_ATM_CLIP -> unsupported */
409         -1,             /* WTAP_ENCAP_LAPB -> unsupported*/
410         -1,             /* WTAP_ENCAP_ATM_SNIFFER -> unsupported */
411         -1              /* WTAP_ENCAP_NULL -> unsupported */
412 };
413 #define NUM_WTAP_ENCAPS (sizeof wtap_encap / sizeof wtap_encap[0])
414
415 /* Returns 0 if we could write the specified encapsulation type,
416    an error indication otherwise. */
417 int netmon_dump_can_write_encap(int filetype, int encap)
418 {
419         /* Per-packet encapsulations aren't supported. */
420         if (encap == WTAP_ENCAP_PER_PACKET)
421                 return WTAP_ERR_ENCAP_PER_PACKET_UNSUPPORTED;
422
423         if (encap < 0 || encap >= NUM_WTAP_ENCAPS || wtap_encap[encap] == -1)
424                 return WTAP_ERR_UNSUPPORTED_ENCAP;
425
426         return 0;
427 }
428
429 /* Returns TRUE on success, FALSE on failure; sets "*err" to an error code on
430    failure */
431 gboolean netmon_dump_open(wtap_dumper *wdh, int *err)
432 {
433         /* This is a netmon file */
434         wdh->subtype_write = netmon_dump;
435         wdh->subtype_close = netmon_dump_close;
436
437         /* We can't fill in all the fields in the file header, as we
438            haven't yet written any packets.  As we'll have to rewrite
439            the header when we've written out all the packets, we just
440            skip over the header for now. */
441         fseek(wdh->fh, CAPTUREFILE_HEADER_SIZE, SEEK_SET);
442
443         wdh->private.netmon = g_malloc(sizeof(netmon_dump_t));
444         wdh->private.netmon->frame_table_offset = CAPTUREFILE_HEADER_SIZE;
445         wdh->private.netmon->got_first_record_time = FALSE;
446         wdh->private.netmon->frame_table = NULL;
447         wdh->private.netmon->frame_table_index = 0;
448         wdh->private.netmon->frame_table_size = 0;
449
450         return TRUE;
451 }
452
453 /* Write a record for a packet to a dump file.
454    Returns TRUE on success, FALSE on failure. */
455 static gboolean netmon_dump(wtap_dumper *wdh, const struct wtap_pkthdr *phdr,
456     const u_char *pd, int *err)
457 {
458         netmon_dump_t *priv = wdh->private.netmon;
459         struct netmonrec_1_x_hdr rec_1_x_hdr;
460         struct netmonrec_2_x_hdr rec_2_x_hdr;
461         char *hdrp;
462         int hdr_size;
463         int nwritten;
464
465         /* NetMon files have a capture start time in the file header,
466            and have times relative to that in the packet headers;
467            pick the time of the first packet as the capture start
468            time. */
469         if (!priv->got_first_record_time) {
470                 priv->first_record_time = phdr->ts;
471                 priv->got_first_record_time = TRUE;
472         }
473         
474         switch (wdh->file_type) {
475
476         case WTAP_FILE_NETMON_1_x:
477                 rec_1_x_hdr.ts_delta = htolel(
478                     (phdr->ts.tv_sec - priv->first_record_time.tv_sec)*1000
479                   + (phdr->ts.tv_usec - priv->first_record_time.tv_usec + 500)/1000);
480                 rec_1_x_hdr.orig_len = htoles(phdr->len);
481                 rec_1_x_hdr.incl_len = htoles(phdr->caplen);
482                 hdrp = (char *)&rec_1_x_hdr;
483                 hdr_size = sizeof rec_1_x_hdr;
484                 break;
485
486         case WTAP_FILE_NETMON_2_x:
487                 /* XXX - fill in 64-bit time diff in microseconds */
488                 rec_2_x_hdr.orig_len = htolel(phdr->len);
489                 rec_2_x_hdr.incl_len = htolel(phdr->caplen);
490                 hdrp = (char *)&rec_2_x_hdr;
491                 hdr_size = sizeof rec_2_x_hdr;
492                 break;
493
494         default:
495                 /* We should never get here - our open routine
496                    should only get called for the types above. */
497                 *err = WTAP_ERR_UNSUPPORTED_FILE_TYPE;
498                 return FALSE;
499         }
500
501         nwritten = fwrite(hdrp, 1, hdr_size, wdh->fh);
502         if (nwritten != hdr_size) {
503                 if (nwritten < 0)
504                         *err = errno;
505                 else
506                         *err = WTAP_ERR_SHORT_WRITE;
507                 return FALSE;
508         }
509         nwritten = fwrite(pd, 1, phdr->caplen, wdh->fh);
510         if (nwritten != phdr->caplen) {
511                 if (nwritten < 0)
512                         *err = errno;
513                 else
514                         *err = WTAP_ERR_SHORT_WRITE;
515                 return FALSE;
516         }
517
518         /*
519          * Stash the file offset of this frame.
520          */
521         if (priv->frame_table_size == 0) {
522                 /*
523                  * Haven't yet allocated the buffer for the frame table.
524                  */
525                 priv->frame_table = g_malloc(1024 * sizeof *priv->frame_table);
526                 priv->frame_table_size = 1024;
527         } else {
528                 /*
529                  * We've allocated it; are we at the end?
530                  */
531                 if (priv->frame_table_index >= priv->frame_table_size) {
532                         /*
533                          * Yes - double the size of the frame table.
534                          */
535                         priv->frame_table_size *= 2;
536                         priv->frame_table = g_realloc(priv->frame_table,
537                             priv->frame_table_size * sizeof *priv->frame_table);
538                 }
539         }
540         priv->frame_table[priv->frame_table_index] =
541             htolel(priv->frame_table_offset);
542         priv->frame_table_index++;
543         priv->frame_table_offset += hdr_size + phdr->caplen;
544
545         return TRUE;
546 }
547
548 /* Finish writing to a dump file.
549    Returns TRUE on success, FALSE on failure. */
550 static gboolean netmon_dump_close(wtap_dumper *wdh, int *err)
551 {
552         netmon_dump_t *priv = wdh->private.netmon;
553         int n_to_write;
554         int nwritten;
555         struct netmon_hdr file_hdr;
556         const char *magicp;
557         int magic_size;
558         struct tm *tm;
559
560         /* Write out the frame table.  "priv->frame_table_index" is
561            the number of entries we've put into it. */
562         n_to_write = priv->frame_table_index * sizeof *priv->frame_table;
563         nwritten = fwrite(priv->frame_table, 1, n_to_write, wdh->fh);
564         if (nwritten != n_to_write) {
565                 if (nwritten < 0)
566                         *err = errno;
567                 else
568                         *err = WTAP_ERR_SHORT_WRITE;
569                 return FALSE;
570         }
571
572         /* Now go fix up the file header. */
573         fseek(wdh->fh, 0, SEEK_SET);
574         memset(&file_hdr, '\0', sizeof file_hdr);
575         switch (wdh->file_type) {
576
577         case WTAP_FILE_NETMON_1_x:
578                 magicp = netmon_1_x_magic;
579                 magic_size = sizeof netmon_1_x_magic;
580                 /* current NetMon version, for 1.x, is 1.1 */
581                 file_hdr.ver_minor = 1;
582                 file_hdr.ver_major = 1;
583                 break;
584
585         case WTAP_FILE_NETMON_2_x:
586                 magicp = netmon_2_x_magic;
587                 magic_size = sizeof netmon_2_x_magic;
588                 /* XXX - fill in V2 stuff. */
589                 break;
590
591         default:
592                 /* We should never get here - our open routine
593                    should only get called for the types above. */
594                 *err = WTAP_ERR_UNSUPPORTED_FILE_TYPE;
595                 return FALSE;
596         }
597         nwritten = fwrite(magicp, 1, magic_size, wdh->fh);
598         if (nwritten != magic_size) {
599                 if (nwritten < 0)
600                         *err = errno;
601                 else
602                         *err = WTAP_ERR_SHORT_WRITE;
603                 return FALSE;
604         }
605
606         file_hdr.network = htoles(wtap_encap[wdh->encap]);
607         tm = localtime(&priv->first_record_time.tv_sec);
608         file_hdr.ts_year = htoles(1900 + tm->tm_year);
609         file_hdr.ts_month = htoles(tm->tm_mon + 1);
610         file_hdr.ts_dow = htoles(tm->tm_wday);
611         file_hdr.ts_day = htoles(tm->tm_mday);
612         file_hdr.ts_hour = htoles(tm->tm_hour);
613         file_hdr.ts_min = htoles(tm->tm_min);
614         file_hdr.ts_sec = htoles(tm->tm_sec);
615         file_hdr.ts_msec = htoles(priv->first_record_time.tv_usec/1000);
616                 /* XXX - what about rounding? */
617         file_hdr.frametableoffset = htolel(priv->frame_table_offset);
618         file_hdr.frametablelength =
619             htolel(priv->frame_table_index * sizeof *priv->frame_table);
620         nwritten = fwrite(&file_hdr, 1, sizeof file_hdr, wdh->fh);
621         if (nwritten != sizeof file_hdr) {
622                 if (nwritten < 0)
623                         *err = errno;
624                 else
625                         *err = WTAP_ERR_SHORT_WRITE;
626                 return FALSE;
627         }
628
629         return TRUE;
630 }