]> git.ipfire.org Git - thirdparty/glibc.git/blob - nscd/servicescache.c
Properly check for short writes when sending the response in nscd
[thirdparty/glibc.git] / nscd / servicescache.c
1 /* Cache handling for services lookup.
2 Copyright (C) 2007-2013 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
4 Contributed by Ulrich Drepper <drepper@drepper.com>, 2007.
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published
8 by the Free Software Foundation; version 2 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, see <http://www.gnu.org/licenses/>. */
18
19 #include <alloca.h>
20 #include <assert.h>
21 #include <errno.h>
22 #include <libintl.h>
23 #include <netdb.h>
24 #include <unistd.h>
25 #include <sys/mman.h>
26 #include <kernel-features.h>
27
28 #include "nscd.h"
29 #include "dbg_log.h"
30
31
32 /* This is the standard reply in case the service is disabled. */
33 static const serv_response_header disabled =
34 {
35 .version = NSCD_VERSION,
36 .found = -1,
37 .s_name_len = 0,
38 .s_proto_len = 0,
39 .s_aliases_cnt = 0,
40 .s_port = -1
41 };
42
43 /* This is the struct describing how to write this record. */
44 const struct iovec serv_iov_disabled =
45 {
46 .iov_base = (void *) &disabled,
47 .iov_len = sizeof (disabled)
48 };
49
50
51 /* This is the standard reply in case we haven't found the dataset. */
52 static const serv_response_header notfound =
53 {
54 .version = NSCD_VERSION,
55 .found = 0,
56 .s_name_len = 0,
57 .s_proto_len = 0,
58 .s_aliases_cnt = 0,
59 .s_port = -1
60 };
61
62
63 static time_t
64 cache_addserv (struct database_dyn *db, int fd, request_header *req,
65 const void *key, struct servent *serv, uid_t owner,
66 struct hashentry *const he, struct datahead *dh, int errval)
67 {
68 bool all_written = true;
69 ssize_t total;
70 time_t t = time (NULL);
71
72 /* We allocate all data in one memory block: the iov vector,
73 the response header and the dataset itself. */
74 struct dataset
75 {
76 struct datahead head;
77 serv_response_header resp;
78 char strdata[0];
79 } *dataset;
80
81 assert (offsetof (struct dataset, resp) == offsetof (struct datahead, data));
82
83 time_t timeout = MAX_TIMEOUT_VALUE;
84 if (serv == NULL)
85 {
86 if (he != NULL && errval == EAGAIN)
87 {
88 /* If we have an old record available but cannot find one
89 now because the service is not available we keep the old
90 record and make sure it does not get removed. */
91 if (reload_count != UINT_MAX)
92 /* Do not reset the value if we never not reload the record. */
93 dh->nreloads = reload_count - 1;
94
95 /* Reload with the same time-to-live value. */
96 timeout = dh->timeout = t + db->postimeout;
97
98 total = 0;
99 }
100 else
101 {
102 /* We have no data. This means we send the standard reply for this
103 case. */
104 total = sizeof (notfound);
105
106 if (fd != -1
107 && TEMP_FAILURE_RETRY (send (fd, &notfound, total,
108 MSG_NOSIGNAL)) != total)
109 all_written = false;
110
111 /* If we have a transient error or cannot permanently store
112 the result, so be it. */
113 if (errval == EAGAIN || __builtin_expect (db->negtimeout == 0, 0))
114 {
115 /* Mark the old entry as obsolete. */
116 if (dh != NULL)
117 dh->usable = false;
118 }
119 else if ((dataset = mempool_alloc (db, (sizeof (struct dataset)
120 + req->key_len), 1)) != NULL)
121 {
122 dataset->head.allocsize = sizeof (struct dataset) + req->key_len;
123 dataset->head.recsize = total;
124 dataset->head.notfound = true;
125 dataset->head.nreloads = 0;
126 dataset->head.usable = true;
127
128 /* Compute the timeout time. */
129 timeout = dataset->head.timeout = t + db->negtimeout;
130
131 /* This is the reply. */
132 memcpy (&dataset->resp, &notfound, total);
133
134 /* Copy the key data. */
135 memcpy (dataset->strdata, key, req->key_len);
136
137 /* If necessary, we also propagate the data to disk. */
138 if (db->persistent)
139 {
140 // XXX async OK?
141 uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
142 msync ((void *) pval,
143 ((uintptr_t) dataset & pagesize_m1)
144 + sizeof (struct dataset) + req->key_len, MS_ASYNC);
145 }
146
147 (void) cache_add (req->type, &dataset->strdata, req->key_len,
148 &dataset->head, true, db, owner, he == NULL);
149
150 pthread_rwlock_unlock (&db->lock);
151
152 /* Mark the old entry as obsolete. */
153 if (dh != NULL)
154 dh->usable = false;
155 }
156 }
157 }
158 else
159 {
160 /* Determine the I/O structure. */
161 size_t s_name_len = strlen (serv->s_name) + 1;
162 size_t s_proto_len = strlen (serv->s_proto) + 1;
163 uint32_t *s_aliases_len;
164 size_t s_aliases_cnt;
165 char *aliases;
166 char *cp;
167 size_t cnt;
168
169 /* Determine the number of aliases. */
170 s_aliases_cnt = 0;
171 for (cnt = 0; serv->s_aliases[cnt] != NULL; ++cnt)
172 ++s_aliases_cnt;
173 /* Determine the length of all aliases. */
174 s_aliases_len = (uint32_t *) alloca (s_aliases_cnt * sizeof (uint32_t));
175 total = 0;
176 for (cnt = 0; cnt < s_aliases_cnt; ++cnt)
177 {
178 s_aliases_len[cnt] = strlen (serv->s_aliases[cnt]) + 1;
179 total += s_aliases_len[cnt];
180 }
181
182 total += (offsetof (struct dataset, strdata)
183 + s_name_len
184 + s_proto_len
185 + s_aliases_cnt * sizeof (uint32_t));
186
187 /* If we refill the cache, first assume the reconrd did not
188 change. Allocate memory on the cache since it is likely
189 discarded anyway. If it turns out to be necessary to have a
190 new record we can still allocate real memory. */
191 bool alloca_used = false;
192 dataset = NULL;
193
194 if (he == NULL)
195 dataset = (struct dataset *) mempool_alloc (db, total + req->key_len,
196 1);
197
198 if (dataset == NULL)
199 {
200 /* We cannot permanently add the result in the moment. But
201 we can provide the result as is. Store the data in some
202 temporary memory. */
203 dataset = (struct dataset *) alloca (total + req->key_len);
204
205 /* We cannot add this record to the permanent database. */
206 alloca_used = true;
207 }
208
209 dataset->head.allocsize = total + req->key_len;
210 dataset->head.recsize = total - offsetof (struct dataset, resp);
211 dataset->head.notfound = false;
212 dataset->head.nreloads = he == NULL ? 0 : (dh->nreloads + 1);
213 dataset->head.usable = true;
214
215 /* Compute the timeout time. */
216 timeout = dataset->head.timeout = t + db->postimeout;
217
218 dataset->resp.version = NSCD_VERSION;
219 dataset->resp.found = 1;
220 dataset->resp.s_name_len = s_name_len;
221 dataset->resp.s_proto_len = s_proto_len;
222 dataset->resp.s_port = serv->s_port;
223 dataset->resp.s_aliases_cnt = s_aliases_cnt;
224
225 cp = dataset->strdata;
226
227 cp = mempcpy (cp, serv->s_name, s_name_len);
228 cp = mempcpy (cp, serv->s_proto, s_proto_len);
229 cp = mempcpy (cp, s_aliases_len, s_aliases_cnt * sizeof (uint32_t));
230
231 /* Then the aliases. */
232 aliases = cp;
233 for (cnt = 0; cnt < s_aliases_cnt; ++cnt)
234 cp = mempcpy (cp, serv->s_aliases[cnt], s_aliases_len[cnt]);
235
236 assert (cp
237 == dataset->strdata + total - offsetof (struct dataset,
238 strdata));
239
240 char *key_copy = memcpy (cp, key, req->key_len);
241
242 /* Now we can determine whether on refill we have to create a new
243 record or not. */
244 if (he != NULL)
245 {
246 assert (fd == -1);
247
248 if (total + req->key_len == dh->allocsize
249 && total - offsetof (struct dataset, resp) == dh->recsize
250 && memcmp (&dataset->resp, dh->data,
251 dh->allocsize - offsetof (struct dataset, resp)) == 0)
252 {
253 /* The data has not changed. We will just bump the
254 timeout value. Note that the new record has been
255 allocated on the stack and need not be freed. */
256 dh->timeout = dataset->head.timeout;
257 ++dh->nreloads;
258 }
259 else
260 {
261 /* We have to create a new record. Just allocate
262 appropriate memory and copy it. */
263 struct dataset *newp
264 = (struct dataset *) mempool_alloc (db, total + req->key_len,
265 1);
266 if (newp != NULL)
267 {
268 /* Adjust pointers into the memory block. */
269 aliases = (char *) newp + (aliases - (char *) dataset);
270 assert (key_copy != NULL);
271 key_copy = (char *) newp + (key_copy - (char *) dataset);
272
273 dataset = memcpy (newp, dataset, total + req->key_len);
274 alloca_used = false;
275 }
276
277 /* Mark the old record as obsolete. */
278 dh->usable = false;
279 }
280 }
281 else
282 {
283 /* We write the dataset before inserting it to the database
284 since while inserting this thread might block and so would
285 unnecessarily keep the receiver waiting. */
286 assert (fd != -1);
287
288 #ifdef HAVE_SENDFILE
289 if (__builtin_expect (db->mmap_used, 1) && !alloca_used)
290 {
291 assert (db->wr_fd != -1);
292 assert ((char *) &dataset->resp > (char *) db->data);
293 assert ((char *) dataset - (char *) db->head
294 + total
295 <= (sizeof (struct database_pers_head)
296 + db->head->module * sizeof (ref_t)
297 + db->head->data_size));
298 ssize_t written = sendfileall (fd, db->wr_fd,
299 (char *) &dataset->resp
300 - (char *) db->head,
301 dataset->head.recsize);
302 if (written != dataset->head.recsize)
303 {
304 # ifndef __ASSUME_SENDFILE
305 if (written == -1 && errno == ENOSYS)
306 goto use_write;
307 # endif
308 all_written = false;
309 }
310 }
311 else
312 # ifndef __ASSUME_SENDFILE
313 use_write:
314 # endif
315 #endif
316 if (writeall (fd, &dataset->resp, dataset->head.recsize)
317 != dataset->head.recsize)
318 all_written = false;
319 }
320
321 /* Add the record to the database. But only if it has not been
322 stored on the stack. */
323 if (! alloca_used)
324 {
325 /* If necessary, we also propagate the data to disk. */
326 if (db->persistent)
327 {
328 // XXX async OK?
329 uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
330 msync ((void *) pval,
331 ((uintptr_t) dataset & pagesize_m1)
332 + total + req->key_len, MS_ASYNC);
333 }
334
335 (void) cache_add (req->type, key_copy, req->key_len,
336 &dataset->head, true, db, owner, he == NULL);
337
338 pthread_rwlock_unlock (&db->lock);
339 }
340 }
341
342 if (__builtin_expect (!all_written, 0) && debug_level > 0)
343 {
344 char buf[256];
345 dbg_log (_("short write in %s: %s"), __FUNCTION__,
346 strerror_r (errno, buf, sizeof (buf)));
347 }
348
349 return timeout;
350 }
351
352
353 static int
354 lookup (int type, char *key, struct servent *resultbufp, char *buffer,
355 size_t buflen, struct servent **serv)
356 {
357 char *proto = strrchr (key, '/');
358 if (proto != NULL && proto != key)
359 {
360 key = strndupa (key, proto - key);
361 if (proto[1] == '\0')
362 proto = NULL;
363 else
364 ++proto;
365 }
366
367 if (type == GETSERVBYNAME)
368 return __getservbyname_r (key, proto, resultbufp, buffer, buflen, serv);
369
370 assert (type == GETSERVBYPORT);
371 return __getservbyport_r (atol (key), proto, resultbufp, buffer, buflen,
372 serv);
373 }
374
375
376 static time_t
377 addservbyX (struct database_dyn *db, int fd, request_header *req,
378 char *key, uid_t uid, struct hashentry *he, struct datahead *dh)
379 {
380 /* Search for the entry matching the key. Please note that we don't
381 look again in the table whether the dataset is now available. We
382 simply insert it. It does not matter if it is in there twice. The
383 pruning function only will look at the timestamp. */
384 size_t buflen = 1024;
385 char *buffer = (char *) alloca (buflen);
386 struct servent resultbuf;
387 struct servent *serv;
388 bool use_malloc = false;
389 int errval = 0;
390
391 if (__builtin_expect (debug_level > 0, 0))
392 {
393 if (he == NULL)
394 dbg_log (_("Haven't found \"%s\" in services cache!"), key);
395 else
396 dbg_log (_("Reloading \"%s\" in services cache!"), key);
397 }
398
399 while (lookup (req->type, key, &resultbuf, buffer, buflen, &serv) != 0
400 && (errval = errno) == ERANGE)
401 {
402 errno = 0;
403
404 if (__builtin_expect (buflen > 32768, 0))
405 {
406 char *old_buffer = buffer;
407 buflen *= 2;
408 buffer = (char *) realloc (use_malloc ? buffer : NULL, buflen);
409 if (buffer == NULL)
410 {
411 /* We ran out of memory. We cannot do anything but
412 sending a negative response. In reality this should
413 never happen. */
414 serv = NULL;
415 buffer = old_buffer;
416
417 /* We set the error to indicate this is (possibly) a
418 temporary error and that it does not mean the entry
419 is not available at all. */
420 errval = EAGAIN;
421 break;
422 }
423 use_malloc = true;
424 }
425 else
426 /* Allocate a new buffer on the stack. If possible combine it
427 with the previously allocated buffer. */
428 buffer = (char *) extend_alloca (buffer, buflen, 2 * buflen);
429 }
430
431 time_t timeout = cache_addserv (db, fd, req, key, serv, uid, he, dh, errval);
432
433 if (use_malloc)
434 free (buffer);
435
436 return timeout;
437 }
438
439
440 void
441 addservbyname (struct database_dyn *db, int fd, request_header *req,
442 void *key, uid_t uid)
443 {
444 addservbyX (db, fd, req, key, uid, NULL, NULL);
445 }
446
447
448 time_t
449 readdservbyname (struct database_dyn *db, struct hashentry *he,
450 struct datahead *dh)
451 {
452 request_header req =
453 {
454 .type = GETSERVBYNAME,
455 .key_len = he->len
456 };
457
458 return addservbyX (db, -1, &req, db->data + he->key, he->owner, he, dh);
459 }
460
461
462 void
463 addservbyport (struct database_dyn *db, int fd, request_header *req,
464 void *key, uid_t uid)
465 {
466 addservbyX (db, fd, req, key, uid, NULL, NULL);
467 }
468
469
470 time_t
471 readdservbyport (struct database_dyn *db, struct hashentry *he,
472 struct datahead *dh)
473 {
474 request_header req =
475 {
476 .type = GETSERVBYPORT,
477 .key_len = he->len
478 };
479
480 return addservbyX (db, -1, &req, db->data + he->key, he->owner, he, dh);
481 }