ifstat.c revision a571587d0b27a2c1492019c3abeb6fb0c0e606ce
1/*
2 * ifstat.c	handy utility to read net interface statistics
3 *
4 *		This program is free software; you can redistribute it and/or
5 *		modify it under the terms of the GNU General Public License
6 *		as published by the Free Software Foundation; either version
7 *		2 of the License, or (at your option) any later version.
8 *
9 * Authors:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
10 */
11
12#include <stdio.h>
13#include <stdlib.h>
14#include <unistd.h>
15#include <fcntl.h>
16#include <string.h>
17#include <errno.h>
18#include <time.h>
19#include <sys/time.h>
20#include <fnmatch.h>
21#include <sys/file.h>
22#include <sys/socket.h>
23#include <sys/un.h>
24#include <sys/poll.h>
25#include <sys/wait.h>
26#include <sys/stat.h>
27#include <signal.h>
28#include <math.h>
29#include <getopt.h>
30
31#include <libnetlink.h>
32#include <linux/if.h>
33#include <linux/if_link.h>
34
35#include <SNAPSHOT.h>
36
37int dump_zeros = 0;
38int reset_history = 0;
39int ignore_history = 0;
40int no_output = 0;
41int no_update = 0;
42int scan_interval = 0;
43int time_constant = 0;
44int show_errors = 0;
45double W;
46char **patterns;
47int npatterns;
48
49char info_source[128];
50int source_mismatch;
51
52#define MAXS (sizeof(struct rtnl_link_stats)/sizeof(__u32))
53
54struct ifstat_ent
55{
56	struct ifstat_ent	*next;
57	char			*name;
58	int			ifindex;
59	unsigned long long	val[MAXS];
60	double			rate[MAXS];
61	__u32			ival[MAXS];
62};
63
64struct ifstat_ent *kern_db;
65struct ifstat_ent *hist_db;
66
67static int match(const char *id)
68{
69	int i;
70
71	if (npatterns == 0)
72		return 1;
73
74	for (i=0; i<npatterns; i++) {
75		if (!fnmatch(patterns[i], id, 0))
76			return 1;
77	}
78	return 0;
79}
80
81static int get_nlmsg(const struct sockaddr_nl *who,
82		     struct nlmsghdr *m, void *arg)
83{
84	struct ifinfomsg *ifi = NLMSG_DATA(m);
85	struct rtattr * tb[IFLA_MAX+1];
86	int len = m->nlmsg_len;
87	struct ifstat_ent *n;
88	int i;
89
90	if (m->nlmsg_type != RTM_NEWLINK)
91		return 0;
92
93	len -= NLMSG_LENGTH(sizeof(*ifi));
94	if (len < 0)
95		return -1;
96
97	if (!(ifi->ifi_flags&IFF_UP))
98		return 0;
99
100	parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len);
101	if (tb[IFLA_IFNAME] == NULL || tb[IFLA_STATS] == NULL)
102		return 0;
103
104	n = malloc(sizeof(*n));
105	if (!n)
106		abort();
107	n->ifindex = ifi->ifi_index;
108	n->name = strdup(RTA_DATA(tb[IFLA_IFNAME]));
109	memcpy(&n->ival, RTA_DATA(tb[IFLA_STATS]), sizeof(n->ival));
110	memset(&n->rate, 0, sizeof(n->rate));
111	for (i=0; i<MAXS; i++)
112		n->val[i] = n->ival[i];
113	n->next = kern_db;
114	kern_db = n;
115	return 0;
116}
117
118void load_info(void)
119{
120	struct ifstat_ent *db, *n;
121	struct rtnl_handle rth;
122
123	if (rtnl_open(&rth, 0) < 0)
124		exit(1);
125
126	if (rtnl_wilddump_request(&rth, AF_INET, RTM_GETLINK) < 0) {
127		perror("Cannot send dump request");
128		exit(1);
129	}
130
131	if (rtnl_dump_filter(&rth, get_nlmsg, NULL, NULL, NULL) < 0) {
132		fprintf(stderr, "Dump terminated\n");
133		exit(1);
134	}
135
136	rtnl_close(&rth);
137
138	db = kern_db;
139	kern_db = NULL;
140
141	while (db) {
142		n = db;
143		db = db->next;
144		n->next = kern_db;
145		kern_db = n;
146	}
147}
148
149void load_raw_table(FILE *fp)
150{
151	char buf[4096];
152	struct ifstat_ent *db = NULL;
153	struct ifstat_ent *n;
154
155	while (fgets(buf, sizeof(buf), fp) != NULL) {
156		char *p;
157		char *next;
158		int i;
159
160		if (buf[0] == '#') {
161			buf[strlen(buf)-1] = 0;
162			if (info_source[0] && strcmp(info_source, buf+1))
163				source_mismatch = 1;
164			strncpy(info_source, buf+1, sizeof(info_source)-1);
165			continue;
166		}
167		if ((n = malloc(sizeof(*n))) == NULL)
168			abort();
169
170		if (!(p = strchr(buf, ' ')))
171			abort();
172		*p++ = 0;
173
174		if (sscanf(buf, "%d", &n->ifindex) != 1)
175			abort();
176		if (!(next = strchr(p, ' ')))
177			abort();
178		*next++ = 0;
179
180		n->name = strdup(p);
181		p = next;
182
183		for (i=0; i<MAXS; i++) {
184			unsigned rate;
185			if (!(next = strchr(p, ' ')))
186				abort();
187			*next++ = 0;
188			if (sscanf(p, "%llu", n->val+i) != 1)
189				abort();
190			n->ival[i] = (__u32)n->val[i];
191			p = next;
192			if (!(next = strchr(p, ' ')))
193				abort();
194			*next++ = 0;
195			if (sscanf(p, "%u", &rate) != 1)
196				abort();
197			n->rate[i] = rate;
198			p = next;
199		}
200		n->next = db;
201		db = n;
202	}
203
204	while (db) {
205		n = db;
206		db = db->next;
207		n->next = kern_db;
208		kern_db = n;
209	}
210}
211
212void dump_raw_db(FILE *fp, int to_hist)
213{
214	struct ifstat_ent *n, *h;
215	h = hist_db;
216	fprintf(fp, "#%s\n", info_source);
217
218	for (n=kern_db; n; n=n->next) {
219		int i;
220		unsigned long long *vals = n->val;
221		double *rates = n->rate;
222		if (!match(n->name)) {
223			struct ifstat_ent *h1;
224			if (!to_hist)
225				continue;
226			for (h1 = h; h1; h1 = h1->next) {
227				if (h1->ifindex == n->ifindex) {
228					vals = h1->val;
229					rates = h1->rate;
230					h = h1->next;
231					break;
232				}
233			}
234		}
235		fprintf(fp, "%d %s ", n->ifindex, n->name);
236		for (i=0; i<MAXS; i++)
237			fprintf(fp, "%llu %u ", vals[i], (unsigned)rates[i]);
238		fprintf(fp, "\n");
239	}
240}
241
242/* use communication definitions of meg/kilo etc */
243static const unsigned long long giga = 1000000000ull;
244static const unsigned long long mega = 1000000;
245static const unsigned long long kilo = 1000;
246
247void format_rate(FILE *fp, unsigned long long *vals, double *rates, int i)
248{
249	char temp[64];
250	if (vals[i] > giga)
251		fprintf(fp, "%7lluM ", vals[i]/mega);
252	else if (vals[i] > mega)
253		fprintf(fp, "%7lluK ", vals[i]/kilo);
254	else
255		fprintf(fp, "%8llu ", vals[i]);
256
257	if (rates[i] > mega) {
258		sprintf(temp, "%uM", (unsigned)(rates[i]/mega));
259		fprintf(fp, "%-6s ", temp);
260	} else if (rates[i] > kilo) {
261		sprintf(temp, "%uK", (unsigned)(rates[i]/kilo));
262		fprintf(fp, "%-6s ", temp);
263	} else
264		fprintf(fp, "%-6u ", (unsigned)rates[i]);
265}
266
267void format_pair(FILE *fp, unsigned long long *vals, int i, int k)
268{
269	char temp[64];
270	if (vals[i] > giga)
271		fprintf(fp, "%7lluM ", vals[i]/mega);
272	else if (vals[i] > mega)
273		fprintf(fp, "%7lluK ", vals[i]/kilo);
274	else
275		fprintf(fp, "%8llu ", vals[i]);
276
277	if (vals[k] > giga) {
278		sprintf(temp, "%uM", (unsigned)(vals[k]/mega));
279		fprintf(fp, "%-6s ", temp);
280	} else if (vals[k] > mega) {
281		sprintf(temp, "%uK", (unsigned)(vals[k]/kilo));
282		fprintf(fp, "%-6s ", temp);
283	} else
284		fprintf(fp, "%-6u ", (unsigned)vals[k]);
285}
286
287void print_head(FILE *fp)
288{
289	fprintf(fp, "#%s\n", info_source);
290	fprintf(fp, "%-15s ", "Interface");
291
292	fprintf(fp, "%8s/%-6s ", "RX Pkts", "Rate");
293	fprintf(fp, "%8s/%-6s ", "TX Pkts", "Rate");
294	fprintf(fp, "%8s/%-6s ", "RX Data", "Rate");
295	fprintf(fp, "%8s/%-6s\n","TX Data", "Rate");
296
297	if (!show_errors) {
298		fprintf(fp, "%-15s ", "");
299		fprintf(fp, "%8s/%-6s ", "RX Errs", "Drop");
300		fprintf(fp, "%8s/%-6s ", "TX Errs", "Drop");
301		fprintf(fp, "%8s/%-6s ", "RX Over", "Rate");
302		fprintf(fp, "%8s/%-6s\n","TX Coll", "Rate");
303	} else {
304		fprintf(fp, "%-15s ", "");
305		fprintf(fp, "%8s/%-6s ", "RX Errs", "Rate");
306		fprintf(fp, "%8s/%-6s ", "RX Drop", "Rate");
307		fprintf(fp, "%8s/%-6s ", "RX Over", "Rate");
308		fprintf(fp, "%8s/%-6s\n","RX Leng", "Rate");
309
310		fprintf(fp, "%-15s ", "");
311		fprintf(fp, "%8s/%-6s ", "RX Crc", "Rate");
312		fprintf(fp, "%8s/%-6s ", "RX Frm", "Rate");
313		fprintf(fp, "%8s/%-6s ", "RX Fifo", "Rate");
314		fprintf(fp, "%8s/%-6s\n","RX Miss", "Rate");
315
316		fprintf(fp, "%-15s ", "");
317		fprintf(fp, "%8s/%-6s ", "TX Errs", "Rate");
318		fprintf(fp, "%8s/%-6s ", "TX Drop", "Rate");
319		fprintf(fp, "%8s/%-6s ", "TX Coll", "Rate");
320		fprintf(fp, "%8s/%-6s\n","TX Carr", "Rate");
321
322		fprintf(fp, "%-15s ", "");
323		fprintf(fp, "%8s/%-6s ", "TX Abrt", "Rate");
324		fprintf(fp, "%8s/%-6s ", "TX Fifo", "Rate");
325		fprintf(fp, "%8s/%-6s ", "TX Hear", "Rate");
326		fprintf(fp, "%8s/%-6s\n","TX Wind", "Rate");
327	}
328}
329
330void print_one_if(FILE *fp, struct ifstat_ent *n, unsigned long long *vals)
331{
332	int i;
333	fprintf(fp, "%-15s ", n->name);
334	for (i=0; i<4; i++)
335		format_rate(fp, vals, n->rate, i);
336	fprintf(fp, "\n");
337
338	if (!show_errors) {
339		fprintf(fp, "%-15s ", "");
340		format_pair(fp, vals, 4, 6);
341		format_pair(fp, vals, 5, 7);
342		format_rate(fp, vals, n->rate, 11);
343		format_rate(fp, vals, n->rate, 9);
344		fprintf(fp, "\n");
345	} else {
346		fprintf(fp, "%-15s ", "");
347		format_rate(fp, vals, n->rate, 4);
348		format_rate(fp, vals, n->rate, 6);
349		format_rate(fp, vals, n->rate, 11);
350		format_rate(fp, vals, n->rate, 10);
351		fprintf(fp, "\n");
352
353		fprintf(fp, "%-15s ", "");
354		format_rate(fp, vals, n->rate, 12);
355		format_rate(fp, vals, n->rate, 13);
356		format_rate(fp, vals, n->rate, 14);
357		format_rate(fp, vals, n->rate, 15);
358		fprintf(fp, "\n");
359
360		fprintf(fp, "%-15s ", "");
361		format_rate(fp, vals, n->rate, 5);
362		format_rate(fp, vals, n->rate, 7);
363		format_rate(fp, vals, n->rate, 9);
364		format_rate(fp, vals, n->rate, 17);
365		fprintf(fp, "\n");
366
367		fprintf(fp, "%-15s ", "");
368		format_rate(fp, vals, n->rate, 16);
369		format_rate(fp, vals, n->rate, 18);
370		format_rate(fp, vals, n->rate, 19);
371		format_rate(fp, vals, n->rate, 20);
372		fprintf(fp, "\n");
373	}
374}
375
376
377void dump_kern_db(FILE *fp)
378{
379	struct ifstat_ent *n, *h;
380	h = hist_db;
381
382	print_head(fp);
383
384	for (n=kern_db; n; n=n->next) {
385		if (!match(n->name))
386			continue;
387		print_one_if(fp, n, n->val);
388	}
389}
390
391
392void dump_incr_db(FILE *fp)
393{
394	struct ifstat_ent *n, *h;
395	h = hist_db;
396
397	print_head(fp);
398
399	for (n=kern_db; n; n=n->next) {
400		int i;
401		unsigned long long vals[MAXS];
402		struct ifstat_ent *h1;
403
404		memcpy(vals, n->val, sizeof(vals));
405
406		for (h1 = h; h1; h1 = h1->next) {
407			if (h1->ifindex == n->ifindex) {
408				for (i = 0; i < MAXS; i++)
409					vals[i] -= h1->val[i];
410				h = h1->next;
411				break;
412			}
413		}
414		if (!match(n->name))
415			continue;
416		print_one_if(fp, n, vals);
417	}
418}
419
420
421static int children;
422
423void sigchild(int signo)
424{
425}
426
427void update_db(int interval)
428{
429	struct ifstat_ent *n, *h;
430
431	n = kern_db;
432	kern_db = NULL;
433
434	load_info();
435
436	h = kern_db;
437	kern_db = n;
438
439	for (n = kern_db; n; n = n->next) {
440		struct ifstat_ent *h1;
441		for (h1 = h; h1; h1 = h1->next) {
442			if (h1->ifindex == n->ifindex) {
443				int i;
444				for (i = 0; i < MAXS; i++) {
445					if ((long)(h1->ival[i] - n->ival[i]) < 0) {
446						memset(n->ival, 0, sizeof(n->ival));
447						break;
448					}
449				}
450				for (i = 0; i < MAXS; i++) {
451					double sample;
452					unsigned long incr = h1->ival[i] - n->ival[i];
453					n->val[i] += incr;
454					n->ival[i] = h1->ival[i];
455					sample = (double)(incr*1000)/interval;
456					if (interval >= scan_interval) {
457						n->rate[i] += W*(sample-n->rate[i]);
458					} else if (interval >= 1000) {
459						if (interval >= time_constant) {
460							n->rate[i] = sample;
461						} else {
462							double w = W*(double)interval/scan_interval;
463							n->rate[i] += w*(sample-n->rate[i]);
464						}
465					}
466				}
467
468				while (h != h1) {
469					struct ifstat_ent *tmp = h;
470					h = h->next;
471					free(tmp->name);
472					free(tmp);
473				};
474				h = h1->next;
475				free(h1->name);
476				free(h1);
477				break;
478			}
479		}
480	}
481}
482
483#define T_DIFF(a,b) (((a).tv_sec-(b).tv_sec)*1000 + ((a).tv_usec-(b).tv_usec)/1000)
484
485
486void server_loop(int fd)
487{
488	struct timeval snaptime = { 0 };
489	struct pollfd p;
490	p.fd = fd;
491	p.events = p.revents = POLLIN;
492
493	sprintf(info_source, "%d.%lu sampling_interval=%d time_const=%d",
494		getpid(), (unsigned long)random(), scan_interval/1000, time_constant/1000);
495
496	load_info();
497
498	for (;;) {
499		int status;
500		int tdiff;
501		struct timeval now;
502
503		gettimeofday(&now, NULL);
504		tdiff = T_DIFF(now, snaptime);
505		if (tdiff >= scan_interval) {
506			update_db(tdiff);
507			snaptime = now;
508			tdiff = 0;
509		}
510
511		if (poll(&p, 1, tdiff + scan_interval) > 0
512		    && (p.revents&POLLIN)) {
513			int clnt = accept(fd, NULL, NULL);
514			if (clnt >= 0) {
515				pid_t pid;
516				if (children >= 5) {
517					close(clnt);
518				} else if ((pid = fork()) != 0) {
519					if (pid>0)
520						children++;
521					close(clnt);
522				} else {
523					FILE *fp = fdopen(clnt, "w");
524					if (fp) {
525						if (tdiff > 0)
526							update_db(tdiff);
527						dump_raw_db(fp, 0);
528					}
529					exit(0);
530				}
531			}
532		}
533		while (children && waitpid(-1, &status, WNOHANG) > 0)
534			children--;
535	}
536}
537
538int verify_forging(int fd)
539{
540	struct ucred cred;
541	socklen_t olen = sizeof(cred);
542
543	if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, (void*)&cred, &olen) ||
544	    olen < sizeof(cred))
545		return -1;
546	if (cred.uid == getuid() || cred.uid == 0)
547		return 0;
548	return -1;
549}
550
551static void usage(void) __attribute__((noreturn));
552
553static void usage(void)
554{
555	fprintf(stderr,
556"Usage: ifstat [OPTION] [ PATTERN [ PATTERN ] ]\n"
557"   -h, --help		this message\n"
558"   -a, --ignore	ignore history\n"
559"   -d, --scan=SECS	sample every statistics every SECS\n"
560"   -e, --errors	show errors\n"
561"   -n, --nooutput	do history only\n"
562"   -r, --reset		reset history\n"
563"   -s, --noupdate	don;t update history\n"
564"   -t, --interval=SECS	report average over the last SECS\n"
565"   -V, --version	output version information\n"
566"   -z, --zeros		show entries with zero activity\n");
567
568	exit(-1);
569}
570
571static const struct option longopts[] = {
572	{ "help", 0, 0, 'h' },
573	{ "ignore",  0,  0, 'a' },
574	{ "scan", 1, 0, 'd'},
575	{ "errors", 0, 0, 'e' },
576	{ "nooutput", 0, 0, 'n' },
577	{ "reset", 0, 0, 'r' },
578	{ "noupdate", 0, 0, 's' },
579	{ "interval", 1, 0, 't' },
580	{ "version", 0, 0, 'V' },
581	{ "zeros", 0, 0, 'z' },
582	{ 0 }
583};
584
585int main(int argc, char *argv[])
586{
587	char hist_name[128];
588	struct sockaddr_un sun;
589	FILE *hist_fp = NULL;
590	int ch;
591	int fd;
592
593	while ((ch = getopt_long(argc, argv, "hvVzrnasd:t:eK",
594			longopts, NULL)) != EOF) {
595		switch(ch) {
596		case 'z':
597			dump_zeros = 1;
598			break;
599		case 'r':
600			reset_history = 1;
601			break;
602		case 'a':
603			ignore_history = 1;
604			break;
605		case 's':
606			no_update = 1;
607			break;
608		case 'n':
609			no_output = 1;
610			break;
611		case 'e':
612			show_errors = 1;
613			break;
614		case 'd':
615			scan_interval = atoi(optarg) * 1000;
616			if (scan_interval <= 0) {
617				fprintf(stderr, "ifstat: invalid scan interval\n");
618				exit(-1);
619			}
620			break;
621		case 't':
622			time_constant = atoi(optarg);
623			if (time_constant <= 0) {
624				fprintf(stderr, "ifstat: invalid time constant divisor\n");
625				exit(-1);
626			}
627			break;
628		case 'v':
629		case 'V':
630			printf("ifstat utility, iproute2-ss%s\n", SNAPSHOT);
631			exit(0);
632		case 'h':
633		case '?':
634		default:
635			usage();
636		}
637	}
638
639	argc -= optind;
640	argv += optind;
641
642	sun.sun_family = AF_UNIX;
643	sun.sun_path[0] = 0;
644	sprintf(sun.sun_path+1, "ifstat%d", getuid());
645
646	if (scan_interval > 0) {
647		if (time_constant == 0)
648			time_constant = 60;
649		time_constant *= 1000;
650		W = 1 - 1/exp(log(10)*(double)scan_interval/time_constant);
651		if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
652			perror("ifstat: socket");
653			exit(-1);
654		}
655		if (bind(fd, (struct sockaddr*)&sun, 2+1+strlen(sun.sun_path+1)) < 0) {
656			perror("ifstat: bind");
657			exit(-1);
658		}
659		if (listen(fd, 5) < 0) {
660			perror("ifstat: listen");
661			exit(-1);
662		}
663		if (daemon(0, 0)) {
664			perror("ifstat: daemon");
665			exit(-1);
666		}
667		signal(SIGPIPE, SIG_IGN);
668		signal(SIGCHLD, sigchild);
669		server_loop(fd);
670		exit(0);
671	}
672
673	patterns = argv;
674	npatterns = argc;
675
676	if (getenv("IFSTAT_HISTORY"))
677		snprintf(hist_name, sizeof(hist_name),
678			 "%s", getenv("IFSTAT_HISTORY"));
679	else
680		snprintf(hist_name, sizeof(hist_name),
681			 "%s/.ifstat.u%d", P_tmpdir, getuid());
682
683	if (reset_history)
684		unlink(hist_name);
685
686	if (!ignore_history || !no_update) {
687		struct stat stb;
688
689		fd = open(hist_name, O_RDWR|O_CREAT|O_NOFOLLOW, 0600);
690		if (fd < 0) {
691			perror("ifstat: open history file");
692			exit(-1);
693		}
694		if ((hist_fp = fdopen(fd, "r+")) == NULL) {
695			perror("ifstat: fdopen history file");
696			exit(-1);
697		}
698		if (flock(fileno(hist_fp), LOCK_EX)) {
699			perror("ifstat: flock history file");
700			exit(-1);
701		}
702		if (fstat(fileno(hist_fp), &stb) != 0) {
703			perror("ifstat: fstat history file");
704			exit(-1);
705		}
706		if (stb.st_nlink != 1 || stb.st_uid != getuid()) {
707			fprintf(stderr, "ifstat: something is so wrong with history file, that I prefer not to proceed.\n");
708			exit(-1);
709		}
710		if (!ignore_history) {
711			FILE *tfp;
712			long uptime;
713			if ((tfp = fopen("/proc/uptime", "r")) != NULL) {
714				if (fscanf(tfp, "%ld", &uptime) != 1)
715					uptime = -1;
716				fclose(tfp);
717			}
718			if (uptime >= 0 && time(NULL) >= stb.st_mtime+uptime) {
719				fprintf(stderr, "ifstat: history is aged out, resetting\n");
720				ftruncate(fileno(hist_fp), 0);
721			}
722		}
723
724		load_raw_table(hist_fp);
725
726		hist_db = kern_db;
727		kern_db = NULL;
728	}
729
730	if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) >= 0 &&
731	    (connect(fd, (struct sockaddr*)&sun, 2+1+strlen(sun.sun_path+1)) == 0
732	     || (strcpy(sun.sun_path+1, "ifstat0"),
733		 connect(fd, (struct sockaddr*)&sun, 2+1+strlen(sun.sun_path+1)) == 0))
734	    && verify_forging(fd) == 0) {
735		FILE *sfp = fdopen(fd, "r");
736		load_raw_table(sfp);
737		if (hist_db && source_mismatch) {
738			fprintf(stderr, "ifstat: history is stale, ignoring it.\n");
739			hist_db = NULL;
740		}
741		fclose(sfp);
742	} else {
743		if (fd >= 0)
744			close(fd);
745		if (hist_db && info_source[0] && strcmp(info_source, "kernel")) {
746			fprintf(stderr, "ifstat: history is stale, ignoring it.\n");
747			hist_db = NULL;
748			info_source[0] = 0;
749		}
750		load_info();
751		if (info_source[0] == 0)
752			strcpy(info_source, "kernel");
753	}
754
755	if (!no_output) {
756		if (ignore_history || hist_db == NULL)
757			dump_kern_db(stdout);
758		else
759			dump_incr_db(stdout);
760	}
761	if (!no_update) {
762		ftruncate(fileno(hist_fp), 0);
763		rewind(hist_fp);
764		dump_raw_db(hist_fp, 1);
765		fflush(hist_fp);
766	}
767	exit(0);
768}
769