1 /* Copyright (c) 1998-2022 Free Software Foundation, Inc.
2 This file is part of the GNU C Library.
3
4 The GNU C Library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License as published by the Free Software Foundation; either
7 version 2.1 of the License, or (at your option) any later version.
8
9 The GNU C Library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
13
14 You should have received a copy of the GNU Lesser General Public
15 License along with the GNU C Library; if not, see
16 <https://www.gnu.org/licenses/>. */
17
18 #include <errno.h>
19 #include <error.h>
20 #include <inttypes.h>
21 #include <langinfo.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <sys/socket.h>
26 #include <unistd.h>
27 #include <libintl.h>
28
29 #include "nscd.h"
30 #include "dbg_log.h"
31 #include "selinux.h"
32 #ifdef HAVE_SELINUX
33 # include <selinux/selinux.h>
34 # include <selinux/avc.h>
35 #endif /* HAVE_SELINUX */
36
37 /* We use this to make sure the receiver is the same. The lower 16
38 bits are reserved for flags indicating compilation variants. This
39 version needs to be updated if the definition of struct statdata
40 changes. */
41 #define STATDATA_VERSION 0x01020000U
42
43 #ifdef HAVE_SELINUX
44 # define STATDATA_VERSION_SELINUX_FLAG 0x0001U
45 #else
46 # define STATDATA_VERSION_SELINUX_FLAG 0x0000U
47 #endif
48
49 /* All flags affecting the struct statdata layout. */
50 #define STATDATA_VERSION_FLAGS STATDATA_VERSION_SELINUX_FLAG
51
52 /* The full version number for struct statdata. */
53 #define STATDATA_VERSION_FULL (STATDATA_VERSION | STATDATA_VERSION_FLAGS)
54
55 /* Statistic data for one database. */
56 struct dbstat
57 {
58 int enabled;
59 int check_file;
60 int shared;
61 int persistent;
62 size_t module;
63
64 unsigned long int postimeout;
65 unsigned long int negtimeout;
66
67 size_t nentries;
68 size_t maxnentries;
69 size_t maxnsearched;
70 size_t datasize;
71 size_t dataused;
72
73 uintmax_t poshit;
74 uintmax_t neghit;
75 uintmax_t posmiss;
76 uintmax_t negmiss;
77
78 uintmax_t rdlockdelayed;
79 uintmax_t wrlockdelayed;
80
81 uintmax_t addfailed;
82 };
83
84 /* Record for transmitting statistics. If this definition changes,
85 update STATDATA_VERSION above. */
86 struct statdata
87 {
88 unsigned int version; /* Must be STATDATA_VERSION_FULL. */
89 int debug_level;
90 time_t runtime;
91 unsigned long int client_queued;
92 int nthreads;
93 int max_nthreads;
94 int paranoia;
95 time_t restart_interval;
96 unsigned int reload_count;
97 int ndbs;
98 struct dbstat dbs[lastdb];
99 #ifdef HAVE_SELINUX
100 struct avc_cache_stats cstats;
101 #endif /* HAVE_SELINUX */
102 };
103
104
105 void
send_stats(int fd,struct database_dyn dbs[lastdb])106 send_stats (int fd, struct database_dyn dbs[lastdb])
107 {
108 struct statdata data;
109 int cnt;
110
111 memset (&data, 0, sizeof (data));
112
113 data.version = STATDATA_VERSION_FULL;
114 data.debug_level = debug_level;
115 data.runtime = time (NULL) - start_time;
116 data.client_queued = client_queued;
117 data.nthreads = nthreads;
118 data.max_nthreads = max_nthreads;
119 data.paranoia = paranoia;
120 data.restart_interval = restart_interval;
121 data.reload_count = reload_count;
122 data.ndbs = lastdb;
123
124 for (cnt = 0; cnt < lastdb; ++cnt)
125 {
126 memset (&data.dbs[cnt], 0, sizeof (data.dbs[cnt]));
127 data.dbs[cnt].enabled = dbs[cnt].enabled;
128 data.dbs[cnt].check_file = dbs[cnt].check_file;
129 data.dbs[cnt].shared = dbs[cnt].shared;
130 data.dbs[cnt].persistent = dbs[cnt].persistent;
131 data.dbs[cnt].postimeout = dbs[cnt].postimeout;
132 data.dbs[cnt].negtimeout = dbs[cnt].negtimeout;
133 if (dbs[cnt].head != NULL)
134 {
135 data.dbs[cnt].module = dbs[cnt].head->module;
136 data.dbs[cnt].poshit = dbs[cnt].head->poshit;
137 data.dbs[cnt].neghit = dbs[cnt].head->neghit;
138 data.dbs[cnt].posmiss = dbs[cnt].head->posmiss;
139 data.dbs[cnt].negmiss = dbs[cnt].head->negmiss;
140 data.dbs[cnt].nentries = dbs[cnt].head->nentries;
141 data.dbs[cnt].maxnentries = dbs[cnt].head->maxnentries;
142 data.dbs[cnt].datasize = dbs[cnt].head->data_size;
143 data.dbs[cnt].dataused = dbs[cnt].head->first_free;
144 data.dbs[cnt].maxnsearched = dbs[cnt].head->maxnsearched;
145 data.dbs[cnt].rdlockdelayed = dbs[cnt].head->rdlockdelayed;
146 data.dbs[cnt].wrlockdelayed = dbs[cnt].head->wrlockdelayed;
147 data.dbs[cnt].addfailed = dbs[cnt].head->addfailed;
148 }
149 }
150
151 if (selinux_enabled)
152 nscd_avc_cache_stats (&data.cstats);
153
154 if (TEMP_FAILURE_RETRY (send (fd, &data, sizeof (data), MSG_NOSIGNAL))
155 != sizeof (data))
156 {
157 char buf[256];
158 dbg_log (_("cannot write statistics: %s"),
159 strerror_r (errno, buf, sizeof (buf)));
160 }
161 }
162
163
164 int
receive_print_stats(void)165 receive_print_stats (void)
166 {
167 struct statdata data;
168 request_header req;
169 ssize_t nbytes;
170 int fd;
171 int i;
172 uid_t uid = getuid ();
173 const char *yesstr = _("yes");
174 const char *nostr = _("no");
175
176 /* Find out whether there is another user but root allowed to
177 request statistics. */
178 if (uid != 0)
179 {
180 /* User specified? */
181 if(stat_user == NULL || stat_uid != uid)
182 {
183 if (stat_user != NULL)
184 error (EXIT_FAILURE, 0,
185 _("Only root or %s is allowed to use this option!"),
186 stat_user);
187 else
188 error (EXIT_FAILURE, 0,
189 _("Only root is allowed to use this option!"));
190 }
191 }
192
193 /* Open a socket to the running nscd. */
194 fd = nscd_open_socket ();
195 if (fd == -1)
196 error (EXIT_FAILURE, 0, _("nscd not running!\n"));
197
198 /* Send the request. */
199 req.version = NSCD_VERSION;
200 req.type = GETSTAT;
201 req.key_len = 0;
202 nbytes = TEMP_FAILURE_RETRY (send (fd, &req, sizeof (request_header),
203 MSG_NOSIGNAL));
204 if (nbytes != sizeof (request_header))
205 {
206 int err = errno;
207 close (fd);
208 error (EXIT_FAILURE, err, _("write incomplete"));
209 }
210
211 /* Read as much data as we expect. */
212 if (TEMP_FAILURE_RETRY (read (fd, &data, sizeof (data))) != sizeof (data)
213 || (data.version != STATDATA_VERSION_FULL
214 /* Yes, this is an assignment! */
215 && (errno = EINVAL)))
216 {
217 /* Not the right version. */
218 int err = errno;
219 close (fd);
220 error (EXIT_FAILURE, err, _("cannot read statistics data"));
221 }
222
223 printf (_("nscd configuration:\n\n%15d server debug level\n"),
224 data.debug_level);
225
226 /* We know that we can simply subtract time_t values. */
227 unsigned long int diff = data.runtime;
228 unsigned int ndays = 0;
229 unsigned int nhours = 0;
230 unsigned int nmins = 0;
231 if (diff > 24 * 60 * 60)
232 {
233 ndays = diff / (24 * 60 * 60);
234 diff %= 24 * 60 * 60;
235 }
236 if (diff > 60 * 60)
237 {
238 nhours = diff / (60 * 60);
239 diff %= 60 * 60;
240 }
241 if (diff > 60)
242 {
243 nmins = diff / 60;
244 diff %= 60;
245 }
246 if (ndays != 0)
247 printf (_("%3ud %2uh %2um %2lus server runtime\n"),
248 ndays, nhours, nmins, diff);
249 else if (nhours != 0)
250 printf (_(" %2uh %2um %2lus server runtime\n"), nhours, nmins, diff);
251 else if (nmins != 0)
252 printf (_(" %2um %2lus server runtime\n"), nmins, diff);
253 else
254 printf (_(" %2lus server runtime\n"), diff);
255
256 printf (_("%15d current number of threads\n"
257 "%15d maximum number of threads\n"
258 "%15lu number of times clients had to wait\n"
259 "%15s paranoia mode enabled\n"
260 "%15lu restart internal\n"
261 "%15u reload count\n"),
262 data.nthreads, data.max_nthreads, data.client_queued,
263 data.paranoia ? yesstr : nostr,
264 (unsigned long int) data.restart_interval, data.reload_count);
265
266 for (i = 0; i < lastdb; ++i)
267 {
268 unsigned long int hit = data.dbs[i].poshit + data.dbs[i].neghit;
269 unsigned long int all = hit + data.dbs[i].posmiss + data.dbs[i].negmiss;
270 const char *enabled = data.dbs[i].enabled ? yesstr : nostr;
271 const char *check_file = data.dbs[i].check_file ? yesstr : nostr;
272 const char *shared = data.dbs[i].shared ? yesstr : nostr;
273 const char *persistent = data.dbs[i].persistent ? yesstr : nostr;
274
275 if (enabled[0] == '\0')
276 /* The locale does not provide this information so we have to
277 translate it ourself. Since we should avoid short translation
278 terms we artifically increase the length. */
279 enabled = data.dbs[i].enabled ? yesstr : nostr;
280 if (check_file[0] == '\0')
281 check_file = data.dbs[i].check_file ? yesstr : nostr;
282 if (shared[0] == '\0')
283 shared = data.dbs[i].shared ? yesstr : nostr;
284 if (persistent[0] == '\0')
285 persistent = data.dbs[i].persistent ? yesstr : nostr;
286
287 if (all == 0)
288 /* If nothing happened so far report a 0% hit rate. */
289 all = 1;
290
291 printf (_("\n%s cache:\n\n"
292 "%15s cache is enabled\n"
293 "%15s cache is persistent\n"
294 "%15s cache is shared\n"
295 "%15zu suggested size\n"
296 "%15zu total data pool size\n"
297 "%15zu used data pool size\n"
298 "%15lu seconds time to live for positive entries\n"
299 "%15lu seconds time to live for negative entries\n"
300 "%15" PRIuMAX " cache hits on positive entries\n"
301 "%15" PRIuMAX " cache hits on negative entries\n"
302 "%15" PRIuMAX " cache misses on positive entries\n"
303 "%15" PRIuMAX " cache misses on negative entries\n"
304 "%15lu%% cache hit rate\n"
305 "%15zu current number of cached values\n"
306 "%15zu maximum number of cached values\n"
307 "%15zu maximum chain length searched\n"
308 "%15" PRIuMAX " number of delays on rdlock\n"
309 "%15" PRIuMAX " number of delays on wrlock\n"
310 "%15" PRIuMAX " memory allocations failed\n"
311 "%15s check /etc/%s for changes\n"),
312 dbnames[i], enabled, persistent, shared,
313 data.dbs[i].module,
314 data.dbs[i].datasize, data.dbs[i].dataused,
315 data.dbs[i].postimeout, data.dbs[i].negtimeout,
316 data.dbs[i].poshit, data.dbs[i].neghit,
317 data.dbs[i].posmiss, data.dbs[i].negmiss,
318 (100 * hit) / all,
319 data.dbs[i].nentries, data.dbs[i].maxnentries,
320 data.dbs[i].maxnsearched,
321 data.dbs[i].rdlockdelayed,
322 data.dbs[i].wrlockdelayed,
323 data.dbs[i].addfailed, check_file, dbnames[i]);
324 }
325
326 if (selinux_enabled)
327 nscd_avc_print_stats (&data.cstats);
328
329 close (fd);
330
331 exit (0);
332 }
333