Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

gdp / apps / gdp-reader.c @ master

History | View | Annotate | Download (16.7 KB)

1
/* vim: set ai sw=4 sts=4 ts=4 : */
2

    
3
/*
4
**  GDP-READER --- read and print records from a GDP log
5
**
6
**                Unfortunately it isn't that simple, since it is possible to read
7
**                using all the internal mechanisms.  The -c, -m, and -s flags
8
**                control which approach is being used.
9
**
10
**                There are two ways of reading.  The first is to get individual
11
**                records in a loop (as implemented in do_simpleread), and the
12
**                second is to request a batch of records (as implemented in
13
**                do_async_read or do_subscribe); these are returned as events
14
**                that are collected after the initial command completes or as
15
**                callbacks that are invoked in a separate thread.  There are two
16
**                interfaces for the event/callback techniques; one only reads
17
**                existing data, and the other ("subscriptions") will wait for
18
**                data to be appended by another client.
19
**
20
**        ----- BEGIN LICENSE BLOCK -----
21
**        Applications for the Global Data Plane
22
**        From the Ubiquitous Swarm Lab, 490 Cory Hall, U.C. Berkeley.
23
**
24
**        Copyright (c) 2015-2019, Regents of the University of California.
25
**        All rights reserved.
26
**
27
**        Permission is hereby granted, without written agreement and without
28
**        license or royalty fees, to use, copy, modify, and distribute this
29
**        software and its documentation for any purpose, provided that the above
30
**        copyright notice and the following two paragraphs appear in all copies
31
**        of this software.
32
**
33
**        IN NO EVENT SHALL REGENTS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
34
**        SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST
35
**        PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION,
36
**        EVEN IF REGENTS HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37
**
38
**        REGENTS SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
39
**        LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
40
**        FOR A PARTICULAR PURPOSE. THE SOFTWARE AND ACCOMPANYING DOCUMENTATION,
41
**        IF ANY, PROVIDED HEREUNDER IS PROVIDED "AS IS". REGENTS HAS NO
42
**        OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS,
43
**        OR MODIFICATIONS.
44
**        ----- END LICENSE BLOCK -----
45
*/
46

    
47
#include <ep/ep.h>
48
#include <ep/ep_dbg.h>
49
#include <ep/ep_app.h>
50
#include <ep/ep_time.h>
51
#include <gdp/gdp.h>
52
#include <event2/buffer.h>
53

    
54
#include <unistd.h>
55
#include <errno.h>
56
#include <errno.h>
57
#include <getopt.h>
58
#include <string.h>
59
#include <sysexits.h>
60

    
61
static EP_DBG        Dbg = EP_DBG_INIT("gdp-reader", "GDP Reader Application");
62

    
63
#ifndef USE_GETDATE
64
# define USE_GETDATE                1
65
#endif
66

    
67

    
68
/*
69
**  DO_LOG --- log a timestamp (for performance checking).
70
*/
71

    
72
FILE        *LogFile;
73
bool        TextData = false;                // set if data should be displayed as text
74
bool        BinaryData = false;                // set if data should be output as binary
75
bool        PrintSig = false;                // set if signature should be printed
76
bool        Quiet = false;                        // don't print metadata
77
int                NRead = 0;                                // number of datums read
78
FILE        *OutFile;                                // data output file
79

    
80
void
81
do_log(const char *tag)
82
{
83
        struct timeval tv;
84

    
85
        if (LogFile == NULL)
86
                return;
87
        gettimeofday(&tv, NULL);
88
        fprintf(LogFile, "%s %ld.%06ld\n", tag, tv.tv_sec, (long) tv.tv_usec);
89
}
90

    
91
#define LOG(tag)        { if (LogFile != NULL) do_log(tag); }
92

    
93
/*
94
**  PRINTDATUM --- just print out a datum
95
*/
96

    
97
void
98
printdatum(gdp_datum_t *datum, FILE *fp)
99
{
100
        uint32_t prflags = 0;
101

    
102
        // logging for simple performance testing
103
        LOG("R");
104

    
105
        if (TextData)
106
                prflags |= GDP_DATUM_PRTEXT;
107
        if (BinaryData)
108
                prflags |= GDP_DATUM_PRBINARY;
109
        if (PrintSig)
110
                prflags |= GDP_DATUM_PRSIG;
111
        if (Quiet)
112
                prflags |= GDP_DATUM_PRQUIET;
113
        flockfile(fp);
114
        if (!Quiet)
115
                fprintf(fp, " >>> ");
116
        gdp_datum_print(datum, fp, prflags);
117
        funlockfile(fp);
118
        NRead++;
119
}
120

    
121

    
122
/*
123
**  DO_SIMPLEREAD --- read from a log using the one-record-at-a-time call
124
*/
125

    
126
EP_STAT
127
do_simpleread(gdp_gin_t *gin,
128
                gdp_recno_t firstrec,
129
                const char *dtstr,
130
                int numrecs)
131
{
132
        EP_STAT estat = EP_STAT_OK;
133
        gdp_datum_t *datum = gdp_datum_new();
134

    
135
        // change the "infinity" sentinel to make the loop easier
136
        if (numrecs == 0)
137
                numrecs = -1;
138

    
139
        // can't start reading before first record (but negative makes sense)
140
        if (firstrec == 0)
141
                firstrec = 1;
142

    
143
        // are we reading by record number or by timestamp?
144
        if (dtstr == NULL)
145
        {
146
                // record number
147
                estat = gdp_gin_read_by_recno(gin, firstrec, datum);
148
        }
149
        else
150
        {
151
                // timestamp
152
                EP_TIME_SPEC ts;
153

    
154
                estat = ep_time_parse(dtstr, &ts, EP_TIME_USE_LOCALTIME);
155
                if (!EP_STAT_ISOK(estat))
156
                {
157
                        ep_app_message(estat,
158
                                                "Cannot convert date/time string \"%s\"",
159
                                                dtstr);
160
                        goto done;
161
                }
162

    
163
                estat = gdp_gin_read_by_ts(gin, &ts, datum);
164
        }
165

    
166
        // start reading data, one record at a time
167
        while (EP_STAT_ISOK(estat) && (numrecs < 0 || --numrecs > 0))
168
        {
169
                gdp_recno_t recno;
170

    
171
                // print the previous value
172
                printdatum(datum, OutFile);
173

    
174
                // flush any left over data
175
                if (gdp_buf_reset(gdp_datum_getbuf(datum)) < 0)
176
                {
177
                        char nbuf[40];
178

    
179
                        (void) (0 == strerror_r(errno, nbuf, sizeof nbuf));
180
                        ep_app_warn("buffer reset failed: %s", nbuf);
181
                }
182

    
183
                // move to next record
184
                recno = gdp_datum_getrecno(datum) + 1;
185
                estat = gdp_gin_read_by_recno(gin, recno, datum);
186
        }
187

    
188
        // print the final value
189
        if (EP_STAT_ISOK(estat))
190
                printdatum(datum, OutFile);
191

    
192
        // end of data is returned as a "not found" error: turn it into a warning
193
        //    to avoid scaring the unsuspecting user
194
        if (EP_STAT_IS_SAME(estat, GDP_STAT_NAK_NOTFOUND))
195
                estat = EP_STAT_END_OF_FILE;
196

    
197
done:
198
        gdp_datum_free(datum);
199
        return estat;
200
}
201

    
202

    
203
/*
204
**  Async Read and Subscriptions.
205
*/
206

    
207
EP_STAT
208
print_event(gdp_event_t *gev)
209
{
210
        EP_STAT estat = gdp_event_getstat(gev);
211
        const char *op = gdp_event_getudata(gev);
212

    
213
        if (op == NULL)
214
                op = "Unknown";
215

    
216
        // decode it
217
        switch (gdp_event_gettype(gev))
218
        {
219
          case GDP_EVENT_DATA:
220
                // this event contains a data return
221
                LOG("S");
222
                printdatum(gdp_event_getdatum(gev), OutFile);
223
                break;
224

    
225
          case GDP_EVENT_DONE:
226
                // "end of subscription": no more data will be returned
227
                if (!Quiet)
228
                {
229
                        ep_app_info("End of %s", op);
230
                }
231
                estat = EP_STAT_END_OF_FILE;
232
                break;
233

    
234
          case GDP_EVENT_SHUTDOWN:
235
                // log daemon has shut down, meaning we lose our subscription
236
                estat = GDP_STAT_DEAD_DAEMON;
237
                ep_app_message(estat, "%s terminating because of log daemon shutdown", op);
238
                break;
239

    
240
          case GDP_EVENT_CREATED:
241
                ep_app_info("Successful append, create, or similar");
242
                break;
243

    
244
          default:
245
                // let the library handle this
246
                gdp_event_print(gev, stderr);
247
                break;
248
        }
249

    
250
        if (EP_STAT_ISFAIL(estat))                        // ERROR or higher severity
251
        {
252
                char ebuf[100];
253
                fprintf(stderr, "    STATUS: %s\n",
254
                                ep_stat_tostr(estat, ebuf, sizeof ebuf));
255
        }
256
        return estat;
257
}
258

    
259

    
260
void
261
read_cb(gdp_event_t *gev)
262
{
263
        (void) print_event(gev);
264
        gdp_event_free(gev);
265
}
266

    
267

    
268
/*
269
**  DO_SUBSCRIBE --- subscribe to a log, possibly using callbacks
270
**
271
**                This routine handles calls that return multiple values via the
272
**                event interface.  They might include subscriptions.
273
*/
274

    
275
EP_STAT
276
do_subscribe(gdp_gin_t *gin,
277
                gdp_recno_t firstrec,
278
                const char *dtstr,
279
                int32_t numrecs,
280
                bool use_callbacks)
281
{
282
        EP_STAT estat;
283
        void (*cbfunc)(gdp_event_t *) = NULL;
284
        EP_TIME_SPEC ts;
285

    
286
        if (use_callbacks)
287
                cbfunc = read_cb;
288

    
289
        // are we reading by record number or by timestamp?
290
        if (dtstr != NULL)
291
        {
292
                // timestamp
293
                estat = ep_time_parse(dtstr, &ts, EP_TIME_USE_LOCALTIME);
294
                if (!EP_STAT_ISOK(estat))
295
                {
296
                        ep_app_message(estat, "Cannot convert date/time string \"%s\"",
297
                                                dtstr);
298
                        return estat;
299
                }
300
        }
301

    
302
        // start up a subscription
303
        if (dtstr == NULL)
304
                estat = gdp_gin_subscribe_by_recno(gin, firstrec, numrecs,
305
                                                                NULL, cbfunc, "Subscription");
306
        else
307
                estat = gdp_gin_subscribe_by_ts(gin, &ts, numrecs,
308
                                                                NULL, cbfunc, "Subscription");
309

    
310
        // check to make sure the subscribe succeeded; if not, bail
311
        if (!EP_STAT_ISOK(estat))
312
        {
313
                char ebuf[200];
314

    
315
                ep_app_fatal("Cannot subscribe: %s",
316
                                ep_stat_tostr(estat, ebuf, sizeof ebuf));
317
        }
318

    
319
        // this sleep will allow multiple results to appear before we start reading
320
        if (ep_dbg_test(Dbg, 100))
321
                ep_time_nanosleep(500000000);        //DEBUG: one half second
322

    
323
        // now start reading the events that will be generated
324
        if (!use_callbacks)
325
        {
326
                int32_t ndone = 0;
327
                for (;;)
328
                {
329
                        // for testing: force early termination and close
330
                        if (ep_dbg_test(Dbg, 127) && ++ndone >= numrecs)
331
                                break;
332

    
333
                        // get the next incoming event
334
                        gdp_event_t *gev = gdp_event_next(NULL, 0);
335

    
336
                        // print it
337
                        estat = print_event(gev);
338

    
339
                        // don't forget to free the event!
340
                        gdp_event_free(gev);
341

    
342
                        EP_STAT_CHECK(estat, break);
343
                }
344
        }
345
        else
346
        {
347
                // hang for an hour waiting for events
348
                sleep(3600);
349
        }
350

    
351
        return estat;
352
}
353

    
354

    
355
/*
356
**  DO_ASYNC_READ --- read asynchronously
357
*/
358

    
359
EP_STAT
360
do_async_read(gdp_gin_t *gin,
361
                gdp_recno_t firstrec,
362
                int32_t numrecs,
363
                bool use_callbacks)
364
{
365
        EP_STAT estat = EP_STAT_OK;
366
        gdp_event_cbfunc_t cbfunc = NULL;
367

    
368
        if (use_callbacks)
369
                cbfunc = read_cb;
370

    
371
        // make the flags more user-friendly
372
        if (firstrec == 0)
373
                firstrec = 1;
374
        if (numrecs <= 0)
375
                numrecs = gdp_gin_getnrecs(gin);
376
        if (firstrec < 0)
377
        {
378
                firstrec += numrecs + 1;
379
                numrecs -= firstrec - 1;
380
        }
381

    
382
        // issue the async read command without reading results yet
383
        estat = gdp_gin_read_by_recno_async(gin, firstrec, numrecs,
384
                                                        cbfunc, "Async Read");
385
        if (!EP_STAT_ISOK(estat))
386
        {
387
                char ebuf[100];
388
                ep_app_error("async_read: gdp_gin_read_by_recno_async error:\n\t%s",
389
                                ep_stat_tostr(estat, ebuf, sizeof ebuf));
390
        }
391

    
392
        // this sleep will allow multiple results to appear before we start reading
393
        if (ep_dbg_test(Dbg, 100))
394
                ep_time_nanosleep(500000000);        //DEBUG: one half second
395

    
396
        // now start reading the events that will be generated
397
        if (!use_callbacks)
398
        {
399
                do
400
                {
401
                        // get the next incoming event
402
                        gdp_event_t *gev = gdp_event_next(NULL, 0);
403

    
404
                        // print it
405
                        estat = print_event(gev);
406

    
407
                        // don't forget to free the event!
408
                        gdp_event_free(gev);
409

    
410
                        //EP_STAT_CHECK(estat, break);
411
                } while (EP_STAT_ISOK(estat));
412
        }
413
        else
414
        {
415
                // hang for 60 seconds waiting for events
416
                sleep(60);
417
        }
418

    
419
        return estat;
420
}
421

    
422

    
423
/*
424
**  PRINT_METADATA --- get and print the metadata
425
*/
426

    
427
void
428
print_metadata(gdp_gin_t *gin)
429
{
430
        EP_STAT estat;
431
        gdp_md_t *gmd;
432
        gdp_recno_t nrecs;
433

    
434
        nrecs = gdp_gin_getnrecs(gin);
435
        printf("Number of records: %" PRIgdp_recno "\n", nrecs);
436
        estat = gdp_gin_getmetadata(gin, &gmd);
437
        EP_STAT_CHECK(estat, goto fail0);
438

    
439
        gdp_md_dump(gmd, stdout, 5, 0);
440
        gdp_md_free(gmd);
441
        return;
442

    
443
fail0:
444
        ep_app_message(estat, "Could not read metadata!");
445
}
446

    
447
void
448
usage(void)
449
{
450
        fprintf(stderr,
451
                        "Usage: %s [-a] [-b] [-c] [-d datetime] [-D dbgspec] [-f firstrec]\n"
452
                        "  [-G router_addr] [-L logfile] [-M] [-n nrecs] [-o outfile]\n"
453
                        "  [-s] [-t] [-v] [-V] log_name\n"
454
                        "    -a  read asynchronously\n"
455
                        "    -b  output data in binary\n"
456
                        "    -c  use callbacks\n"
457
                        "    -d  first date/time to read from\n"
458
                        "    -D  turn on debugging flags\n"
459
                        "    -f  first record number to read (from 1)\n"
460
                        "    -G  IP host to contact for gdp_router\n"
461
                        "    -L  set logging file name (for debugging)\n"
462
                        "    -M  show log metadata\n"
463
                        "    -n  set number of records to read (default all)\n"
464
                        "    -o  set output file name\n"
465
                        "    -q  be quiet (don't print any metadata)\n"
466
                        "    -s  subscribe to this log\n"
467
                        "    -t  print data as text (instead of hexdump)\n"
468
                        "    -v  print verbose output (include signature)\n"
469
                        "    -V  verify proof on returned data\n",
470
                        ep_app_getprogname());
471
        exit(EX_USAGE);
472
}
473

    
474
/*
475
**  MAIN --- the name says it all
476
*/
477

    
478
int
479
main(int argc, char **argv)
480
{
481
        gdp_gin_t *gin = NULL;
482
        EP_STAT estat;
483
        gdp_name_t gobname;
484
        int opt;
485
        char *gdpd_addr = NULL;
486
        bool subscribe = false;
487
        bool async = false;
488
        bool use_callbacks = false;
489
        bool showmetadata = false;
490
        bool vrfy_proof = false;
491
        int32_t numrecs = 0;
492
        gdp_recno_t firstrec = 0;
493
        bool show_usage = false;
494
        char *log_file_name = NULL;
495
        gdp_iomode_t open_mode = GDP_MODE_RO;
496
        const char *dtstr = NULL;
497

    
498
        ep_lib_init(EP_LIB_USEPTHREADS);        // initialize app errors, etc.
499

    
500
        // parse command-line options
501
        while ((opt = getopt(argc, argv, "abAcd:D:f:G:L:mMn:o:qstvV")) > 0)
502
        {
503
                errno = 0;                                                // avoid misleading messages
504
                switch (opt)
505
                {
506
                  case 'A':                                // hidden flag for debugging only
507
                        open_mode = GDP_MODE_RA;
508
                        break;
509

    
510
                  case 'a':
511
                        // do asynchronous read
512
                        async = true;
513
                        break;
514

    
515
                  case 'b':
516
                        BinaryData = true;
517
                        break;
518

    
519
                  case 'c':
520
                        // use callbacks
521
                        use_callbacks = true;
522
                        break;
523

    
524
                  case 'd':
525
                        dtstr = optarg;
526
                        break;
527

    
528
                  case 'D':
529
                        // turn on debugging
530
                        ep_dbg_set(optarg);
531
                        break;
532

    
533
                  case 'f':
534
                        // select the first record
535
                        firstrec = atol(optarg);
536
                        break;
537

    
538
                  case 'G':
539
                        // set the port for connecting to the GDP daemon
540
                        gdpd_addr = optarg;
541
                        break;
542

    
543
                  case 'L':
544
                        log_file_name = optarg;
545
                        break;
546

    
547
                  case 'm':
548
                        // multiread: obsolete
549
                        ep_app_warn("Multiread (-m) is deprecated; using Async (-a)");
550
                        async = true;
551
                        break;
552

    
553
                  case 'M':
554
                        showmetadata = true;
555
                        break;
556

    
557
                  case 'n':
558
                        // select the number of records to be returned
559
                        numrecs = atol(optarg);
560
                        break;
561

    
562
                  case 'o':
563
                        OutFile = fopen(optarg, "w");
564
                        if (OutFile == NULL)
565
                        {
566
                                ep_app_error("Cannot open output file %s", optarg);
567
                                exit(EX_CANTCREAT);
568
                        }
569
                        break;
570

    
571
                  case 'q':
572
                        // be quiet (don't print metadata)
573
                        Quiet = true;
574
                        break;
575

    
576
                  case 's':
577
                        // subscribe to this log
578
                        subscribe = true;
579
                        break;
580

    
581
                  case 't':
582
                        // print data as text
583
                        TextData = true;
584
                        break;
585

    
586
                  case 'v':
587
                        PrintSig = true;
588

    
589
                  case 'V':
590
                        vrfy_proof = true;
591
                        break;
592

    
593
                  default:
594
                        show_usage = true;
595
                        break;
596
                }
597
        }
598
        argc -= optind;
599
        argv += optind;
600

    
601
        if (firstrec != 0 && dtstr != NULL)
602
        {
603
                ep_app_error("Cannot specify -f and -d");
604
                exit(EX_USAGE);
605
        }
606

    
607
        int ntypes = 0;
608
        if (async)
609
                ntypes++;
610
        if (subscribe)
611
                ntypes++;
612
        if (ntypes > 1)
613
        {
614
                ep_app_error("Can only specify at most one of -a and -s");
615
                usage();
616
        }
617
        else if (ntypes < 1 && use_callbacks)
618
        {
619
                ep_app_warn("UseCallbacks (-c) flag does nothing without -a or -s");
620
        }
621

    
622
        ntypes = 0;
623
        if (TextData)
624
                ntypes++;
625
        if (BinaryData)
626
                ntypes++;
627
        if (ntypes > 1)
628
        {
629
                ep_app_error("Can only specify at most one of -b and -t");
630
                usage();
631
        }
632

    
633
        // we require a log name
634
        if (show_usage || argc <= 0)
635
                usage();
636

    
637
        if (log_file_name != NULL)
638
        {
639
                // open a log file (for timing measurements)
640
                LogFile = fopen(log_file_name, "a");
641
                if (LogFile == NULL)
642
                        ep_app_warn("Cannot open log file %s: %s",
643
                                        log_file_name, strerror(errno));
644
                else
645
                        setlinebuf(LogFile);
646
        }
647

    
648
        if (OutFile == NULL)
649
                OutFile = stdout;
650

    
651
        // initialize the GDP library
652
        estat = gdp_init(gdpd_addr);
653
        if (!EP_STAT_ISOK(estat))
654
        {
655
                ep_app_error("GDP Initialization failed");
656
                goto fail0;
657
        }
658

    
659
        // allow thread to settle to avoid interspersed debug output
660
        ep_time_nanosleep(INT64_C(100000000));                // 100 msec
661

    
662
        // parse the name (either base64-encoded or symbolic)
663
        estat = gdp_parse_name(argv[0], gobname);
664
        if (EP_STAT_ISFAIL(estat))
665
        {
666
                ep_app_message(estat, "illegal log name syntax:\n\t%s", argv[0]);
667
                exit(EX_USAGE);
668
        }
669

    
670
        // convert it to printable format and tell the user what we are doing
671
        if (!Quiet)
672
        {
673
                gdp_pname_t gobpname;
674

    
675
                gdp_printable_name(gobname, gobpname);
676
                fprintf(stderr, "Reading log %s\n", gobpname);
677
        }
678

    
679
        // set up any special open parameters
680
        gdp_open_info_t *open_info = gdp_open_info_new();
681
        if (vrfy_proof)
682
                gdp_open_info_set_vrfy(open_info, true);
683

    
684
        // open the log; arguably this shouldn't be necessary
685
        estat = gdp_gin_open(gobname, open_mode, open_info, &gin);
686
        gdp_open_info_free(open_info);
687
        if (!EP_STAT_ISOK(estat))
688
        {
689
                ep_app_message(estat, "Cannot open log %s", argv[0]);
690
                exit(EX_NOINPUT);
691
        }
692

    
693
        // if we are converting a date/time string, set the local timezone
694
        if (dtstr != NULL)
695
                tzset();
696

    
697
        if (showmetadata)
698
                print_metadata(gin);
699

    
700
        // arrange to do the reading via one of the helper routines
701
        if (async)
702
                estat = do_async_read(gin, firstrec, numrecs, use_callbacks);
703
        else if (subscribe)
704
                estat = do_subscribe(gin, firstrec, dtstr, numrecs, use_callbacks);
705
        else
706
                estat = do_simpleread(gin, firstrec, dtstr, numrecs);
707

    
708
fail0:
709
        ;                                // silly compiler grammar
710
        int exitstat;
711

    
712
        if (!EP_STAT_ISFAIL(estat))                        // WARN or OK
713
                exitstat = EX_OK;
714
        else if (EP_STAT_IS_SAME(estat, GDP_STAT_NAK_NOROUTE))
715
                exitstat = EX_NOINPUT;
716
        else if (EP_STAT_ISABORT(estat))
717
                exitstat = EX_SOFTWARE;
718
        else
719
                exitstat = EX_UNAVAILABLE;
720

    
721
        if (ep_dbg_test(Dbg, 9))
722
        {
723
                char ebuf[100];
724
                ep_dbg_printf("Cleaning up, exitstat %d, estat %s\n",
725
                                exitstat, ep_stat_tostr(estat, ebuf, sizeof ebuf));
726
        }
727

    
728
        // might as well let the GDP know we're going away
729
        if (gin != NULL)
730
        {
731
                EP_STAT close_stat = gdp_gin_close(gin);
732
                if (!EP_STAT_ISOK(close_stat))
733
                        ep_app_message(close_stat, "cannot close log");
734
        }
735

    
736
        // this sleep is to watch for any extraneous results coming back
737
        if (ep_dbg_test(Dbg, 126))
738
        {
739
                int sleep_time = 40;
740
                ep_dbg_printf("Sleeping for %d seconds\n", sleep_time);
741
                while (sleep_time-- > 0)
742
                        ep_time_nanosleep(INT64_C(1000000000));                // one second
743
        }
744

    
745
        // might as well let the user know what's going on....
746
        if (EP_STAT_ISFAIL(estat))
747
                ep_app_message(estat, "exiting after %d records", NRead);
748
        else if (!Quiet)
749
                fprintf(stderr, "Exiting after %d records\n", NRead);
750
        return exitstat;
751
}