LCOV - code coverage report
Current view: top level - shared - nscd-flush.c (source / functions) Hit Total Coverage
Test: main_coverage.info Lines: 0 60 0.0 %
Date: 2019-08-22 15:41:25 Functions: 0 2 0.0 %

          Line data    Source code
       1             : /* SPDX-License-Identifier: LGPL-2.1+ */
       2             : #include <sys/poll.h>
       3             : 
       4             : #include "fd-util.h"
       5             : #include "io-util.h"
       6             : #include "nscd-flush.h"
       7             : #include "socket-util.h"
       8             : #include "strv.h"
       9             : #include "time-util.h"
      10             : 
      11             : #define NSCD_FLUSH_CACHE_TIMEOUT_USEC (5*USEC_PER_SEC)
      12             : 
      13             : struct nscdInvalidateRequest {
      14             :         int32_t version;
      15             :         int32_t type; /* in glibc this is an enum. We don't replicate this here 1:1. Also, wtf, how unportable is that
      16             :                        * even? */
      17             :         int32_t key_len;
      18             :         char dbname[];
      19             : };
      20             : 
      21             : static const union sockaddr_union nscd_sa = {
      22             :         .un.sun_family = AF_UNIX,
      23             :         .un.sun_path = "/run/nscd/socket",
      24             : };
      25             : 
      26           0 : static int nscd_flush_cache_one(const char *database, usec_t end) {
      27           0 :         size_t req_size, has_written = 0, has_read = 0, l;
      28             :         struct nscdInvalidateRequest *req;
      29           0 :         _cleanup_close_ int fd = -1;
      30             :         int32_t resp;
      31             :         int events;
      32             : 
      33           0 :         assert(database);
      34             : 
      35           0 :         l = strlen(database);
      36           0 :         req_size = offsetof(struct nscdInvalidateRequest, dbname) + l + 1;
      37             : 
      38           0 :         req = alloca(req_size);
      39           0 :         *req = (struct nscdInvalidateRequest) {
      40             :                 .version = 2,
      41             :                 .type = 10,
      42           0 :                 .key_len = l + 1,
      43             :         };
      44             : 
      45           0 :         strcpy(req->dbname, database);
      46             : 
      47           0 :         fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
      48           0 :         if (fd < 0)
      49           0 :                 return log_debug_errno(errno, "Failed to allocate nscd socket: %m");
      50             : 
      51             :         /* Note: connect() returns EINPROGRESS if O_NONBLOCK is set and establishing a connection takes time. The
      52             :          * kernel lets us know this way that the connection is now being established, and we should watch with poll()
      53             :          * to learn when it is fully established. That said, AF_UNIX on Linux never triggers this IRL (connect() is
      54             :          * always instant on AF_UNIX), hence handling this is mostly just an exercise in defensive, protocol-agnostic
      55             :          * programming.
      56             :          *
      57             :          * connect() returns EAGAIN if the socket's backlog limit has been reached. When we see this we give up right
      58             :          * away, after all this entire function here is written in a defensive style so that a non-responding nscd
      59             :          * doesn't stall us for good. (Even if we wanted to handle this better: the Linux kernel doesn't really have a
      60             :          * nice way to connect() to a server synchronously with a time limit that would also cover dealing with the
      61             :          * backlog limit. After all SO_RCVTIMEO and SR_SNDTIMEO don't apply to connect(), and alarm() is frickin' ugly
      62             :          * and not really reasonably usable from threads-aware code.) */
      63           0 :         if (connect(fd, &nscd_sa.sa, SOCKADDR_UN_LEN(nscd_sa.un)) < 0) {
      64           0 :                 if (errno == EAGAIN)
      65           0 :                         return log_debug_errno(errno, "nscd is overloaded (backlog limit reached) and refuses to take further connections: %m");
      66           0 :                 if (errno != EINPROGRESS)
      67           0 :                         return log_debug_errno(errno, "Failed to connect to nscd socket: %m");
      68             : 
      69             :                 /* Continue in case of EINPROGRESS, but don't bother with send() or recv() until being notified that
      70             :                  * establishing the connection is complete. */
      71           0 :                 events = 0;
      72             :         } else
      73           0 :                 events = POLLIN|POLLOUT; /* Let's assume initially that we can write and read to the fd, to suppress
      74             :                                           * one poll() invocation */
      75           0 :         for (;;) {
      76             :                 usec_t p;
      77             : 
      78           0 :                 if (events & POLLOUT) {
      79             :                         ssize_t m;
      80             : 
      81           0 :                         assert(has_written < req_size);
      82             : 
      83           0 :                         m = send(fd, (uint8_t*) req + has_written, req_size - has_written, MSG_NOSIGNAL);
      84           0 :                         if (m < 0) {
      85           0 :                                 if (errno != EAGAIN) /* Note that EAGAIN is returned by the kernel whenever it can't
      86             :                                                       * take the data right now, and that includes if the connect() is
      87             :                                                       * asynchronous and we saw EINPROGRESS on it, and it hasn't
      88             :                                                       * completed yet. */
      89           0 :                                         return log_debug_errno(errno, "Failed to write to nscd socket: %m");
      90             :                         } else
      91           0 :                                 has_written += m;
      92             :                 }
      93             : 
      94           0 :                 if (events & (POLLIN|POLLERR|POLLHUP)) {
      95             :                         ssize_t m;
      96             : 
      97           0 :                         if (has_read >= sizeof(resp))
      98           0 :                                 return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Response from nscd longer than expected: %m");
      99             : 
     100           0 :                         m = recv(fd, (uint8_t*) &resp + has_read, sizeof(resp) - has_read, 0);
     101           0 :                         if (m < 0) {
     102           0 :                                 if (errno != EAGAIN)
     103           0 :                                         return log_debug_errno(errno, "Failed to read from nscd socket: %m");
     104           0 :                         } else if (m == 0) { /* EOF */
     105           0 :                                 if (has_read == 0 && has_written >= req_size) /* Older nscd immediately terminated the
     106             :                                                                                * connection, accept that as OK */
     107           0 :                                         return 1;
     108             : 
     109           0 :                                 return log_debug_errno(SYNTHETIC_ERRNO(EIO), "nscd prematurely ended connection.");
     110             :                         } else
     111           0 :                                 has_read += m;
     112             :                 }
     113             : 
     114           0 :                 if (has_written >= req_size && has_read >= sizeof(resp)) { /* done? */
     115           0 :                         if (resp < 0)
     116           0 :                                 return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "nscd sent us a negative error number: %i", resp);
     117           0 :                         if (resp > 0)
     118           0 :                                 return log_debug_errno(resp, "nscd return failure code on invalidating '%s'.", database);
     119           0 :                         return 1;
     120             :                 }
     121             : 
     122           0 :                 p = now(CLOCK_MONOTONIC);
     123           0 :                 if (p >= end)
     124           0 :                         return -ETIMEDOUT;
     125             : 
     126           0 :                 events = fd_wait_for_event(fd, POLLIN | (has_written < req_size ? POLLOUT : 0), end - p);
     127           0 :                 if (events < 0)
     128           0 :                         return events;
     129             :         }
     130             : }
     131             : 
     132           0 : int nscd_flush_cache(char **databases) {
     133             :         usec_t end;
     134           0 :         int r = 0;
     135             :         char **i;
     136             : 
     137             :         /* Tries to invalidate the specified database in nscd. We do this carefully, with a 5s time-out, so that we
     138             :          * don't block indefinitely on another service. */
     139             : 
     140           0 :         end = usec_add(now(CLOCK_MONOTONIC), NSCD_FLUSH_CACHE_TIMEOUT_USEC);
     141             : 
     142           0 :         STRV_FOREACH(i, databases) {
     143             :                 int k;
     144             : 
     145           0 :                 k = nscd_flush_cache_one(*i, end);
     146           0 :                 if (k < 0 && r >= 0)
     147           0 :                         r = k;
     148             :         }
     149             : 
     150           0 :         return r;
     151             : }

Generated by: LCOV version 1.14