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