]>
Commit | Line | Data |
---|---|---|
f079c372 LP |
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 | 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; | |
30 | int32_t resp; | |
31 | int events; | |
32 | ||
33 | assert(database); | |
34 | ||
35 | l = strlen(database); | |
36 | req_size = offsetof(struct nscdInvalidateRequest, dbname) + l + 1; | |
37 | ||
38 | req = alloca(req_size); | |
39 | *req = (struct nscdInvalidateRequest) { | |
40 | .version = 2, | |
41 | .type = 10, | |
42 | .key_len = l + 1, | |
43 | }; | |
44 | ||
45 | strcpy(req->dbname, database); | |
46 | ||
47 | fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); | |
48 | if (fd < 0) | |
49 | 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 | |
5238e957 | 54 | * always instant on AF_UNIX), hence handling this is mostly just an exercise in defensive, protocol-agnostic |
f079c372 LP |
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 | if (connect(fd, &nscd_sa.sa, SOCKADDR_UN_LEN(nscd_sa.un)) < 0) { | |
64 | if (errno == EAGAIN) | |
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"); | |
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 | events = 0; | |
72 | } else | |
73 | events = POLLIN|POLLOUT; /* Let's assume initially that we can write and read to the fd, to suppress | |
74 | * one poll() invocation */ | |
75 | for (;;) { | |
76 | usec_t p; | |
77 | ||
78 | if (events & POLLOUT) { | |
79 | ssize_t m; | |
80 | ||
81 | assert(has_written < req_size); | |
82 | ||
83 | m = send(fd, (uint8_t*) req + has_written, req_size - has_written, MSG_NOSIGNAL); | |
84 | if (m < 0) { | |
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 | |
88 | * completed yet. */ | |
89 | return log_debug_errno(errno, "Failed to write to nscd socket: %m"); | |
90 | } else | |
91 | has_written += m; | |
92 | } | |
93 | ||
94 | if (events & (POLLIN|POLLERR|POLLHUP)) { | |
95 | ssize_t m; | |
96 | ||
97 | if (has_read >= sizeof(resp)) | |
98 | return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Response from nscd longer than expected: %m"); | |
99 | ||
100 | m = recv(fd, (uint8_t*) &resp + has_read, sizeof(resp) - has_read, 0); | |
101 | if (m < 0) { | |
102 | if (errno != EAGAIN) | |
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 */ | |
107 | return 1; | |
108 | ||
109 | return log_debug_errno(SYNTHETIC_ERRNO(EIO), "nscd prematurely ended connection."); | |
110 | } else | |
111 | has_read += m; | |
112 | } | |
113 | ||
114 | if (has_written >= req_size && has_read >= sizeof(resp)) { /* done? */ | |
115 | if (resp < 0) | |
5238e957 | 116 | return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "nscd sent us a negative error number: %i", resp); |
f079c372 LP |
117 | if (resp > 0) |
118 | return log_debug_errno(resp, "nscd return failure code on invalidating '%s'.", database); | |
119 | return 1; | |
120 | } | |
121 | ||
122 | p = now(CLOCK_MONOTONIC); | |
123 | if (p >= end) | |
124 | return -ETIMEDOUT; | |
125 | ||
126 | events = fd_wait_for_event(fd, POLLIN | (has_written < req_size ? POLLOUT : 0), end - p); | |
127 | if (events < 0) | |
128 | return events; | |
129 | } | |
130 | } | |
131 | ||
132 | int nscd_flush_cache(char **databases) { | |
133 | usec_t end; | |
134 | int r = 0; | |
135 | char **i; | |
136 | ||
201632e3 | 137 | /* Tries to invalidate the specified database in nscd. We do this carefully, with a 5s timeout, so that we |
f079c372 LP |
138 | * don't block indefinitely on another service. */ |
139 | ||
140 | end = usec_add(now(CLOCK_MONOTONIC), NSCD_FLUSH_CACHE_TIMEOUT_USEC); | |
141 | ||
142 | STRV_FOREACH(i, databases) { | |
143 | int k; | |
144 | ||
145 | k = nscd_flush_cache_one(*i, end); | |
146 | if (k < 0 && r >= 0) | |
147 | r = k; | |
148 | } | |
149 | ||
150 | return r; | |
151 | } |