mirror of
https://git.proxmox.com/git/mirror_iproute2
synced 2025-10-05 03:59:55 +00:00

The code here was doing strncpy() in a way that causes gcc 10 warning about possible string overflow. Just use strlcpy() which will null terminate and bound the string as expected. This has existed since start of git era so no Fixes tag. Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
775 lines
15 KiB
C
775 lines
15 KiB
C
/*
|
|
* nstat.c handy utility to read counters /proc/net/netstat and snmp
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version
|
|
* 2 of the License, or (at your option) any later version.
|
|
*
|
|
* Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <time.h>
|
|
#include <sys/time.h>
|
|
#include <fnmatch.h>
|
|
#include <sys/file.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/un.h>
|
|
#include <sys/poll.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/stat.h>
|
|
#include <signal.h>
|
|
#include <math.h>
|
|
#include <getopt.h>
|
|
|
|
#include <json_writer.h>
|
|
#include "version.h"
|
|
#include "utils.h"
|
|
|
|
int dump_zeros;
|
|
int reset_history;
|
|
int ignore_history;
|
|
int no_output;
|
|
int json_output;
|
|
int no_update;
|
|
int scan_interval;
|
|
int time_constant;
|
|
double W;
|
|
char **patterns;
|
|
int npatterns;
|
|
|
|
char info_source[128];
|
|
int source_mismatch;
|
|
|
|
static int generic_proc_open(const char *env, char *name)
|
|
{
|
|
char store[128];
|
|
char *p = getenv(env);
|
|
|
|
if (!p) {
|
|
p = getenv("PROC_ROOT") ? : "/proc";
|
|
snprintf(store, sizeof(store)-1, "%s/%s", p, name);
|
|
p = store;
|
|
}
|
|
return open(p, O_RDONLY);
|
|
}
|
|
|
|
static int net_netstat_open(void)
|
|
{
|
|
return generic_proc_open("PROC_NET_NETSTAT", "net/netstat");
|
|
}
|
|
|
|
static int net_snmp_open(void)
|
|
{
|
|
return generic_proc_open("PROC_NET_SNMP", "net/snmp");
|
|
}
|
|
|
|
static int net_snmp6_open(void)
|
|
{
|
|
return generic_proc_open("PROC_NET_SNMP6", "net/snmp6");
|
|
}
|
|
|
|
static int net_sctp_snmp_open(void)
|
|
{
|
|
return generic_proc_open("PROC_NET_SCTP_SNMP", "net/sctp/snmp");
|
|
}
|
|
|
|
struct nstat_ent {
|
|
struct nstat_ent *next;
|
|
char *id;
|
|
unsigned long long val;
|
|
double rate;
|
|
};
|
|
|
|
struct nstat_ent *kern_db;
|
|
struct nstat_ent *hist_db;
|
|
|
|
static const char *useless_numbers[] = {
|
|
"IpForwarding", "IpDefaultTTL",
|
|
"TcpRtoAlgorithm", "TcpRtoMin", "TcpRtoMax",
|
|
"TcpMaxConn", "TcpCurrEstab"
|
|
};
|
|
|
|
static int useless_number(const char *id)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(useless_numbers); i++)
|
|
if (strcmp(id, useless_numbers[i]) == 0)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static int match(const char *id)
|
|
{
|
|
int i;
|
|
|
|
if (npatterns == 0)
|
|
return 1;
|
|
|
|
for (i = 0; i < npatterns; i++) {
|
|
if (!fnmatch(patterns[i], id, FNM_CASEFOLD))
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void load_good_table(FILE *fp)
|
|
{
|
|
char buf[4096];
|
|
struct nstat_ent *db = NULL;
|
|
struct nstat_ent *n;
|
|
|
|
while (fgets(buf, sizeof(buf), fp) != NULL) {
|
|
int nr;
|
|
unsigned long long val;
|
|
double rate;
|
|
char idbuf[sizeof(buf)];
|
|
|
|
if (buf[0] == '#') {
|
|
buf[strlen(buf)-1] = 0;
|
|
if (info_source[0] && strcmp(info_source, buf+1))
|
|
source_mismatch = 1;
|
|
strlcpy(info_source, buf + 1, sizeof(info_source));
|
|
continue;
|
|
}
|
|
/* idbuf is as big as buf, so this is safe */
|
|
nr = sscanf(buf, "%s%llu%lg", idbuf, &val, &rate);
|
|
if (nr < 2) {
|
|
fprintf(stderr, "%s:%d: error parsing history file\n",
|
|
__FILE__, __LINE__);
|
|
exit(-2);
|
|
}
|
|
if (nr < 3)
|
|
rate = 0;
|
|
if (useless_number(idbuf))
|
|
continue;
|
|
if ((n = malloc(sizeof(*n))) == NULL) {
|
|
perror("nstat: malloc");
|
|
exit(-1);
|
|
}
|
|
n->id = strdup(idbuf);
|
|
n->val = val;
|
|
n->rate = rate;
|
|
n->next = db;
|
|
db = n;
|
|
}
|
|
|
|
while (db) {
|
|
n = db;
|
|
db = db->next;
|
|
n->next = kern_db;
|
|
kern_db = n;
|
|
}
|
|
}
|
|
|
|
static int count_spaces(const char *line)
|
|
{
|
|
int count = 0;
|
|
char c;
|
|
|
|
while ((c = *line++) != 0)
|
|
count += c == ' ' || c == '\n';
|
|
return count;
|
|
}
|
|
|
|
static void load_ugly_table(FILE *fp)
|
|
{
|
|
char *buf = NULL;
|
|
size_t buflen = 0;
|
|
ssize_t nread;
|
|
struct nstat_ent *db = NULL;
|
|
struct nstat_ent *n;
|
|
|
|
while ((nread = getline(&buf, &buflen, fp)) != -1) {
|
|
char idbuf[4096];
|
|
int off;
|
|
char *p;
|
|
int count1, count2, skip = 0;
|
|
|
|
p = strchr(buf, ':');
|
|
if (!p) {
|
|
fprintf(stderr, "%s:%d: error parsing history file\n",
|
|
__FILE__, __LINE__);
|
|
exit(-2);
|
|
}
|
|
count1 = count_spaces(buf);
|
|
*p = 0;
|
|
idbuf[0] = 0;
|
|
strncat(idbuf, buf, sizeof(idbuf) - 1);
|
|
off = p - buf;
|
|
p += 2;
|
|
|
|
while (*p) {
|
|
char *next;
|
|
|
|
if ((next = strchr(p, ' ')) != NULL)
|
|
*next++ = 0;
|
|
else if ((next = strchr(p, '\n')) != NULL)
|
|
*next++ = 0;
|
|
if (off < sizeof(idbuf)) {
|
|
idbuf[off] = 0;
|
|
strncat(idbuf, p, sizeof(idbuf) - off - 1);
|
|
}
|
|
n = malloc(sizeof(*n));
|
|
if (!n) {
|
|
perror("nstat: malloc");
|
|
exit(-1);
|
|
}
|
|
n->id = strdup(idbuf);
|
|
n->rate = 0;
|
|
n->next = db;
|
|
db = n;
|
|
p = next;
|
|
}
|
|
n = db;
|
|
nread = getline(&buf, &buflen, fp);
|
|
if (nread == -1) {
|
|
fprintf(stderr, "%s:%d: error parsing history file\n",
|
|
__FILE__, __LINE__);
|
|
exit(-2);
|
|
}
|
|
count2 = count_spaces(buf);
|
|
if (count2 > count1)
|
|
skip = count2 - count1;
|
|
do {
|
|
p = strrchr(buf, ' ');
|
|
if (!p) {
|
|
fprintf(stderr, "%s:%d: error parsing history file\n",
|
|
__FILE__, __LINE__);
|
|
exit(-2);
|
|
}
|
|
*p = 0;
|
|
if (sscanf(p+1, "%llu", &n->val) != 1) {
|
|
fprintf(stderr, "%s:%d: error parsing history file\n",
|
|
__FILE__, __LINE__);
|
|
exit(-2);
|
|
}
|
|
/* Trick to skip "dummy" trailing ICMP MIB in 2.4 */
|
|
if (skip)
|
|
skip--;
|
|
else
|
|
n = n->next;
|
|
} while (p > buf + off + 2);
|
|
}
|
|
free(buf);
|
|
|
|
while (db) {
|
|
n = db;
|
|
db = db->next;
|
|
if (useless_number(n->id)) {
|
|
free(n->id);
|
|
free(n);
|
|
} else {
|
|
n->next = kern_db;
|
|
kern_db = n;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void load_sctp_snmp(void)
|
|
{
|
|
FILE *fp = fdopen(net_sctp_snmp_open(), "r");
|
|
|
|
if (fp) {
|
|
load_good_table(fp);
|
|
fclose(fp);
|
|
}
|
|
}
|
|
|
|
static void load_snmp(void)
|
|
{
|
|
FILE *fp = fdopen(net_snmp_open(), "r");
|
|
|
|
if (fp) {
|
|
load_ugly_table(fp);
|
|
fclose(fp);
|
|
}
|
|
}
|
|
|
|
static void load_snmp6(void)
|
|
{
|
|
FILE *fp = fdopen(net_snmp6_open(), "r");
|
|
|
|
if (fp) {
|
|
load_good_table(fp);
|
|
fclose(fp);
|
|
}
|
|
}
|
|
|
|
static void load_netstat(void)
|
|
{
|
|
FILE *fp = fdopen(net_netstat_open(), "r");
|
|
|
|
if (fp) {
|
|
load_ugly_table(fp);
|
|
fclose(fp);
|
|
}
|
|
}
|
|
|
|
|
|
static void dump_kern_db(FILE *fp, int to_hist)
|
|
{
|
|
json_writer_t *jw = json_output ? jsonw_new(fp) : NULL;
|
|
struct nstat_ent *n, *h;
|
|
|
|
h = hist_db;
|
|
if (jw) {
|
|
jsonw_start_object(jw);
|
|
jsonw_pretty(jw, pretty);
|
|
jsonw_name(jw, info_source);
|
|
jsonw_start_object(jw);
|
|
} else
|
|
fprintf(fp, "#%s\n", info_source);
|
|
|
|
for (n = kern_db; n; n = n->next) {
|
|
unsigned long long val = n->val;
|
|
|
|
if (!dump_zeros && !val && !n->rate)
|
|
continue;
|
|
if (!match(n->id)) {
|
|
struct nstat_ent *h1;
|
|
|
|
if (!to_hist)
|
|
continue;
|
|
for (h1 = h; h1; h1 = h1->next) {
|
|
if (strcmp(h1->id, n->id) == 0) {
|
|
val = h1->val;
|
|
h = h1->next;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (jw)
|
|
jsonw_uint_field(jw, n->id, val);
|
|
else
|
|
fprintf(fp, "%-32s%-16llu%6.1f\n", n->id, val, n->rate);
|
|
}
|
|
|
|
if (jw) {
|
|
jsonw_end_object(jw);
|
|
|
|
jsonw_end_object(jw);
|
|
jsonw_destroy(&jw);
|
|
}
|
|
}
|
|
|
|
static void dump_incr_db(FILE *fp)
|
|
{
|
|
json_writer_t *jw = json_output ? jsonw_new(fp) : NULL;
|
|
struct nstat_ent *n, *h;
|
|
|
|
h = hist_db;
|
|
if (jw) {
|
|
jsonw_start_object(jw);
|
|
jsonw_pretty(jw, pretty);
|
|
jsonw_name(jw, info_source);
|
|
jsonw_start_object(jw);
|
|
} else
|
|
fprintf(fp, "#%s\n", info_source);
|
|
|
|
for (n = kern_db; n; n = n->next) {
|
|
int ovfl = 0;
|
|
unsigned long long val = n->val;
|
|
struct nstat_ent *h1;
|
|
|
|
for (h1 = h; h1; h1 = h1->next) {
|
|
if (strcmp(h1->id, n->id) == 0) {
|
|
if (val < h1->val) {
|
|
ovfl = 1;
|
|
val = h1->val;
|
|
}
|
|
val -= h1->val;
|
|
h = h1->next;
|
|
break;
|
|
}
|
|
}
|
|
if (!dump_zeros && !val && !n->rate)
|
|
continue;
|
|
if (!match(n->id))
|
|
continue;
|
|
|
|
if (jw)
|
|
jsonw_uint_field(jw, n->id, val);
|
|
else
|
|
fprintf(fp, "%-32s%-16llu%6.1f%s\n", n->id, val,
|
|
n->rate, ovfl?" (overflow)":"");
|
|
}
|
|
|
|
if (jw) {
|
|
jsonw_end_object(jw);
|
|
|
|
jsonw_end_object(jw);
|
|
jsonw_destroy(&jw);
|
|
}
|
|
}
|
|
|
|
static int children;
|
|
|
|
static void sigchild(int signo)
|
|
{
|
|
}
|
|
|
|
static void update_db(int interval)
|
|
{
|
|
struct nstat_ent *n, *h;
|
|
|
|
n = kern_db;
|
|
kern_db = NULL;
|
|
|
|
load_netstat();
|
|
load_snmp6();
|
|
load_snmp();
|
|
load_sctp_snmp();
|
|
|
|
h = kern_db;
|
|
kern_db = n;
|
|
|
|
for (n = kern_db; n; n = n->next) {
|
|
struct nstat_ent *h1;
|
|
|
|
for (h1 = h; h1; h1 = h1->next) {
|
|
if (strcmp(h1->id, n->id) == 0) {
|
|
double sample;
|
|
unsigned long long incr = h1->val - n->val;
|
|
|
|
n->val = h1->val;
|
|
sample = (double)incr * 1000.0 / interval;
|
|
if (interval >= scan_interval) {
|
|
n->rate += W*(sample-n->rate);
|
|
} else if (interval >= 1000) {
|
|
if (interval >= time_constant) {
|
|
n->rate = sample;
|
|
} else {
|
|
double w = W*(double)interval/scan_interval;
|
|
|
|
n->rate += w*(sample-n->rate);
|
|
}
|
|
}
|
|
|
|
while (h != h1) {
|
|
struct nstat_ent *tmp = h;
|
|
|
|
h = h->next;
|
|
free(tmp->id);
|
|
free(tmp);
|
|
};
|
|
h = h1->next;
|
|
free(h1->id);
|
|
free(h1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#define T_DIFF(a, b) (((a).tv_sec-(b).tv_sec)*1000 + ((a).tv_usec-(b).tv_usec)/1000)
|
|
|
|
|
|
static void server_loop(int fd)
|
|
{
|
|
struct timeval snaptime = { 0 };
|
|
struct pollfd p;
|
|
|
|
p.fd = fd;
|
|
p.events = p.revents = POLLIN;
|
|
|
|
sprintf(info_source, "%d.%lu sampling_interval=%d time_const=%d",
|
|
getpid(), (unsigned long)random(), scan_interval/1000, time_constant/1000);
|
|
|
|
load_netstat();
|
|
load_snmp6();
|
|
load_snmp();
|
|
load_sctp_snmp();
|
|
|
|
for (;;) {
|
|
int status;
|
|
time_t tdiff;
|
|
struct timeval now;
|
|
|
|
gettimeofday(&now, NULL);
|
|
tdiff = T_DIFF(now, snaptime);
|
|
if (tdiff >= scan_interval) {
|
|
update_db(tdiff);
|
|
snaptime = now;
|
|
tdiff = 0;
|
|
}
|
|
if (poll(&p, 1, scan_interval - tdiff) > 0
|
|
&& (p.revents&POLLIN)) {
|
|
int clnt = accept(fd, NULL, NULL);
|
|
|
|
if (clnt >= 0) {
|
|
pid_t pid;
|
|
|
|
if (children >= 5) {
|
|
close(clnt);
|
|
} else if ((pid = fork()) != 0) {
|
|
if (pid > 0)
|
|
children++;
|
|
close(clnt);
|
|
} else {
|
|
FILE *fp = fdopen(clnt, "w");
|
|
|
|
if (fp)
|
|
dump_kern_db(fp, 0);
|
|
exit(0);
|
|
}
|
|
}
|
|
}
|
|
while (children && waitpid(-1, &status, WNOHANG) > 0)
|
|
children--;
|
|
}
|
|
}
|
|
|
|
static int verify_forging(int fd)
|
|
{
|
|
struct ucred cred;
|
|
socklen_t olen = sizeof(cred);
|
|
|
|
if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, (void *)&cred, &olen) ||
|
|
olen < sizeof(cred))
|
|
return -1;
|
|
if (cred.uid == getuid() || cred.uid == 0)
|
|
return 0;
|
|
return -1;
|
|
}
|
|
|
|
static void usage(void) __attribute__((noreturn));
|
|
|
|
static void usage(void)
|
|
{
|
|
fprintf(stderr,
|
|
"Usage: nstat [OPTION] [ PATTERN [ PATTERN ] ]\n"
|
|
" -h, --help this message\n"
|
|
" -a, --ignore ignore history\n"
|
|
" -d, --scan=SECS sample every statistics every SECS\n"
|
|
" -j, --json format output in JSON\n"
|
|
" -n, --nooutput do history only\n"
|
|
" -p, --pretty pretty print\n"
|
|
" -r, --reset reset history\n"
|
|
" -s, --noupdate don't update history\n"
|
|
" -t, --interval=SECS report average over the last SECS\n"
|
|
" -V, --version output version information\n"
|
|
" -z, --zeros show entries with zero activity\n");
|
|
exit(-1);
|
|
}
|
|
|
|
static const struct option longopts[] = {
|
|
{ "help", 0, 0, 'h' },
|
|
{ "ignore", 0, 0, 'a' },
|
|
{ "scan", 1, 0, 'd'},
|
|
{ "nooutput", 0, 0, 'n' },
|
|
{ "json", 0, 0, 'j' },
|
|
{ "reset", 0, 0, 'r' },
|
|
{ "noupdate", 0, 0, 's' },
|
|
{ "pretty", 0, 0, 'p' },
|
|
{ "interval", 1, 0, 't' },
|
|
{ "version", 0, 0, 'V' },
|
|
{ "zeros", 0, 0, 'z' },
|
|
{ 0 }
|
|
};
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
char *hist_name;
|
|
struct sockaddr_un sun;
|
|
FILE *hist_fp = NULL;
|
|
int ch;
|
|
int fd;
|
|
|
|
while ((ch = getopt_long(argc, argv, "h?vVzrnasd:t:jp",
|
|
longopts, NULL)) != EOF) {
|
|
switch (ch) {
|
|
case 'z':
|
|
dump_zeros = 1;
|
|
break;
|
|
case 'r':
|
|
reset_history = 1;
|
|
break;
|
|
case 'a':
|
|
ignore_history = 1;
|
|
break;
|
|
case 's':
|
|
no_update = 1;
|
|
break;
|
|
case 'n':
|
|
no_output = 1;
|
|
break;
|
|
case 'd':
|
|
scan_interval = 1000*atoi(optarg);
|
|
break;
|
|
case 't':
|
|
if (sscanf(optarg, "%d", &time_constant) != 1 ||
|
|
time_constant <= 0) {
|
|
fprintf(stderr, "nstat: invalid time constant divisor\n");
|
|
exit(-1);
|
|
}
|
|
break;
|
|
case 'j':
|
|
json_output = 1;
|
|
break;
|
|
case 'p':
|
|
pretty = 1;
|
|
break;
|
|
case 'v':
|
|
case 'V':
|
|
printf("nstat utility, iproute2-%s\n", version);
|
|
exit(0);
|
|
case 'h':
|
|
case '?':
|
|
default:
|
|
usage();
|
|
}
|
|
}
|
|
|
|
argc -= optind;
|
|
argv += optind;
|
|
|
|
sun.sun_family = AF_UNIX;
|
|
sun.sun_path[0] = 0;
|
|
sprintf(sun.sun_path+1, "nstat%d", getuid());
|
|
|
|
if (scan_interval > 0) {
|
|
if (time_constant == 0)
|
|
time_constant = 60;
|
|
time_constant *= 1000;
|
|
W = 1 - 1/exp(log(10)*(double)scan_interval/time_constant);
|
|
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
|
|
perror("nstat: socket");
|
|
exit(-1);
|
|
}
|
|
if (bind(fd, (struct sockaddr *)&sun, 2+1+strlen(sun.sun_path+1)) < 0) {
|
|
perror("nstat: bind");
|
|
exit(-1);
|
|
}
|
|
if (listen(fd, 5) < 0) {
|
|
perror("nstat: listen");
|
|
exit(-1);
|
|
}
|
|
if (daemon(0, 0)) {
|
|
perror("nstat: daemon");
|
|
exit(-1);
|
|
}
|
|
signal(SIGPIPE, SIG_IGN);
|
|
signal(SIGCHLD, sigchild);
|
|
server_loop(fd);
|
|
exit(0);
|
|
}
|
|
|
|
patterns = argv;
|
|
npatterns = argc;
|
|
|
|
if ((hist_name = getenv("NSTAT_HISTORY")) == NULL) {
|
|
hist_name = malloc(128);
|
|
sprintf(hist_name, "/tmp/.nstat.u%d", getuid());
|
|
}
|
|
|
|
if (reset_history)
|
|
unlink(hist_name);
|
|
|
|
if (!ignore_history || !no_update) {
|
|
struct stat stb;
|
|
|
|
fd = open(hist_name, O_RDWR|O_CREAT|O_NOFOLLOW, 0600);
|
|
if (fd < 0) {
|
|
perror("nstat: open history file");
|
|
exit(-1);
|
|
}
|
|
if ((hist_fp = fdopen(fd, "r+")) == NULL) {
|
|
perror("nstat: fdopen history file");
|
|
exit(-1);
|
|
}
|
|
if (flock(fileno(hist_fp), LOCK_EX)) {
|
|
perror("nstat: flock history file");
|
|
exit(-1);
|
|
}
|
|
if (fstat(fileno(hist_fp), &stb) != 0) {
|
|
perror("nstat: fstat history file");
|
|
exit(-1);
|
|
}
|
|
if (stb.st_nlink != 1 || stb.st_uid != getuid()) {
|
|
fprintf(stderr, "nstat: something is so wrong with history file, that I prefer not to proceed.\n");
|
|
exit(-1);
|
|
}
|
|
if (!ignore_history) {
|
|
FILE *tfp;
|
|
long uptime = -1;
|
|
|
|
if ((tfp = fopen("/proc/uptime", "r")) != NULL) {
|
|
if (fscanf(tfp, "%ld", &uptime) != 1)
|
|
uptime = -1;
|
|
fclose(tfp);
|
|
}
|
|
if (uptime >= 0 && time(NULL) >= stb.st_mtime+uptime) {
|
|
fprintf(stderr, "nstat: history is aged out, resetting\n");
|
|
if (ftruncate(fileno(hist_fp), 0) < 0)
|
|
perror("nstat: ftruncate");
|
|
}
|
|
}
|
|
|
|
load_good_table(hist_fp);
|
|
|
|
hist_db = kern_db;
|
|
kern_db = NULL;
|
|
}
|
|
|
|
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) >= 0 &&
|
|
(connect(fd, (struct sockaddr *)&sun, 2+1+strlen(sun.sun_path+1)) == 0
|
|
|| (strcpy(sun.sun_path+1, "nstat0"),
|
|
connect(fd, (struct sockaddr *)&sun, 2+1+strlen(sun.sun_path+1)) == 0))
|
|
&& verify_forging(fd) == 0) {
|
|
FILE *sfp = fdopen(fd, "r");
|
|
|
|
if (!sfp) {
|
|
fprintf(stderr, "nstat: fdopen failed: %s\n",
|
|
strerror(errno));
|
|
close(fd);
|
|
} else {
|
|
load_good_table(sfp);
|
|
if (hist_db && source_mismatch) {
|
|
fprintf(stderr, "nstat: history is stale, ignoring it.\n");
|
|
hist_db = NULL;
|
|
}
|
|
fclose(sfp);
|
|
}
|
|
} else {
|
|
if (fd >= 0)
|
|
close(fd);
|
|
if (hist_db && info_source[0] && strcmp(info_source, "kernel")) {
|
|
fprintf(stderr, "nstat: history is stale, ignoring it.\n");
|
|
hist_db = NULL;
|
|
info_source[0] = 0;
|
|
}
|
|
load_netstat();
|
|
load_snmp6();
|
|
load_snmp();
|
|
load_sctp_snmp();
|
|
if (info_source[0] == 0)
|
|
strcpy(info_source, "kernel");
|
|
}
|
|
|
|
if (!no_output) {
|
|
if (ignore_history || hist_db == NULL)
|
|
dump_kern_db(stdout, 0);
|
|
else
|
|
dump_incr_db(stdout);
|
|
}
|
|
if (!no_update) {
|
|
if (ftruncate(fileno(hist_fp), 0) < 0)
|
|
perror("nstat: ftruncate");
|
|
rewind(hist_fp);
|
|
|
|
json_output = 0;
|
|
dump_kern_db(hist_fp, 1);
|
|
fclose(hist_fp);
|
|
}
|
|
exit(0);
|
|
}
|