1 /* SPDX-License-Identifier: LGPL-2.1+ */
6 #include "nscd-flush.h"
7 #include "socket-util.h"
11 #define NSCD_FLUSH_CACHE_TIMEOUT_USEC (5*USEC_PER_SEC)
13 struct nscdInvalidateRequest
{
15 int32_t type
; /* in glibc this is an enum. We don't replicate this here 1:1. Also, wtf, how unportable is that
21 static const union sockaddr_union nscd_sa
= {
22 .un
.sun_family
= AF_UNIX
,
23 .un
.sun_path
= "/run/nscd/socket",
26 static int nscd_flush_cache_one(const char *database
, usec_t end
) {
27 size_t req_size
, has_written
= 0, has_read
= 0, l
;
28 struct nscdInvalidateRequest
*req
;
29 _cleanup_close_
int fd
= -1;
36 req_size
= offsetof(struct nscdInvalidateRequest
, dbname
) + l
+ 1;
38 req
= alloca(req_size
);
39 *req
= (struct nscdInvalidateRequest
) {
45 strcpy(req
->dbname
, database
);
47 fd
= socket(AF_UNIX
, SOCK_STREAM
|SOCK_CLOEXEC
|SOCK_NONBLOCK
, 0);
49 return log_debug_errno(errno
, "Failed to allocate nscd socket: %m");
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 excercise in defensive, protocol-agnostic
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 if (connect(fd
, &nscd_sa
.sa
, SOCKADDR_UN_LEN(nscd_sa
.un
)) < 0) {
65 return log_debug_errno(errno
, "nscd is overloaded (backlog limit reached) and refuses to take further connections: %m");
66 if (errno
!= EINPROGRESS
)
67 return log_debug_errno(errno
, "Failed to connect to nscd socket: %m");
69 /* Continue in case of EINPROGRESS, but don't bother with send() or recv() until being notified that
70 * establishing the connection is complete. */
73 events
= POLLIN
|POLLOUT
; /* Let's assume initially that we can write and read to the fd, to suppress
74 * one poll() invocation */
78 if (events
& POLLOUT
) {
81 assert(has_written
< req_size
);
83 m
= send(fd
, (uint8_t*) req
+ has_written
, req_size
- has_written
, MSG_NOSIGNAL
);
85 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
89 return log_debug_errno(errno
, "Failed to write to nscd socket: %m");
94 if (events
& (POLLIN
|POLLERR
|POLLHUP
)) {
97 if (has_read
>= sizeof(resp
))
98 return log_debug_errno(SYNTHETIC_ERRNO(EIO
), "Response from nscd longer than expected: %m");
100 m
= recv(fd
, (uint8_t*) &resp
+ has_read
, sizeof(resp
) - has_read
, 0);
103 return log_debug_errno(errno
, "Failed to read from nscd socket: %m");
104 } else if (m
== 0) { /* EOF */
105 if (has_read
== 0 && has_written
>= req_size
) /* Older nscd immediately terminated the
106 * connection, accept that as OK */
109 return log_debug_errno(SYNTHETIC_ERRNO(EIO
), "nscd prematurely ended connection.");
114 if (has_written
>= req_size
&& has_read
>= sizeof(resp
)) { /* done? */
116 return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG
), "nscd sent us a negative error numer: %i", resp
);
118 return log_debug_errno(resp
, "nscd return failure code on invalidating '%s'.", database
);
122 p
= now(CLOCK_MONOTONIC
);
126 events
= fd_wait_for_event(fd
, POLLIN
| (has_written
< req_size
? POLLOUT
: 0), end
- p
);
132 int nscd_flush_cache(char **databases
) {
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. */
140 end
= usec_add(now(CLOCK_MONOTONIC
), NSCD_FLUSH_CACHE_TIMEOUT_USEC
);
142 STRV_FOREACH(i
, databases
) {
145 k
= nscd_flush_cache_one(*i
, end
);