noping: Color response times based on their percentile.
[liboping.git] / src / oping.c
1 /**
2  * Object oriented C module to send ICMP and ICMPv6 `echo's.
3  * Copyright (C) 2006-2014  Florian octo Forster <ff at octo.it>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; only version 2 of the License is
8  * applicable.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
18  */
19
20 #if HAVE_CONFIG_H
21 # include <config.h>
22 #endif
23
24 #if STDC_HEADERS
25 # include <stdlib.h>
26 # include <stdio.h>
27 # include <string.h>
28 # include <stdint.h>
29 # include <inttypes.h>
30 # include <errno.h>
31 # include <assert.h>
32 #else
33 # error "You don't have the standard C99 header files installed"
34 #endif /* STDC_HEADERS */
35
36 #if HAVE_UNISTD_H
37 # include <unistd.h>
38 #endif
39
40 #if HAVE_MATH_H
41 # include <math.h>
42 #endif
43
44 #if TIME_WITH_SYS_TIME
45 # include <sys/time.h>
46 # include <time.h>
47 #else
48 # if HAVE_SYS_TIME_H
49 #  include <sys/time.h>
50 # else
51 #  include <time.h>
52 # endif
53 #endif
54
55 #if HAVE_SYS_SOCKET_H
56 # include <sys/socket.h>
57 #endif
58 #if HAVE_NETINET_IN_H
59 # include <netinet/in.h>
60 #endif
61 #if HAVE_NETINET_IP_H
62 # include <netinet/ip.h>
63 #endif
64
65 #if HAVE_NETDB_H
66 # include <netdb.h> /* NI_MAXHOST */
67 #endif
68
69 #if HAVE_SIGNAL_H
70 # include <signal.h>
71 #endif
72
73 #if HAVE_SYS_TYPES_H
74 #include <sys/types.h>
75 #endif
76
77 #include <locale.h>
78 #include <langinfo.h>
79
80 #if USE_NCURSES
81 # define NCURSES_OPAQUE 1
82 /* http://newsgroups.derkeiler.com/Archive/Rec/rec.games.roguelike.development/2010-09/msg00050.html */
83 # define _X_OPEN_SOURCE_EXTENDED
84
85 # if HAVE_NCURSESW_NCURSES_H
86 #  include <ncursesw/ncurses.h>
87 # elif HAVE_NCURSES_H
88 #  include <ncurses.h>
89 # endif
90
91 # define OPING_GREEN 1
92 # define OPING_YELLOW 2
93 # define OPING_RED 3
94 # define OPING_GREEN_HIST 4
95 # define OPING_YELLOW_HIST 5
96 # define OPING_RED_HIST 6
97
98 static char const * const hist_symbols_utf8[] = {
99         "▁", "▂", "▃", "▄", "▅", "▆", "▇", "█" };
100 static size_t const hist_symbols_utf8_num = sizeof (hist_symbols_utf8)
101         / sizeof (hist_symbols_utf8[0]);
102
103 /* scancodes for 6 levels of horizontal bars, ncurses-specific */
104 /* those are not the usual constants because those are not constant */
105 static int const hist_symbols_acs[] = {
106         115, /* ACS_S9 "⎽" */
107         114, /* ACS_S7 "⎼" */
108         113, /* ACS_S5 "─" */
109         112, /* ACS_S3 "⎻" */
110         111  /* ACS_S1 "⎺" */
111 };
112 static size_t const hist_symbols_acs_num = sizeof (hist_symbols_acs)
113         / sizeof (hist_symbols_acs[0]);
114
115 /* use different colors without a background for scancodes */
116 static int const hist_colors_utf8[] = {
117         OPING_GREEN_HIST, OPING_YELLOW_HIST, OPING_RED_HIST };
118 static int const hist_colors_acs[] = {
119         OPING_GREEN, OPING_YELLOW, OPING_RED };
120 /* assuming that both arrays are the same size */
121 static size_t const hist_colors_num = sizeof (hist_colors_utf8)
122         / sizeof (hist_colors_utf8[0]);
123 #endif
124
125 /* "─" */
126 #define BOXPLOT_WHISKER_BAR       (113 | A_ALTCHARSET)
127 /* "├" */
128 #define BOXPLOT_WHISKER_LEFT_END  (116 | A_ALTCHARSET)
129 /* "┤" */
130 #define BOXPLOT_WHISKER_RIGHT_END (117 | A_ALTCHARSET)
131 /* Inverted */
132 #define BOXPLOT_BOX               ' '
133 /* "│", inverted */
134 #define BOXPLOT_MEDIAN            (120 | A_ALTCHARSET)
135
136 #include "oping.h"
137
138 #ifndef _POSIX_SAVED_IDS
139 # define _POSIX_SAVED_IDS 0
140 #endif
141
142 #ifndef IPTOS_MINCOST
143 # define IPTOS_MINCOST 0x02
144 #endif
145
146 /* Remove GNU specific __attribute__ settings when using another compiler */
147 #if !__GNUC__
148 # define __attribute__(x) /**/
149 #endif
150
151 typedef struct ping_context
152 {
153         char host[NI_MAXHOST];
154         char addr[NI_MAXHOST];
155
156         int index;
157         int req_sent;
158         int req_rcvd;
159
160         double latency_min;
161         double latency_max;
162         double latency_total;
163         double latency_total_square;
164
165 /* 1000 + one "infinity" bucket. */
166 #define OPING_HISTOGRAM_BUCKETS 1001
167         uint32_t *histogram_counters;
168         uint32_t *histogram_accumulated;
169         double *histogram_ratio;
170         size_t latency_histogram_size;
171
172 #if USE_NCURSES
173         WINDOW *window;
174 #endif
175 } ping_context_t;
176
177 static double  opt_interval   = 1.0;
178 static int     opt_addrfamily = PING_DEF_AF;
179 static char   *opt_srcaddr    = NULL;
180 static char   *opt_device     = NULL;
181 static char   *opt_filename   = NULL;
182 static int     opt_count      = -1;
183 static int     opt_send_ttl   = 64;
184 static uint8_t opt_send_qos   = 0;
185 #define OPING_DEFAULT_PERCENTILE 95.0
186 static double  opt_percentile = -1.0;
187 static double  opt_exit_status_threshold = 1.0;
188 #if USE_NCURSES
189 static int     opt_show_graph = 1;
190 static int     opt_utf8       = 0;
191 #endif
192
193 static int host_num = 0;
194
195 #if USE_NCURSES
196 static WINDOW *main_win = NULL;
197 #endif
198
199 static void sigint_handler (int signal) /* {{{ */
200 {
201         /* Make compiler happy */
202         signal = 0;
203         /* Exit the loop */
204         opt_count = 0;
205 } /* }}} void sigint_handler */
206
207 static ping_context_t *context_create (void) /* {{{ */
208 {
209         ping_context_t *ret;
210
211         if ((ret = malloc (sizeof (ping_context_t))) == NULL)
212                 return (NULL);
213
214         memset (ret, '\0', sizeof (ping_context_t));
215
216         ret->latency_min   = -1.0;
217         ret->latency_max   = -1.0;
218         ret->latency_total = 0.0;
219         ret->latency_total_square = 0.0;
220
221         ret->latency_histogram_size = (size_t) OPING_HISTOGRAM_BUCKETS;
222         ret->histogram_counters = calloc (ret->latency_histogram_size,
223                         sizeof (*ret->histogram_counters));
224         ret->histogram_accumulated = calloc (ret->latency_histogram_size,
225                         sizeof (*ret->histogram_accumulated));
226         ret->histogram_ratio = calloc (ret->latency_histogram_size,
227                         sizeof (*ret->histogram_ratio));
228
229 #if USE_NCURSES
230         ret->window = NULL;
231 #endif
232
233         return (ret);
234 } /* }}} ping_context_t *context_create */
235
236 static void context_destroy (ping_context_t *context) /* {{{ */
237 {
238         if (context == NULL)
239                 return;
240
241 #if USE_NCURSES
242         if (context->window != NULL)
243         {
244                 delwin (context->window);
245                 context->window = NULL;
246         }
247 #endif
248
249         free (context->histogram_counters);
250         context->histogram_counters = NULL;
251
252         free (context->histogram_accumulated);
253         context->histogram_accumulated = NULL;
254
255         free (context->histogram_ratio);
256         context->histogram_ratio = NULL;
257
258         free (context);
259 } /* }}} void context_destroy */
260
261 static double context_get_average (ping_context_t *ctx) /* {{{ */
262 {
263         double num_total;
264
265         if (ctx == NULL)
266                 return (-1.0);
267
268         if (ctx->req_rcvd < 1)
269                 return (-0.0);
270
271         num_total = (double) ctx->req_rcvd;
272         return (ctx->latency_total / num_total);
273 } /* }}} double context_get_average */
274
275 static double context_get_percentile (ping_context_t *ctx, /* {{{ */
276                 double percentile)
277 {
278         double threshold = percentile / 100.0;
279         double index_to_ms_factor;
280         size_t i;
281
282         if (ctx->histogram_ratio == NULL)
283                 return (NAN);
284
285         for (i = 0; i < ctx->latency_histogram_size; i++)
286                 if (ctx->histogram_ratio[i] >= threshold)
287                         break;
288
289         if (i >= ctx->latency_histogram_size)
290                 return (NAN);
291         else if (i == (ctx->latency_histogram_size - 1))
292                 return (INFINITY);
293
294         index_to_ms_factor = (1000.0 * opt_interval) / (ctx->latency_histogram_size - 1);
295
296         /* Multiply with i+1, because we're interested in the _upper_ bound of
297          * each bucket. */
298         return (index_to_ms_factor * ((double) (i + 1)));
299 } /* }}} double context_get_percentile */
300
301 static double context_get_stddev (ping_context_t *ctx) /* {{{ */
302 {
303         double num_total;
304
305         if (ctx == NULL)
306                 return (-1.0);
307
308         if (ctx->req_rcvd < 1)
309                 return (-0.0);
310         else if (ctx->req_rcvd < 2)
311                 return (0.0);
312
313         num_total = (double) ctx->req_rcvd;
314         return (sqrt (((num_total * ctx->latency_total_square)
315                                         - (ctx->latency_total * ctx->latency_total))
316                                 / (num_total * (num_total - 1.0))));
317 } /* }}} double context_get_stddev */
318
319 static double context_get_packet_loss (const ping_context_t *ctx) /* {{{ */
320 {
321         if (ctx == NULL)
322                 return (-1.0);
323
324         if (ctx->req_sent < 1)
325                 return (0.0);
326
327         return (100.0 * (ctx->req_sent - ctx->req_rcvd)
328                         / ((double) ctx->req_sent));
329 } /* }}} double context_get_packet_loss */
330
331 static int ping_initialize_contexts (pingobj_t *ping) /* {{{ */
332 {
333         pingobj_iter_t *iter;
334         int index;
335
336         if (ping == NULL)
337                 return (EINVAL);
338
339         index = 0;
340         for (iter = ping_iterator_get (ping);
341                         iter != NULL;
342                         iter = ping_iterator_next (iter))
343         {
344                 ping_context_t *context;
345                 size_t buffer_size;
346
347                 context = context_create ();
348                 context->index = index;
349
350                 buffer_size = sizeof (context->host);
351                 ping_iterator_get_info (iter, PING_INFO_HOSTNAME, context->host, &buffer_size);
352
353                 buffer_size = sizeof (context->addr);
354                 ping_iterator_get_info (iter, PING_INFO_ADDRESS, context->addr, &buffer_size);
355
356                 ping_iterator_set_context (iter, (void *) context);
357
358                 index++;
359         }
360
361         return (0);
362 } /* }}} int ping_initialize_contexts */
363
364 static void usage_exit (const char *name, int status) /* {{{ */
365 {
366         fprintf (stderr, "Usage: %s [OPTIONS] "
367                                 "-f filename | host [host [host ...]]\n"
368
369                         "\nAvailable options:\n"
370                         "  -4|-6        force the use of IPv4 or IPv6\n"
371                         "  -c count     number of ICMP packets to send\n"
372                         "  -i interval  interval with which to send ICMP packets\n"
373                         "  -t ttl       time to live for each ICMP packet\n"
374                         "  -Q qos       Quality of Service (QoS) of outgoing packets\n"
375                         "               Use \"-Q help\" for a list of valid options.\n"
376                         "  -I srcaddr   source address\n"
377                         "  -D device    outgoing interface name\n"
378                         "  -f filename  filename to read hosts from\n"
379 #if USE_NCURSES
380                         "  -u / -U      force / disable UTF-8 output\n"
381                         "  -g graph     graph type to draw\n"
382 #endif
383                         "  -P percent   Report the n'th percentile of latency\n"
384                         "  -Z percent   Exit with non-zero exit status if more than this percentage of\n"
385                         "               probes timed out. (default: never)\n"
386
387                         "\noping "PACKAGE_VERSION", http://verplant.org/liboping/\n"
388                         "by Florian octo Forster <octo@verplant.org>\n"
389                         "for contributions see `AUTHORS'\n",
390                         name);
391         exit (status);
392 } /* }}} void usage_exit */
393
394 __attribute__((noreturn))
395 static void usage_qos_exit (const char *arg, int status) /* {{{ */
396 {
397         if (arg != 0)
398                 fprintf (stderr, "Invalid QoS argument: \"%s\"\n\n", arg);
399
400         fprintf (stderr, "Valid QoS arguments (option \"-Q\") are:\n"
401                         "\n"
402                         "  Differentiated Services (IPv4 and IPv6, RFC 2474)\n"
403                         "\n"
404                         "    be                     Best Effort (BE, default PHB).\n"
405                         "    ef                     Expedited Forwarding (EF) PHB group (RFC 3246).\n"
406                         "                           (low delay, low loss, low jitter)\n"
407                         "    va                     Voice Admit (VA) DSCP (RFC 5865).\n"
408                         "                           (capacity-admitted traffic)\n"
409                         "    af[1-4][1-3]           Assured Forwarding (AF) PHB group (RFC 2597).\n"
410                         "                           For example: \"af12\" (class 1, precedence 2)\n"
411                         "    cs[0-7]                Class Selector (CS) PHB group (RFC 2474).\n"
412                         "                           For example: \"cs1\" (priority traffic)\n"
413                         "\n"
414                         "  Type of Service (IPv4, RFC 1349, obsolete)\n"
415                         "\n"
416                         "    lowdelay     (%#04x)    minimize delay\n"
417                         "    throughput   (%#04x)    maximize throughput\n"
418                         "    reliability  (%#04x)    maximize reliability\n"
419                         "    mincost      (%#04x)    minimize monetary cost\n"
420                         "\n"
421                         "  Specify manually\n"
422                         "\n"
423                         "    0x00 - 0xff            Hexadecimal numeric specification.\n"
424                         "       0 -  255            Decimal numeric specification.\n"
425                         "\n",
426                         (unsigned int) IPTOS_LOWDELAY,
427                         (unsigned int) IPTOS_THROUGHPUT,
428                         (unsigned int) IPTOS_RELIABILITY,
429                         (unsigned int) IPTOS_MINCOST);
430
431         exit (status);
432 } /* }}} void usage_qos_exit */
433
434 static int set_opt_send_qos (const char *opt) /* {{{ */
435 {
436         if (opt == NULL)
437                 return (EINVAL);
438
439         if (strcasecmp ("help", opt) == 0)
440                 usage_qos_exit (/* arg = */ NULL, /* status = */ EXIT_SUCCESS);
441         /* DiffServ (RFC 2474): */
442         /* - Best effort (BE) */
443         else if (strcasecmp ("be", opt) == 0)
444                 opt_send_qos = 0;
445         /* - Expedited Forwarding (EF, RFC 3246) */
446         else if (strcasecmp ("ef", opt) == 0)
447                 opt_send_qos = 0xB8; /* == 0x2E << 2 */
448         /* - Voice Admit (VA, RFC 5865) */
449         else if (strcasecmp ("va", opt) == 0)
450                 opt_send_qos = 0xB0; /* == 0x2D << 2 */
451         /* - Assured Forwarding (AF, RFC 2597) */
452         else if ((strncasecmp ("af", opt, strlen ("af")) == 0)
453                         && (strlen (opt) == 4))
454         {
455                 uint8_t dscp;
456                 uint8_t class = 0;
457                 uint8_t prec = 0;
458
459                 /* There are four classes, AF1x, AF2x, AF3x, and AF4x. */
460                 if (opt[2] == '1')
461                         class = 1;
462                 else if (opt[2] == '2')
463                         class = 2;
464                 else if (opt[2] == '3')
465                         class = 3;
466                 else if (opt[2] == '4')
467                         class = 4;
468                 else
469                         usage_qos_exit (/* arg = */ opt, /* status = */ EXIT_SUCCESS);
470
471                 /* In each class, there are three precedences, AFx1, AFx2, and AFx3 */
472                 if (opt[3] == '1')
473                         prec = 1;
474                 else if (opt[3] == '2')
475                         prec = 2;
476                 else if (opt[3] == '3')
477                         prec = 3;
478                 else
479                         usage_qos_exit (/* arg = */ opt, /* status = */ EXIT_SUCCESS);
480
481                 dscp = (8 * class) + (2 * prec);
482                 /* The lower two bits are used for Explicit Congestion Notification (ECN) */
483                 opt_send_qos = dscp << 2;
484         }
485         /* - Class Selector (CS) */
486         else if ((strncasecmp ("cs", opt, strlen ("cs")) == 0)
487                         && (strlen (opt) == 3))
488         {
489                 uint8_t class;
490
491                 if ((opt[2] < '0') || (opt[2] > '7'))
492                         usage_qos_exit (/* arg = */ opt, /* status = */ EXIT_FAILURE);
493
494                 /* Not exactly legal by the C standard, but I don't know of any
495                  * system not supporting this hack. */
496                 class = ((uint8_t) opt[2]) - ((uint8_t) '0');
497                 opt_send_qos = class << 5;
498         }
499         /* Type of Service (RFC 1349) */
500         else if (strcasecmp ("lowdelay", opt) == 0)
501                 opt_send_qos = IPTOS_LOWDELAY;
502         else if (strcasecmp ("throughput", opt) == 0)
503                 opt_send_qos = IPTOS_THROUGHPUT;
504         else if (strcasecmp ("reliability", opt) == 0)
505                 opt_send_qos = IPTOS_RELIABILITY;
506         else if (strcasecmp ("mincost", opt) == 0)
507                 opt_send_qos = IPTOS_MINCOST;
508         /* Numeric value */
509         else
510         {
511                 unsigned long value;
512                 char *endptr;
513
514                 errno = 0;
515                 endptr = NULL;
516                 value = strtoul (opt, &endptr, /* base = */ 0);
517                 if ((errno != 0) || (endptr == opt)
518                                 || (endptr == NULL) || (*endptr != 0)
519                                 || (value > 0xff))
520                         usage_qos_exit (/* arg = */ opt, /* status = */ EXIT_FAILURE);
521                 
522                 opt_send_qos = (uint8_t) value;
523         }
524
525         return (0);
526 } /* }}} int set_opt_send_qos */
527
528 static char *format_qos (uint8_t qos, char *buffer, size_t buffer_size) /* {{{ */
529 {
530         uint8_t dscp;
531         uint8_t ecn;
532         char *dscp_str;
533         char *ecn_str;
534
535         dscp = qos >> 2;
536         ecn = qos & 0x03;
537
538         switch (dscp)
539         {
540                 case 0x00: dscp_str = "be";  break;
541                 case 0x2e: dscp_str = "ef";  break;
542                 case 0x2d: dscp_str = "va";  break;
543                 case 0x0a: dscp_str = "af11"; break;
544                 case 0x0c: dscp_str = "af12"; break;
545                 case 0x0e: dscp_str = "af13"; break;
546                 case 0x12: dscp_str = "af21"; break;
547                 case 0x14: dscp_str = "af22"; break;
548                 case 0x16: dscp_str = "af23"; break;
549                 case 0x1a: dscp_str = "af31"; break;
550                 case 0x1c: dscp_str = "af32"; break;
551                 case 0x1e: dscp_str = "af33"; break;
552                 case 0x22: dscp_str = "af41"; break;
553                 case 0x24: dscp_str = "af42"; break;
554                 case 0x26: dscp_str = "af43"; break;
555                 case 0x08: dscp_str = "cs1";  break;
556                 case 0x10: dscp_str = "cs2";  break;
557                 case 0x18: dscp_str = "cs3";  break;
558                 case 0x20: dscp_str = "cs4";  break;
559                 case 0x28: dscp_str = "cs5";  break;
560                 case 0x30: dscp_str = "cs6";  break;
561                 case 0x38: dscp_str = "cs7";  break;
562                 default:   dscp_str = NULL;
563         }
564
565         switch (ecn)
566         {
567                 case 0x01: ecn_str = ",ecn(1)"; break;
568                 case 0x02: ecn_str = ",ecn(0)"; break;
569                 case 0x03: ecn_str = ",ce"; break;
570                 default:   ecn_str = "";
571         }
572
573         if (dscp_str == NULL)
574                 snprintf (buffer, buffer_size, "0x%02x%s", dscp, ecn_str);
575         else
576                 snprintf (buffer, buffer_size, "%s%s", dscp_str, ecn_str);
577         buffer[buffer_size - 1] = 0;
578
579         return (buffer);
580 } /* }}} char *format_qos */
581
582 static int read_options (int argc, char **argv) /* {{{ */
583 {
584         int optchar;
585
586         while (1)
587         {
588                 optchar = getopt (argc, argv, "46c:hi:I:t:Q:f:D:Z:P:"
589 #if USE_NCURSES
590                                 "uUg:"
591 #endif
592                                 );
593
594                 if (optchar == -1)
595                         break;
596
597                 switch (optchar)
598                 {
599                         case '4':
600                         case '6':
601                                 opt_addrfamily = (optchar == '4') ? AF_INET : AF_INET6;
602                                 break;
603
604                         case 'c':
605                                 {
606                                         int new_count;
607                                         new_count = atoi (optarg);
608                                         if (new_count > 0)
609                                         {
610                                                 opt_count = new_count;
611
612                                                 if ((opt_percentile < 0.0) && (opt_count < 20))
613                                                         opt_percentile = 100.0 * (opt_count - 1) / opt_count;
614                                         }
615                                         else
616                                                 fprintf(stderr, "Ignoring invalid count: %s\n",
617                                                                 optarg);
618                                 }
619                                 break;
620
621                         case 'f':
622                                 {
623                                         if (opt_filename != NULL)
624                                                 free (opt_filename);
625                                         opt_filename = strdup (optarg);
626                                 }
627                                 break;
628
629                         case 'i':
630                                 {
631                                         double new_interval;
632                                         new_interval = atof (optarg);
633                                         if (new_interval < 0.001)
634                                                 fprintf (stderr, "Ignoring invalid interval: %s\n",
635                                                                 optarg);
636                                         else
637                                                 opt_interval = new_interval;
638                                 }
639                                 break;
640
641                         case 'I':
642                                 {
643                                         if (opt_srcaddr != NULL)
644                                                 free (opt_srcaddr);
645                                         opt_srcaddr = strdup (optarg);
646                                 }
647                                 break;
648
649                         case 'D':
650                                 opt_device = optarg;
651                                 break;
652
653                         case 't':
654                         {
655                                 int new_send_ttl;
656                                 new_send_ttl = atoi (optarg);
657                                 if ((new_send_ttl > 0) && (new_send_ttl < 256))
658                                         opt_send_ttl = new_send_ttl;
659                                 else
660                                         fprintf (stderr, "Ignoring invalid TTL argument: %s\n",
661                                                         optarg);
662                                 break;
663                         }
664
665                         case 'Q':
666                                 set_opt_send_qos (optarg);
667                                 break;
668
669                         case 'P':
670                                 {
671                                         double new_percentile;
672                                         new_percentile = atof (optarg);
673                                         if (isnan (new_percentile)
674                                                         || (new_percentile < 0.1)
675                                                         || (new_percentile > 100.0))
676                                                 fprintf (stderr, "Ignoring invalid percentile: %s\n",
677                                                                 optarg);
678                                         else
679                                                 opt_percentile = new_percentile;
680                                 }
681                                 break;
682
683 #if USE_NCURSES
684                         case 'g':
685                                 if (strcasecmp ("none", optarg) == 0)
686                                         opt_show_graph = 0;
687                                 else if (strcasecmp ("prettyping", optarg) == 0)
688                                         opt_show_graph = 1;
689                                 else if (strcasecmp ("boxplot", optarg) == 0)
690                                         opt_show_graph = 2;
691                                 else if (strcasecmp ("histogram", optarg) == 0)
692                                         opt_show_graph = 3;
693                                 else
694                                         fprintf (stderr, "Unknown graph option: %s\n", optarg);
695                                 break;
696
697                         case 'u':
698                                 opt_utf8 = 2;
699                                 break;
700                         case 'U':
701                                 opt_utf8 = 1;
702                                 break;
703 #endif
704
705                         case 'Z':
706                         {
707                                 char *endptr = NULL;
708                                 double tmp;
709
710                                 errno = 0;
711                                 tmp = strtod (optarg, &endptr);
712                                 if ((errno != 0) || (endptr == NULL) || (*endptr != 0) || (tmp < 0.0) || (tmp > 100.0))
713                                 {
714                                         fprintf (stderr, "Ignoring invalid -Z argument: %s\n", optarg);
715                                         fprintf (stderr, "The \"-Z\" option requires a numeric argument between 0 and 100.\n");
716                                 }
717                                 else
718                                         opt_exit_status_threshold = tmp / 100.0;
719
720                                 break;
721                         }
722
723                         case 'h':
724                                 usage_exit (argv[0], 0);
725                                 break;
726
727                         default:
728                                 usage_exit (argv[0], 1);
729                 }
730         }
731
732         if (opt_percentile <= 0.0)
733                 opt_percentile = OPING_DEFAULT_PERCENTILE;
734
735         return (optind);
736 } /* }}} read_options */
737
738 static void time_normalize (struct timespec *ts) /* {{{ */
739 {
740         while (ts->tv_nsec < 0)
741         {
742                 if (ts->tv_sec == 0)
743                 {
744                         ts->tv_nsec = 0;
745                         return;
746                 }
747
748                 ts->tv_sec  -= 1;
749                 ts->tv_nsec += 1000000000;
750         }
751
752         while (ts->tv_nsec >= 1000000000)
753         {
754                 ts->tv_sec  += 1;
755                 ts->tv_nsec -= 1000000000;
756         }
757 } /* }}} void time_normalize */
758
759 static void time_calc (struct timespec *ts_dest, /* {{{ */
760                 const struct timespec *ts_int,
761                 const struct timeval  *tv_begin,
762                 const struct timeval  *tv_end)
763 {
764         ts_dest->tv_sec = tv_begin->tv_sec + ts_int->tv_sec;
765         ts_dest->tv_nsec = (tv_begin->tv_usec * 1000) + ts_int->tv_nsec;
766         time_normalize (ts_dest);
767
768         /* Assure that `(begin + interval) > end'.
769          * This may seem overly complicated, but `tv_sec' is of type `time_t'
770          * which may be `unsigned. *sigh* */
771         if ((tv_end->tv_sec > ts_dest->tv_sec)
772                         || ((tv_end->tv_sec == ts_dest->tv_sec)
773                                 && ((tv_end->tv_usec * 1000) > ts_dest->tv_nsec)))
774         {
775                 ts_dest->tv_sec  = 0;
776                 ts_dest->tv_nsec = 0;
777                 return;
778         }
779
780         ts_dest->tv_sec = ts_dest->tv_sec - tv_end->tv_sec;
781         ts_dest->tv_nsec = ts_dest->tv_nsec - (tv_end->tv_usec * 1000);
782         time_normalize (ts_dest);
783 } /* }}} void time_calc */
784
785 #if USE_NCURSES
786 static _Bool has_utf8() /* {{{ */
787 {
788 # if HAVE_NCURSESW_NCURSES_H
789         if (!opt_utf8)
790         {
791                 /* Automatically determine */
792                 if (strcasecmp ("UTF-8", nl_langinfo (CODESET)) == 0)
793                         opt_utf8 = 2;
794                 else
795                         opt_utf8 = 1;
796         }
797         return ((_Bool) (opt_utf8 - 1));
798 # else
799         return (0);
800 # endif
801 } /* }}} _Bool has_utf8 */
802
803 static int update_graph_boxplot (ping_context_t *ctx) /* {{{ */
804 {
805         double *ratios;
806         size_t i;
807         size_t x_max;
808         size_t x;
809
810         x_max = (size_t) getmaxx (ctx->window);
811         if (x_max <= 8)
812                 return (EINVAL);
813         x_max -= 4;
814
815         ratios = calloc (x_max, sizeof (*ratios));
816
817         /* Downsample */
818         for (i = 0; i < ctx->latency_histogram_size; i++)
819         {
820                 x = i * x_max / ctx->latency_histogram_size;
821                 ratios[x] = ctx->histogram_ratio[i];
822         }
823
824         for (x = 0; x < x_max; x++)
825         {
826                 int symbol = ' ';
827                 _Bool reverse = 0;
828
829                 if (x == 0)
830                 {
831                         if (ratios[x] >= 0.5)
832                         {
833                                 symbol = BOXPLOT_MEDIAN;
834                                 reverse = 1;
835                         }
836                         else if (ratios[x] > 0.25)
837                         {
838                                 symbol = BOXPLOT_BOX;
839                                 reverse = 1;
840                         }
841                         else if (ratios[x] > 0.025)
842                                 symbol = BOXPLOT_WHISKER_BAR;
843                         else
844                                 symbol = ' '; /* NOP */
845                 }
846                 else /* (x != 0) */
847                 {
848                         if ((ratios[x - 1] < 0.5) && (ratios[x] >= 0.5))
849                         {
850                                 symbol = BOXPLOT_MEDIAN;
851                                 reverse = 1;
852                         }
853                         else if (((ratios[x] >= 0.25) && (ratios[x] <= 0.75))
854                                         || ((ratios[x - 1] < 0.75) && (ratios[x] > 0.75)))
855                         {
856                                 symbol = BOXPLOT_BOX;
857                                 reverse = 1;
858                         }
859                         else if ((ratios[x] < 0.5) && (ratios[x] >= 0.025))
860                         {
861                                 if (ratios[x - 1] < 0.025)
862                                         symbol = BOXPLOT_WHISKER_LEFT_END;
863                                 else
864                                         symbol = BOXPLOT_WHISKER_BAR;
865                         }
866                         else if ((ratios[x] > .5) && (ratios[x] < 0.975))
867                         {
868                                 symbol = BOXPLOT_WHISKER_BAR;
869                         }
870                         else if ((ratios[x] >= 0.975) && (ratios[x - 1] < 0.975))
871                                 symbol = BOXPLOT_WHISKER_RIGHT_END;
872                 }
873
874                 if (reverse)
875                         wattron (ctx->window, A_REVERSE);
876                 mvwaddch (ctx->window, /* y = */ 3, /* x = */ (int) (x + 2), symbol);
877                 // mvwprintw (ctx->window, /* y = */ 3, /* x = */ (int) (x + 2), symbol);
878                 if (reverse)
879                         wattroff (ctx->window, A_REVERSE);
880         }
881
882         free (ratios);
883         return (0);
884 } /* }}} int update_graph_boxplot */
885
886 static int update_graph_prettyping (ping_context_t *ctx, /* {{{ */
887                 double latency, unsigned int sequence)
888 {
889         int color = OPING_RED;
890         char const *symbol = "!";
891         int symbolc = '!';
892
893         int x_max;
894         int x_pos;
895
896         x_max = getmaxx (ctx->window);
897         x_pos = ((sequence - 1) % (x_max - 4)) + 2;
898
899         if (latency >= 0.0)
900         {
901                 double ratio;
902
903                 size_t symbols_num = hist_symbols_acs_num;
904                 size_t colors_num = 1;
905
906                 size_t index_symbols;
907                 size_t index_colors;
908                 size_t intensity;
909
910                 /* latency is in milliseconds, opt_interval is in seconds. */
911                 ratio = (latency * 0.001) / opt_interval;
912                 if (ratio > 1) {
913                         ratio = 1.0;
914                 }
915
916                 if (has_utf8 ())
917                         symbols_num = hist_symbols_utf8_num;
918
919                 if (has_colors () == TRUE)
920                         colors_num = hist_colors_num;
921
922                 intensity = (size_t) (ratio * ((double) (symbols_num * colors_num)));
923                 if (intensity >= (symbols_num * colors_num))
924                         intensity = (symbols_num * colors_num) - 1;
925
926                 index_symbols = intensity % symbols_num;
927                 assert (index_symbols < symbols_num);
928
929                 index_colors = intensity / symbols_num;
930                 assert (index_colors < colors_num);
931
932                 if (has_utf8())
933                 {
934                         color = hist_colors_utf8[index_colors];
935                         symbol = hist_symbols_utf8[index_symbols];
936                 }
937                 else
938                 {
939                         color = hist_colors_acs[index_colors];
940                         symbolc = hist_symbols_acs[index_symbols] | A_ALTCHARSET;
941                 }
942         }
943         else /* if (!(latency >= 0.0)) */
944                 wattron (ctx->window, A_BOLD);
945
946         if (has_colors () == TRUE)
947                 wattron (ctx->window, COLOR_PAIR(color));
948
949         if (has_utf8())
950                 mvwprintw (ctx->window, /* y = */ 3, /* x = */ x_pos, symbol);
951         else
952                 mvwaddch (ctx->window, /* y = */ 3, /* x = */ x_pos, symbolc);
953
954         if (has_colors () == TRUE)
955                 wattroff (ctx->window, COLOR_PAIR(color));
956
957         /* Use negation here to handle NaN correctly. */
958         if (!(latency >= 0.0))
959                 wattroff (ctx->window, A_BOLD);
960
961         wprintw (ctx->window, " ");
962         return (0);
963 } /* }}} int update_graph_prettyping */
964
965 static int update_graph_histogram (ping_context_t *ctx) /* {{{ */
966 {
967         uint32_t *counters;
968         uint32_t *accumulated;
969         uint32_t num;
970         uint32_t max;
971         size_t i;
972         size_t x_max;
973         size_t x;
974
975         size_t symbols_num = hist_symbols_acs_num;
976
977         if (has_utf8 ())
978                 symbols_num = hist_symbols_utf8_num;
979
980         x_max = (size_t) getmaxx (ctx->window);
981         if (x_max <= 4)
982                 return (EINVAL);
983         x_max -= 4;
984
985         counters = calloc (x_max, sizeof (*counters));
986         accumulated = calloc (x_max, sizeof (*accumulated));
987
988         /* Downsample */
989         max = 0;
990         for (i = 0; i < ctx->latency_histogram_size; i++)
991         {
992                 x = i * x_max / ctx->latency_histogram_size;
993                 counters[x] += ctx->histogram_counters[i];
994                 accumulated[x] = counters[x];
995
996                 if (max < counters[x])
997                         max = counters[x];
998         }
999
1000         /* Sum */
1001         for (x = 1; x < x_max; x++)
1002                 accumulated[x] += accumulated[x - 1];
1003         num = accumulated[x_max - 1];
1004
1005         /* Calculate ratios */
1006         for (x = 0; x < x_max; x++)
1007         {
1008                 double height = ((double) counters[x]) / ((double) max);
1009                 double ratio_this = ((double) accumulated[x]) / ((double) num);
1010                 double ratio_prev = 0.0;
1011                 size_t index;
1012                 int color = 0;
1013
1014                 index = (size_t) (height * ((double) symbols_num));
1015                 if (index >= symbols_num)
1016                         index = symbols_num - 1;
1017
1018                 if (x > 0)
1019                         ratio_prev = ((double) accumulated[x - 1]) / ((double) num);
1020
1021                 if (has_colors () == TRUE)
1022                 {
1023                         if ((ratio_this <= 0.5) || ((ratio_prev < 0.5) && (ratio_this > 0.5)))
1024                                 color = OPING_GREEN;
1025                         else if ((ratio_this <= 0.95) || ((ratio_prev < 0.95) && (ratio_this > 0.95)))
1026                                 color = OPING_YELLOW;
1027                         else
1028                                 color = OPING_RED;
1029
1030                         wattron (ctx->window, COLOR_PAIR(color));
1031                 }
1032
1033                 if (counters[x] == 0)
1034                         mvwaddch (ctx->window, /* y = */ 3, /* x = */ x + 2, ' ');
1035                 else if (has_utf8 ())
1036                         mvwprintw (ctx->window, /* y = */ 3, /* x = */ x + 2,
1037                                         hist_symbols_utf8[index]);
1038                 else
1039                         mvwaddch (ctx->window, /* y = */ 3, /* x = */ x + 2,
1040                                         hist_symbols_acs[index] | A_ALTCHARSET);
1041
1042                 if (has_colors () == TRUE)
1043                         wattroff (ctx->window, COLOR_PAIR(color));
1044
1045         }
1046
1047         free (accumulated);
1048         return (0);
1049 } /* }}} int update_graph_histogram */
1050
1051 static int update_stats_from_context (ping_context_t *ctx, pingobj_iter_t *iter) /* {{{ */
1052 {
1053         double latency = -1.0;
1054         size_t buffer_len = sizeof (latency);
1055
1056         ping_iterator_get_info (iter, PING_INFO_LATENCY,
1057                         &latency, &buffer_len);
1058
1059         unsigned int sequence = 0;
1060         buffer_len = sizeof (sequence);
1061         ping_iterator_get_info (iter, PING_INFO_SEQUENCE,
1062                         &sequence, &buffer_len);
1063
1064
1065         if ((ctx == NULL) || (ctx->window == NULL))
1066                 return (EINVAL);
1067
1068         /* werase (ctx->window); */
1069
1070         box (ctx->window, 0, 0);
1071         wattron (ctx->window, A_BOLD);
1072         mvwprintw (ctx->window, /* y = */ 0, /* x = */ 5,
1073                         " %s ", ctx->host);
1074         wattroff (ctx->window, A_BOLD);
1075         wprintw (ctx->window, "ping statistics ");
1076         mvwprintw (ctx->window, /* y = */ 1, /* x = */ 2,
1077                         "%i packets transmitted, %i received, %.2f%% packet "
1078                         "loss, time %.1fms",
1079                         ctx->req_sent, ctx->req_rcvd,
1080                         context_get_packet_loss (ctx),
1081                         ctx->latency_total);
1082         if (ctx->req_rcvd != 0)
1083         {
1084                 double average;
1085                 double deviation;
1086                 double percentile;
1087
1088                 average = context_get_average (ctx);
1089                 deviation = context_get_stddev (ctx);
1090                 percentile = context_get_percentile (ctx, opt_percentile);
1091
1092                 mvwprintw (ctx->window, /* y = */ 2, /* x = */ 2,
1093                                 "rtt min/avg/%.0f%%/max/sdev = "
1094                                 "%.3f/%.3f/%.0f/%.3f/%.3f ms\n",
1095                                 opt_percentile,
1096                                 ctx->latency_min,
1097                                 average,
1098                                 percentile,
1099                                 ctx->latency_max,
1100                                 deviation);
1101         }
1102
1103         if (opt_show_graph == 1)
1104                 update_graph_prettyping (ctx, latency, sequence);
1105         else if (opt_show_graph == 2)
1106                 update_graph_boxplot (ctx);
1107         else if (opt_show_graph == 3)
1108                 update_graph_histogram (ctx);
1109
1110         wrefresh (ctx->window);
1111
1112         return (0);
1113 } /* }}} int update_stats_from_context */
1114
1115 static int on_resize (pingobj_t *ping) /* {{{ */
1116 {
1117         pingobj_iter_t *iter;
1118         int width = 0;
1119         int height = 0;
1120         int main_win_height;
1121         int box_height = (opt_show_graph == 0) ? 4 : 5;
1122
1123         getmaxyx (stdscr, height, width);
1124         if ((height < 1) || (width < 1))
1125                 return (EINVAL);
1126
1127         main_win_height = height - (box_height * host_num);
1128         wresize (main_win, main_win_height, /* width = */ width);
1129         /* Allow scrolling */
1130         scrollok (main_win, TRUE);
1131         /* wsetscrreg (main_win, 0, main_win_height - 1); */
1132         /* Allow hardware accelerated scrolling. */
1133         idlok (main_win, TRUE);
1134         wrefresh (main_win);
1135
1136         for (iter = ping_iterator_get (ping);
1137                         iter != NULL;
1138                         iter = ping_iterator_next (iter))
1139         {
1140                 ping_context_t *context;
1141
1142                 context = ping_iterator_get_context (iter);
1143                 if (context == NULL)
1144                         continue;
1145
1146                 if (context->window != NULL)
1147                 {
1148                         delwin (context->window);
1149                         context->window = NULL;
1150                 }
1151                 context->window = newwin (/* height = */ box_height,
1152                                 /* width = */ width,
1153                                 /* y = */ main_win_height + (box_height * context->index),
1154                                 /* x = */ 0);
1155         }
1156
1157         return (0);
1158 } /* }}} */
1159
1160 static int check_resize (pingobj_t *ping) /* {{{ */
1161 {
1162         int need_resize = 0;
1163
1164         while (42)
1165         {
1166                 int key = wgetch (stdscr);
1167                 if (key == ERR)
1168                         break;
1169                 else if (key == KEY_RESIZE)
1170                         need_resize = 1;
1171         }
1172
1173         if (need_resize)
1174                 return (on_resize (ping));
1175         else
1176                 return (0);
1177 } /* }}} int check_resize */
1178
1179 static int pre_loop_hook (pingobj_t *ping) /* {{{ */
1180 {
1181         pingobj_iter_t *iter;
1182         int width = 0;
1183         int height = 0;
1184         int main_win_height;
1185         int box_height = (opt_show_graph == 0) ? 4 : 5;
1186
1187         initscr ();
1188         cbreak ();
1189         noecho ();
1190         nodelay (stdscr, TRUE);
1191
1192         getmaxyx (stdscr, height, width);
1193         if ((height < 1) || (width < 1))
1194                 return (EINVAL);
1195
1196         if (has_colors () == TRUE)
1197         {
1198                 start_color ();
1199                 init_pair (OPING_GREEN,  COLOR_GREEN,  /* default = */ 0);
1200                 init_pair (OPING_YELLOW, COLOR_YELLOW, /* default = */ 0);
1201                 init_pair (OPING_RED,    COLOR_RED,    /* default = */ 0);
1202                 init_pair (OPING_GREEN_HIST,  COLOR_GREEN,  COLOR_BLACK);
1203                 init_pair (OPING_YELLOW_HIST, COLOR_YELLOW, COLOR_GREEN);
1204                 init_pair (OPING_RED_HIST,    COLOR_RED,    COLOR_YELLOW);
1205         }
1206
1207         main_win_height = height - (box_height * host_num);
1208         main_win = newwin (/* height = */ main_win_height,
1209                         /* width = */ width,
1210                         /* y = */ 0, /* x = */ 0);
1211         /* Allow scrolling */
1212         scrollok (main_win, TRUE);
1213         /* wsetscrreg (main_win, 0, main_win_height - 1); */
1214         /* Allow hardware accelerated scrolling. */
1215         idlok (main_win, TRUE);
1216         wmove (main_win, /* y = */ main_win_height - 1, /* x = */ 0);
1217         wrefresh (main_win);
1218
1219         for (iter = ping_iterator_get (ping);
1220                         iter != NULL;
1221                         iter = ping_iterator_next (iter))
1222         {
1223                 ping_context_t *context;
1224
1225                 context = ping_iterator_get_context (iter);
1226                 if (context == NULL)
1227                         continue;
1228
1229                 if (context->window != NULL)
1230                 {
1231                         delwin (context->window);
1232                         context->window = NULL;
1233                 }
1234                 context->window = newwin (/* height = */ box_height,
1235                                 /* width = */ width,
1236                                 /* y = */ main_win_height + (box_height * context->index),
1237                                 /* x = */ 0);
1238         }
1239
1240
1241         /* Don't know what good this does exactly, but without this code
1242          * "check_resize" will be called right after startup and *somehow*
1243          * this leads to display errors. If we purge all initial characters
1244          * here, the problem goes away. "wgetch" is non-blocking due to
1245          * "nodelay" (see above). */
1246         while (wgetch (stdscr) != ERR)
1247         {
1248                 /* eat up characters */;
1249         }
1250
1251         return (0);
1252 } /* }}} int pre_loop_hook */
1253
1254 static int pre_sleep_hook (pingobj_t *ping) /* {{{ */
1255 {
1256         return (check_resize (ping));
1257 } /* }}} int pre_sleep_hook */
1258
1259 static int post_sleep_hook (pingobj_t *ping) /* {{{ */
1260 {
1261         return (check_resize (ping));
1262 } /* }}} int pre_sleep_hook */
1263 #else /* if !USE_NCURSES */
1264 static int pre_loop_hook (pingobj_t *ping) /* {{{ */
1265 {
1266         pingobj_iter_t *iter;
1267
1268         for (iter = ping_iterator_get (ping);
1269                         iter != NULL;
1270                         iter = ping_iterator_next (iter))
1271         {
1272                 ping_context_t *ctx;
1273                 size_t buffer_size;
1274
1275                 ctx = ping_iterator_get_context (iter);
1276                 if (ctx == NULL)
1277                         continue;
1278
1279                 buffer_size = 0;
1280                 ping_iterator_get_info (iter, PING_INFO_DATA, NULL, &buffer_size);
1281
1282                 printf ("PING %s (%s) %zu bytes of data.\n",
1283                                 ctx->host, ctx->addr, buffer_size);
1284         }
1285
1286         return (0);
1287 } /* }}} int pre_loop_hook */
1288
1289 static int pre_sleep_hook (__attribute__((unused)) pingobj_t *ping) /* {{{ */
1290 {
1291         fflush (stdout);
1292
1293         return (0);
1294 } /* }}} int pre_sleep_hook */
1295
1296 static int post_sleep_hook (__attribute__((unused)) pingobj_t *ping) /* {{{ */
1297 {
1298         return (0);
1299 } /* }}} int post_sleep_hook */
1300 #endif
1301
1302 static size_t latency_to_bucket (ping_context_t *ctx, double latency) /* {{{ */
1303 {
1304         size_t bucket;
1305
1306         /* latency is in ms, opt_interval is in s. */
1307         bucket = (size_t) ((latency * (ctx->latency_histogram_size - 1))
1308                         / (1000.0 * opt_interval));
1309         if (bucket >= ctx->latency_histogram_size)
1310                 bucket = ctx->latency_histogram_size - 1;
1311
1312         return (bucket);
1313 } /* }}} size_t latency_to_bucket */
1314
1315 static void update_context (ping_context_t *context, double latency) /* {{{ */
1316 {
1317         size_t bucket;
1318         size_t i;
1319         double num;
1320
1321         context->req_rcvd++;
1322         context->latency_total += latency;
1323         context->latency_total_square += (latency * latency);
1324
1325         if ((context->latency_max < 0.0) || (context->latency_max < latency))
1326                 context->latency_max = latency;
1327         if ((context->latency_min < 0.0) || (context->latency_min > latency))
1328                 context->latency_min = latency;
1329
1330         bucket = latency_to_bucket (context, latency);
1331         num = (double) context->req_rcvd;
1332
1333         context->histogram_counters[bucket]++;
1334
1335         context->histogram_accumulated[0] = context->histogram_counters[0];
1336         context->histogram_ratio[0] = ((double) context->histogram_accumulated[0]) / num;
1337         for (i = 1; i < context->latency_histogram_size; i++)
1338         {
1339                 context->histogram_accumulated[i] = context->histogram_accumulated[i - 1]
1340                         + context->histogram_counters[i];
1341                 context->histogram_ratio[i] = ((double) context->histogram_accumulated[i]) / num;
1342         }
1343 } /* }}} void update_context */
1344
1345 static void update_host_hook (pingobj_iter_t *iter, /* {{{ */
1346                 __attribute__((unused)) int index)
1347 {
1348         double          latency;
1349         unsigned int    sequence;
1350         int             recv_ttl;
1351         uint8_t         recv_qos;
1352         char            recv_qos_str[16];
1353         size_t          buffer_len;
1354         size_t          data_len;
1355         ping_context_t *context;
1356
1357         latency = -1.0;
1358         buffer_len = sizeof (latency);
1359         ping_iterator_get_info (iter, PING_INFO_LATENCY,
1360                         &latency, &buffer_len);
1361
1362         sequence = 0;
1363         buffer_len = sizeof (sequence);
1364         ping_iterator_get_info (iter, PING_INFO_SEQUENCE,
1365                         &sequence, &buffer_len);
1366
1367         recv_ttl = -1;
1368         buffer_len = sizeof (recv_ttl);
1369         ping_iterator_get_info (iter, PING_INFO_RECV_TTL,
1370                         &recv_ttl, &buffer_len);
1371
1372         recv_qos = 0;
1373         buffer_len = sizeof (recv_qos);
1374         ping_iterator_get_info (iter, PING_INFO_RECV_QOS,
1375                         &recv_qos, &buffer_len);
1376
1377         data_len = 0;
1378         ping_iterator_get_info (iter, PING_INFO_DATA,
1379                         NULL, &data_len);
1380
1381         context = (ping_context_t *) ping_iterator_get_context (iter);
1382
1383 #if USE_NCURSES
1384 # define HOST_PRINTF(...) wprintw(main_win, __VA_ARGS__)
1385 #else
1386 # define HOST_PRINTF(...) printf(__VA_ARGS__)
1387 #endif
1388
1389         context->req_sent++;
1390         if (latency > 0.0)
1391         {
1392                 update_context (context, latency);
1393
1394 #if USE_NCURSES
1395                 if (has_colors () == TRUE)
1396                 {
1397                         size_t bucket;
1398                         double ratio_this;
1399                         double ratio_prev;
1400                         int color = OPING_GREEN;
1401
1402                         bucket = latency_to_bucket (context, latency);
1403                         ratio_this = context->histogram_ratio[bucket];
1404                         if (bucket > 0)
1405                                 ratio_prev = context->histogram_ratio[bucket - 1];
1406                         else
1407                                 ratio_prev = 0.0;
1408
1409                         if ((ratio_this <= 0.5) ||
1410                                         ((ratio_prev < 0.5) && (ratio_this > 0.5)))
1411                                 color = OPING_GREEN;
1412                         else if ((ratio_this <= 0.95) ||
1413                                         ((ratio_prev < 0.95) && (ratio_this > 0.95)))
1414                                 color = OPING_YELLOW;
1415                         else
1416                                 color = OPING_RED;
1417
1418                         HOST_PRINTF ("%zu bytes from %s (%s): icmp_seq=%u ttl=%i ",
1419                                         data_len, context->host, context->addr,
1420                                         sequence, recv_ttl,
1421                                         format_qos (recv_qos, recv_qos_str, sizeof (recv_qos_str)));
1422                         if ((recv_qos != 0) || (opt_send_qos != 0))
1423                         {
1424                                 HOST_PRINTF ("qos=%s ",
1425                                                 format_qos (recv_qos, recv_qos_str, sizeof (recv_qos_str)));
1426                         }
1427                         HOST_PRINTF ("time=");
1428                         wattron (main_win, COLOR_PAIR(color));
1429                         HOST_PRINTF ("%.2f", latency);
1430                         wattroff (main_win, COLOR_PAIR(color));
1431                         HOST_PRINTF (" ms\n");
1432                 }
1433                 else
1434                 {
1435 #endif
1436                 HOST_PRINTF ("%zu bytes from %s (%s): icmp_seq=%u ttl=%i ",
1437                                 data_len,
1438                                 context->host, context->addr,
1439                                 sequence, recv_ttl);
1440                 if ((recv_qos != 0) || (opt_send_qos != 0))
1441                 {
1442                         HOST_PRINTF ("qos=%s ",
1443                                         format_qos (recv_qos, recv_qos_str, sizeof (recv_qos_str)));
1444                 }
1445                 HOST_PRINTF ("time=%.2f ms\n", latency);
1446 #if USE_NCURSES
1447                 }
1448 #endif
1449         }
1450         else
1451         {
1452 #if USE_NCURSES
1453                 if (has_colors () == TRUE)
1454                 {
1455                         HOST_PRINTF ("echo reply from %s (%s): icmp_seq=%u ",
1456                                         context->host, context->addr,
1457                                         sequence);
1458                         wattron (main_win, COLOR_PAIR(OPING_RED) | A_BOLD);
1459                         HOST_PRINTF ("timeout");
1460                         wattroff (main_win, COLOR_PAIR(OPING_RED) | A_BOLD);
1461                         HOST_PRINTF ("\n");
1462                 }
1463                 else
1464                 {
1465 #endif
1466                 HOST_PRINTF ("echo reply from %s (%s): icmp_seq=%u timeout\n",
1467                                 context->host, context->addr,
1468                                 sequence);
1469 #if USE_NCURSES
1470                 }
1471 #endif
1472         }
1473
1474 #if USE_NCURSES
1475         update_stats_from_context (context, iter);
1476         wrefresh (main_win);
1477 #endif
1478 } /* }}} void update_host_hook */
1479
1480 /* Prints statistics for each host, cleans up the contexts and returns the
1481  * number of hosts which failed to return more than the fraction
1482  * opt_exit_status_threshold of pings. */
1483 static int post_loop_hook (pingobj_t *ping) /* {{{ */
1484 {
1485         pingobj_iter_t *iter;
1486         int failure_count = 0;
1487
1488 #if USE_NCURSES
1489         endwin ();
1490 #endif
1491
1492         for (iter = ping_iterator_get (ping);
1493                         iter != NULL;
1494                         iter = ping_iterator_next (iter))
1495         {
1496                 ping_context_t *context;
1497
1498                 context = ping_iterator_get_context (iter);
1499
1500                 printf ("\n--- %s ping statistics ---\n"
1501                                 "%i packets transmitted, %i received, %.2f%% packet loss, time %.1fms\n",
1502                                 context->host, context->req_sent, context->req_rcvd,
1503                                 context_get_packet_loss (context),
1504                                 context->latency_total);
1505
1506                 {
1507                         double pct_failed = 1.0 - (((double) context->req_rcvd)
1508                                         / ((double) context->req_sent));
1509                         if (pct_failed > opt_exit_status_threshold)
1510                                 failure_count++;
1511                 }
1512
1513                 if (context->req_rcvd != 0)
1514                 {
1515                         double average;
1516                         double deviation;
1517                         double percentile;
1518
1519                         average = context_get_average (context);
1520                         deviation = context_get_stddev (context);
1521                         percentile = context_get_percentile (context, opt_percentile);
1522
1523                         printf ("rtt min/avg/%.0f%%/max/sdev = "
1524                                         "%.3f/%.3f/%.0f/%.3f/%.3f ms\n",
1525                                         opt_percentile,
1526                                         context->latency_min,
1527                                         average,
1528                                         percentile,
1529                                         context->latency_max,
1530                                         deviation);
1531                 }
1532
1533                 ping_iterator_set_context (iter, NULL);
1534                 context_destroy (context);
1535         }
1536
1537         return (failure_count);
1538 } /* }}} int post_loop_hook */
1539
1540 int main (int argc, char **argv) /* {{{ */
1541 {
1542         pingobj_t      *ping;
1543         pingobj_iter_t *iter;
1544
1545         struct sigaction sigint_action;
1546
1547         struct timeval  tv_begin;
1548         struct timeval  tv_end;
1549         struct timespec ts_wait;
1550         struct timespec ts_int;
1551
1552         int optind;
1553         int i;
1554         int status;
1555 #if _POSIX_SAVED_IDS
1556         uid_t saved_set_uid;
1557
1558         /* Save the old effective user id */
1559         saved_set_uid = geteuid ();
1560         /* Set the effective user ID to the real user ID without changing the
1561          * saved set-user ID */
1562         status = seteuid (getuid ());
1563         if (status != 0)
1564         {
1565                 fprintf (stderr, "Temporarily dropping privileges "
1566                                 "failed: %s\n", strerror (errno));
1567                 exit (EXIT_FAILURE);
1568         }
1569 #endif
1570
1571         setlocale(LC_ALL, "");
1572         optind = read_options (argc, argv);
1573
1574 #if !_POSIX_SAVED_IDS
1575         /* Cannot temporarily drop privileges -> reject every file but "-". */
1576         if ((opt_filename != NULL)
1577                         && (strcmp ("-", opt_filename) != 0)
1578                         && (getuid () != geteuid ()))
1579         {
1580                 fprintf (stderr, "Your real and effective user IDs don't "
1581                                 "match. Reading from a file (option '-f')\n"
1582                                 "is therefore too risky. You can still read "
1583                                 "from STDIN using '-f -' if you like.\n"
1584                                 "Sorry.\n");
1585                 exit (EXIT_FAILURE);
1586         }
1587 #endif
1588
1589         if ((optind >= argc) && (opt_filename == NULL)) {
1590                 usage_exit (argv[0], 1);
1591         }
1592
1593         if ((ping = ping_construct ()) == NULL)
1594         {
1595                 fprintf (stderr, "ping_construct failed\n");
1596                 return (1);
1597         }
1598
1599         if (ping_setopt (ping, PING_OPT_TTL, &opt_send_ttl) != 0)
1600         {
1601                 fprintf (stderr, "Setting TTL to %i failed: %s\n",
1602                                 opt_send_ttl, ping_get_error (ping));
1603         }
1604
1605         if (ping_setopt (ping, PING_OPT_QOS, &opt_send_qos) != 0)
1606         {
1607                 fprintf (stderr, "Setting TOS to %i failed: %s\n",
1608                                 opt_send_qos, ping_get_error (ping));
1609         }
1610
1611         {
1612                 double temp_sec;
1613                 double temp_nsec;
1614
1615                 temp_nsec = modf (opt_interval, &temp_sec);
1616                 ts_int.tv_sec  = (time_t) temp_sec;
1617                 ts_int.tv_nsec = (long) (temp_nsec * 1000000000L);
1618
1619                 /* printf ("ts_int = %i.%09li\n", (int) ts_int.tv_sec, ts_int.tv_nsec); */
1620         }
1621
1622         if (opt_addrfamily != PING_DEF_AF)
1623                 ping_setopt (ping, PING_OPT_AF, (void *) &opt_addrfamily);
1624
1625         if (opt_srcaddr != NULL)
1626         {
1627                 if (ping_setopt (ping, PING_OPT_SOURCE, (void *) opt_srcaddr) != 0)
1628                 {
1629                         fprintf (stderr, "Setting source address failed: %s\n",
1630                                         ping_get_error (ping));
1631                 }
1632         }
1633
1634         if (opt_device != NULL)
1635         {
1636                 if (ping_setopt (ping, PING_OPT_DEVICE, (void *) opt_device) != 0)
1637                 {
1638                         fprintf (stderr, "Setting device failed: %s\n",
1639                                         ping_get_error (ping));
1640                 }
1641         }
1642
1643         if (opt_filename != NULL)
1644         {
1645                 FILE *infile;
1646                 char line[256];
1647                 char host[256];
1648
1649                 if (strcmp (opt_filename, "-") == 0)
1650                         /* Open STDIN */
1651                         infile = fdopen(0, "r");
1652                 else
1653                         infile = fopen(opt_filename, "r");
1654
1655                 if (infile == NULL)
1656                 {
1657                         fprintf (stderr, "Opening %s failed: %s\n",
1658                                         (strcmp (opt_filename, "-") == 0)
1659                                         ? "STDIN" : opt_filename,
1660                                         strerror(errno));
1661                         return (1);
1662                 }
1663
1664 #if _POSIX_SAVED_IDS
1665                 /* Regain privileges */
1666                 status = seteuid (saved_set_uid);
1667                 if (status != 0)
1668                 {
1669                         fprintf (stderr, "Temporarily re-gaining privileges "
1670                                         "failed: %s\n", strerror (errno));
1671                         exit (EXIT_FAILURE);
1672                 }
1673 #endif
1674
1675                 while (fgets(line, sizeof(line), infile))
1676                 {
1677                         /* Strip whitespace */
1678                         if (sscanf(line, "%s", host) != 1)
1679                                 continue;
1680
1681                         if ((host[0] == 0) || (host[0] == '#'))
1682                                 continue;
1683
1684                         if (ping_host_add(ping, host) < 0)
1685                         {
1686                                 const char *errmsg = ping_get_error (ping);
1687
1688                                 fprintf (stderr, "Adding host `%s' failed: %s\n", host, errmsg);
1689                                 continue;
1690                         }
1691                         else
1692                         {
1693                                 host_num++;
1694                         }
1695                 }
1696
1697 #if _POSIX_SAVED_IDS
1698                 /* Drop privileges */
1699                 status = seteuid (getuid ());
1700                 if (status != 0)
1701                 {
1702                         fprintf (stderr, "Temporarily dropping privileges "
1703                                         "failed: %s\n", strerror (errno));
1704                         exit (EXIT_FAILURE);
1705                 }
1706 #endif
1707
1708                 fclose(infile);
1709         }
1710
1711 #if _POSIX_SAVED_IDS
1712         /* Regain privileges */
1713         status = seteuid (saved_set_uid);
1714         if (status != 0)
1715         {
1716                 fprintf (stderr, "Temporarily re-gaining privileges "
1717                                 "failed: %s\n", strerror (errno));
1718                 exit (EXIT_FAILURE);
1719         }
1720 #endif
1721
1722         for (i = optind; i < argc; i++)
1723         {
1724                 if (ping_host_add (ping, argv[i]) < 0)
1725                 {
1726                         const char *errmsg = ping_get_error (ping);
1727
1728                         fprintf (stderr, "Adding host `%s' failed: %s\n", argv[i], errmsg);
1729                         continue;
1730                 }
1731                 else
1732                 {
1733                         host_num++;
1734                 }
1735         }
1736
1737         /* Permanently drop root privileges if we're setuid-root. */
1738         status = setuid (getuid ());
1739         if (status != 0)
1740         {
1741                 fprintf (stderr, "Dropping privileges failed: %s\n",
1742                                 strerror (errno));
1743                 exit (EXIT_FAILURE);
1744         }
1745
1746 #if _POSIX_SAVED_IDS
1747         saved_set_uid = (uid_t) -1;
1748 #endif
1749
1750         ping_initialize_contexts (ping);
1751
1752         if (i == 0)
1753                 return (1);
1754
1755         memset (&sigint_action, '\0', sizeof (sigint_action));
1756         sigint_action.sa_handler = sigint_handler;
1757         if (sigaction (SIGINT, &sigint_action, NULL) < 0)
1758         {
1759                 perror ("sigaction");
1760                 return (1);
1761         }
1762
1763         pre_loop_hook (ping);
1764
1765         while (opt_count != 0)
1766         {
1767                 int index;
1768                 int status;
1769
1770                 if (gettimeofday (&tv_begin, NULL) < 0)
1771                 {
1772                         perror ("gettimeofday");
1773                         return (1);
1774                 }
1775
1776                 status = ping_send (ping);
1777                 if (status == -EINTR)
1778                 {
1779                         continue;
1780                 }
1781                 else if (status < 0)
1782                 {
1783                         fprintf (stderr, "ping_send failed: %s\n",
1784                                         ping_get_error (ping));
1785                         return (1);
1786                 }
1787
1788                 index = 0;
1789                 for (iter = ping_iterator_get (ping);
1790                                 iter != NULL;
1791                                 iter = ping_iterator_next (iter))
1792                 {
1793                         update_host_hook (iter, index);
1794                         index++;
1795                 }
1796
1797                 pre_sleep_hook (ping);
1798
1799                 /* Don't sleep in the last iteration */
1800                 if (opt_count == 1)
1801                         break;
1802
1803                 if (gettimeofday (&tv_end, NULL) < 0)
1804                 {
1805                         perror ("gettimeofday");
1806                         return (1);
1807                 }
1808
1809                 time_calc (&ts_wait, &ts_int, &tv_begin, &tv_end);
1810
1811                 /* printf ("Sleeping for %i.%09li seconds\n", (int) ts_wait.tv_sec, ts_wait.tv_nsec); */
1812                 while ((status = nanosleep (&ts_wait, &ts_wait)) != 0)
1813                 {
1814                         if (errno == EINTR)
1815                         {
1816                                 continue;
1817                         }
1818                         else
1819                         {
1820                                 perror ("nanosleep");
1821                                 break;
1822                         }
1823                 }
1824
1825                 post_sleep_hook (ping);
1826
1827                 if (opt_count > 0)
1828                         opt_count--;
1829         } /* while (opt_count != 0) */
1830
1831         /* Returns the number of failed hosts according to -Z. */
1832         status = post_loop_hook (ping);
1833
1834         ping_destroy (ping);
1835
1836         if (status == 0)
1837                 exit (EXIT_SUCCESS);
1838         else
1839         {
1840                 if (status > 255)
1841                         status = 255;
1842                 exit (status);
1843         }
1844 } /* }}} int main */
1845
1846 /* vim: set fdm=marker : */