1/*
2 * avcstat - Display SELinux avc statistics.
3 *
4 * Copyright (C) 2004 Red Hat, Inc., James Morris <jmorris@redhat.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2,
8 * as published by the Free Software Foundation.
9 *
10 */
11#include <stdio.h>
12#include <stdlib.h>
13#include <libgen.h>
14#include <stdarg.h>
15#include <errno.h>
16#include <string.h>
17#include <fcntl.h>
18#include <unistd.h>
19#include <signal.h>
20#include <sys/types.h>
21#include <sys/stat.h>
22#include <sys/ioctl.h>
23#include <linux/limits.h>
24
25#define DEF_STAT_FILE	"/avc/cache_stats"
26#define DEF_BUF_SIZE	8192
27#define HEADERS		"lookups hits misses allocations reclaims frees"
28
29struct avc_cache_stats {
30	unsigned long long lookups;
31	unsigned long long hits;
32	unsigned long long misses;
33	unsigned long long allocations;
34	unsigned long long reclaims;
35	unsigned long long frees;
36};
37
38static int interval;
39static int rows;
40static char *progname;
41static char buf[DEF_BUF_SIZE];
42
43/* selinuxfs mount point */
44extern char *selinux_mnt;
45
46static __attribute__((__format__(printf,1,2),__noreturn__)) void die(const char *msg, ...)
47{
48	va_list args;
49
50	fputs("ERROR: ", stderr);
51
52	va_start(args, msg);
53	vfprintf(stderr, msg, args);
54	va_end(args);
55
56	if (errno)
57		fprintf(stderr, ": %s", strerror(errno));
58
59	fputc('\n', stderr);
60	exit(1);
61}
62
63static void usage(void)
64{
65	printf("\nUsage: %s [-c] [-f status_file] [interval]\n\n", progname);
66	printf
67	    ("Display SELinux AVC statistics.  If the interval parameter is specified, the\n");
68	printf
69	    ("program will loop, displaying updated statistics every \'interval\' seconds.\n");
70	printf
71	    ("Relative values are displayed by default. Use the -c option to specify the\n");
72	printf
73	    ("display of cumulative values.  The -f option specifies the location of the\n");
74	printf("AVC statistics file, defaulting to \'%s%s\'.\n\n", selinux_mnt,
75	       DEF_STAT_FILE);
76}
77
78static void set_window_rows(void)
79{
80	int ret;
81	struct winsize ws;
82
83	ret = ioctl(fileno(stdout), TIOCGWINSZ, &ws);
84	if (ret < 0 || ws.ws_row < 3)
85		ws.ws_row = 24;
86	rows = ws.ws_row;
87}
88
89static void sighandler(int num)
90{
91	if (num == SIGWINCH)
92		set_window_rows();
93}
94
95int main(int argc, char **argv)
96{
97	struct avc_cache_stats tot, rel, last;
98	int fd, i, cumulative = 0;
99	struct sigaction sa;
100	char avcstatfile[PATH_MAX];
101	snprintf(avcstatfile, sizeof avcstatfile, "%s%s", selinux_mnt,
102		 DEF_STAT_FILE);
103	progname = basename(argv[0]);
104
105	memset(&last, 0, sizeof(last));
106
107	while ((i = getopt(argc, argv, "cf:h?-")) != -1) {
108		switch (i) {
109		case 'c':
110			cumulative = 1;
111			break;
112		case 'f':
113			strncpy(avcstatfile, optarg, sizeof avcstatfile);
114			break;
115		case 'h':
116		case '-':
117			usage();
118			exit(0);
119		default:
120			usage();
121			die("unrecognized parameter '%c'", i);
122		}
123	}
124
125	if (optind < argc) {
126		char *arg = argv[optind];
127		unsigned int n = strtoul(arg, NULL, 10);
128
129		if (errno == ERANGE) {
130			usage();
131			die("invalid interval \'%s\'", arg);
132		}
133		if (n == 0) {
134			usage();
135			exit(0);
136		}
137		interval = n;
138	}
139
140	sa.sa_handler = sighandler;
141	sa.sa_flags = SA_RESTART;
142	sigemptyset(&sa.sa_mask);
143
144	i = sigaction(SIGWINCH, &sa, NULL);
145	if (i < 0)
146		die("sigaction");
147
148	set_window_rows();
149	fd = open(avcstatfile, O_RDONLY);
150	if (fd < 0)
151		die("open: \'%s\'", avcstatfile);
152
153	for (i = 0;; i++) {
154		char *line;
155		ssize_t ret, parsed = 0;
156
157		memset(buf, 0, DEF_BUF_SIZE);
158		ret = read(fd, buf, DEF_BUF_SIZE-1);
159		if (ret < 0)
160			die("read");
161
162		if (ret == 0)
163			die("read: \'%s\': unexpected end of file",
164			    avcstatfile);
165
166		line = strtok(buf, "\n");
167		if (!line)
168			die("unable to parse \'%s\': end of line not found",
169			    avcstatfile);
170
171		if (strcmp(line, HEADERS))
172			die("unable to parse \'%s\': invalid headers",
173			    avcstatfile);
174
175		if (!i || !(i % (rows - 2)))
176			printf("%10s %10s %10s %10s %10s %10s\n", "lookups",
177			       "hits", "misses", "allocs", "reclaims", "frees");
178
179		memset(&tot, 0, sizeof(tot));
180
181		while ((line = strtok(NULL, "\n"))) {
182			struct avc_cache_stats tmp;
183
184			ret = sscanf(line, "%llu %llu %llu %llu %llu %llu",
185				     &tmp.lookups,
186				     &tmp.hits,
187				     &tmp.misses,
188				     &tmp.allocations,
189				     &tmp.reclaims, &tmp.frees);
190			if (ret != 6)
191				die("unable to parse \'%s\': scan error",
192				    avcstatfile);
193
194			tot.lookups += tmp.lookups;
195			tot.hits += tmp.hits;
196			tot.misses += tmp.misses;
197			tot.allocations += tmp.allocations;
198			tot.reclaims += tmp.reclaims;
199			tot.frees += tmp.frees;
200			parsed = 1;
201		}
202
203		if (!parsed)
204			die("unable to parse \'%s\': no data", avcstatfile);
205
206		if (cumulative || !i)
207			printf("%10Lu %10Lu %10Lu %10Lu %10Lu %10Lu\n",
208			       tot.lookups, tot.hits, tot.misses,
209			       tot.allocations, tot.reclaims, tot.frees);
210		else {
211			rel.lookups = tot.lookups - last.lookups;
212			rel.hits = tot.hits - last.hits;
213			rel.misses = tot.misses - last.misses;
214			rel.allocations = tot.allocations - last.allocations;
215			rel.reclaims = tot.reclaims - last.reclaims;
216			rel.frees = tot.frees - last.frees;
217			printf("%10Lu %10Lu %10Lu %10Lu %10Lu %10Lu\n",
218			       rel.lookups, rel.hits, rel.misses,
219			       rel.allocations, rel.reclaims, rel.frees);
220		}
221
222		if (!interval)
223			break;
224
225		memcpy(&last, &tot, sizeof(last));
226		sleep(interval);
227
228		ret = lseek(fd, 0, 0);
229		if (ret < 0)
230			die("lseek");
231	}
232
233	close(fd);
234	return 0;
235}
236