Use writeall.
[kopensolaris-gnu/glibc.git] / nscd / pwdcache.c
1 /* Cache handling for passwd lookup.
2    Copyright (C) 1998-2002, 2003, 2004, 2005 Free Software Foundation, Inc.
3    This file is part of the GNU C Library.
4    Contributed by Ulrich Drepper <drepper@cygnus.com>, 1998.
5
6    The GNU C Library is free software; you can redistribute it and/or
7    modify it under the terms of the GNU Lesser General Public
8    License as published by the Free Software Foundation; either
9    version 2.1 of the License, or (at your option) any later version.
10
11    The GNU C Library 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 GNU
14    Lesser General Public License for more details.
15
16    You should have received a copy of the GNU Lesser General Public
17    License along with the GNU C Library; if not, write to the Free
18    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
19    02111-1307 USA.  */
20
21 #include <alloca.h>
22 #include <assert.h>
23 #include <errno.h>
24 #include <error.h>
25 #include <libintl.h>
26 #include <pwd.h>
27 #include <stdbool.h>
28 #include <stddef.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <time.h>
33 #include <unistd.h>
34 #include <sys/mman.h>
35 #include <stackinfo.h>
36
37 #include "nscd.h"
38 #include "dbg_log.h"
39
40 /* This is the standard reply in case the service is disabled.  */
41 static const pw_response_header disabled =
42 {
43   .version = NSCD_VERSION,
44   .found = -1,
45   .pw_name_len = 0,
46   .pw_passwd_len = 0,
47   .pw_uid = -1,
48   .pw_gid = -1,
49   .pw_gecos_len = 0,
50   .pw_dir_len = 0,
51   .pw_shell_len = 0
52 };
53
54 /* This is the struct describing how to write this record.  */
55 const struct iovec pwd_iov_disabled =
56 {
57   .iov_base = (void *) &disabled,
58   .iov_len = sizeof (disabled)
59 };
60
61
62 /* This is the standard reply in case we haven't found the dataset.  */
63 static const pw_response_header notfound =
64 {
65   .version = NSCD_VERSION,
66   .found = 0,
67   .pw_name_len = 0,
68   .pw_passwd_len = 0,
69   .pw_uid = -1,
70   .pw_gid = -1,
71   .pw_gecos_len = 0,
72   .pw_dir_len = 0,
73   .pw_shell_len = 0
74 };
75
76
77 static void
78 cache_addpw (struct database_dyn *db, int fd, request_header *req,
79              const void *key, struct passwd *pwd, uid_t owner,
80              struct hashentry *he, struct datahead *dh, int errval)
81 {
82   ssize_t total;
83   ssize_t written;
84   time_t t = time (NULL);
85
86   /* We allocate all data in one memory block: the iov vector,
87      the response header and the dataset itself.  */
88   struct dataset
89   {
90     struct datahead head;
91     pw_response_header resp;
92     char strdata[0];
93   } *dataset;
94
95   assert (offsetof (struct dataset, resp) == offsetof (struct datahead, data));
96
97   if (pwd == NULL)
98     {
99       if (he != NULL && errval == EAGAIN)
100         {
101           /* If we have an old record available but cannot find one
102              now because the service is not available we keep the old
103              record and make sure it does not get removed.  */
104           if (reload_count != UINT_MAX && dh->nreloads == reload_count)
105             /* Do not reset the value if we never not reload the record.  */
106             dh->nreloads = reload_count - 1;
107
108           written = total = 0;
109         }
110       else
111         {
112           /* We have no data.  This means we send the standard reply for this
113              case.  */
114           written = total = sizeof (notfound);
115
116           if (fd != -1)
117             written = TEMP_FAILURE_RETRY (write (fd, &notfound, total));
118
119           dataset = mempool_alloc (db, sizeof (struct dataset) + req->key_len);
120           /* If we cannot permanently store the result, so be it.  */
121           if (dataset != NULL)
122             {
123               dataset->head.allocsize = sizeof (struct dataset) + req->key_len;
124               dataset->head.recsize = total;
125               dataset->head.notfound = true;
126               dataset->head.nreloads = 0;
127               dataset->head.usable = true;
128
129               /* Compute the timeout time.  */
130               dataset->head.timeout = t + db->negtimeout;
131
132               /* This is the reply.  */
133               memcpy (&dataset->resp, &notfound, total);
134
135               /* Copy the key data.  */
136               char *key_copy = memcpy (dataset->strdata, key, req->key_len);
137
138               /* If necessary, we also propagate the data to disk.  */
139               if (db->persistent)
140                 {
141                   // XXX async OK?
142                   uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
143                   msync ((void *) pval,
144                          ((uintptr_t) dataset & pagesize_m1)
145                          + sizeof (struct dataset) + req->key_len, MS_ASYNC);
146                 }
147
148               /* Now get the lock to safely insert the records.  */
149               pthread_rwlock_rdlock (&db->lock);
150
151               if (cache_add (req->type, key_copy, req->key_len,
152                              &dataset->head, true, db, owner) < 0)
153                 /* Ensure the data can be recovered.  */
154                 dataset->head.usable = false;
155
156
157               pthread_rwlock_unlock (&db->lock);
158
159               /* Mark the old entry as obsolete.  */
160               if (dh != NULL)
161                 dh->usable = false;
162             }
163           else
164             ++db->head->addfailed;
165         }
166     }
167   else
168     {
169       /* Determine the I/O structure.  */
170       size_t pw_name_len = strlen (pwd->pw_name) + 1;
171       size_t pw_passwd_len = strlen (pwd->pw_passwd) + 1;
172       size_t pw_gecos_len = strlen (pwd->pw_gecos) + 1;
173       size_t pw_dir_len = strlen (pwd->pw_dir) + 1;
174       size_t pw_shell_len = strlen (pwd->pw_shell) + 1;
175       char *cp;
176       const size_t key_len = strlen (key);
177       const size_t buf_len = 3 * sizeof (pwd->pw_uid) + key_len + 1;
178       char *buf = alloca (buf_len);
179       ssize_t n;
180
181       /* We need this to insert the `byuid' entry.  */
182       int key_offset;
183       n = snprintf (buf, buf_len, "%d%c%n%s", pwd->pw_uid, '\0',
184                     &key_offset, (char *) key) + 1;
185
186       written = total = (sizeof (struct dataset) + pw_name_len + pw_passwd_len
187                          + pw_gecos_len + pw_dir_len + pw_shell_len);
188
189       /* If we refill the cache, first assume the reconrd did not
190          change.  Allocate memory on the cache since it is likely
191          discarded anyway.  If it turns out to be necessary to have a
192          new record we can still allocate real memory.  */
193       bool alloca_used = false;
194       dataset = NULL;
195
196       if (he == NULL)
197         {
198           dataset = (struct dataset *) mempool_alloc (db, total + n);
199           if (dataset == NULL)
200             ++db->head->addfailed;
201         }
202
203       if (dataset == NULL)
204         {
205           /* We cannot permanently add the result in the moment.  But
206              we can provide the result as is.  Store the data in some
207              temporary memory.  */
208           dataset = (struct dataset *) alloca (total + n);
209
210           /* We cannot add this record to the permanent database.  */
211           alloca_used = true;
212         }
213
214       dataset->head.allocsize = total + n;
215       dataset->head.recsize = total - offsetof (struct dataset, resp);
216       dataset->head.notfound = false;
217       dataset->head.nreloads = he == NULL ? 0 : (dh->nreloads + 1);
218       dataset->head.usable = true;
219
220       /* Compute the timeout time.  */
221       dataset->head.timeout = t + db->postimeout;
222
223       dataset->resp.version = NSCD_VERSION;
224       dataset->resp.found = 1;
225       dataset->resp.pw_name_len = pw_name_len;
226       dataset->resp.pw_passwd_len = pw_passwd_len;
227       dataset->resp.pw_uid = pwd->pw_uid;
228       dataset->resp.pw_gid = pwd->pw_gid;
229       dataset->resp.pw_gecos_len = pw_gecos_len;
230       dataset->resp.pw_dir_len = pw_dir_len;
231       dataset->resp.pw_shell_len = pw_shell_len;
232
233       cp = dataset->strdata;
234
235       /* Copy the strings over into the buffer.  */
236       cp = mempcpy (cp, pwd->pw_name, pw_name_len);
237       cp = mempcpy (cp, pwd->pw_passwd, pw_passwd_len);
238       cp = mempcpy (cp, pwd->pw_gecos, pw_gecos_len);
239       cp = mempcpy (cp, pwd->pw_dir, pw_dir_len);
240       cp = mempcpy (cp, pwd->pw_shell, pw_shell_len);
241
242       /* Finally the stringified UID value.  */
243       memcpy (cp, buf, n);
244       char *key_copy = cp + key_offset;
245       assert (key_copy == (char *) rawmemchr (cp, '\0') + 1);
246
247       /* Now we can determine whether on refill we have to create a new
248          record or not.  */
249       if (he != NULL)
250         {
251           assert (fd == -1);
252
253           if (total + n == dh->allocsize
254               && total - offsetof (struct dataset, resp) == dh->recsize
255               && memcmp (&dataset->resp, dh->data,
256                          dh->allocsize - offsetof (struct dataset, resp)) == 0)
257             {
258               /* The data has not changed.  We will just bump the
259                  timeout value.  Note that the new record has been
260                  allocated on the stack and need not be freed.  */
261               dh->timeout = dataset->head.timeout;
262               ++dh->nreloads;
263             }
264           else
265             {
266               /* We have to create a new record.  Just allocate
267                  appropriate memory and copy it.  */
268               struct dataset *newp
269                 = (struct dataset *) mempool_alloc (db, total + n);
270               if (newp != NULL)
271                 {
272                   /* Adjust pointer into the memory block.  */
273                   cp = (char *) newp + (cp - (char *) dataset);
274
275                   dataset = memcpy (newp, dataset, total + n);
276                   alloca_used = false;
277                 }
278
279               /* Mark the old record as obsolete.  */
280               dh->usable = false;
281             }
282         }
283       else
284         {
285           /* We write the dataset before inserting it to the database
286              since while inserting this thread might block and so would
287              unnecessarily let the receiver wait.  */
288           assert (fd != -1);
289
290           written = writeall (fd, &dataset->resp, total);
291         }
292
293
294       /* Add the record to the database.  But only if it has not been
295          stored on the stack.  */
296       if (! alloca_used)
297         {
298           /* If necessary, we also propagate the data to disk.  */
299           if (db->persistent)
300             {
301               // XXX async OK?
302               uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
303               msync ((void *) pval,
304                      ((uintptr_t) dataset & pagesize_m1) + total + n,
305                      MS_ASYNC);
306             }
307
308           /* Now get the lock to safely insert the records.  */
309           pthread_rwlock_rdlock (&db->lock);
310
311           /* NB: in the following code we always must add the entry
312              marked with FIRST first.  Otherwise we end up with
313              dangling "pointers" in case a latter hash entry cannot be
314              added.  */
315           bool first = req->type == GETPWBYNAME;
316
317           /* If the request was by UID, add that entry first.  */
318           if (req->type != GETPWBYNAME)
319             {
320               if (cache_add (GETPWBYUID, cp, key_offset, &dataset->head, true,
321                              db, owner) < 0)
322                 {
323                   /* Could not allocate memory.  Make sure the data gets
324                      discarded.  */
325                   dataset->head.usable = false;
326                   goto out;
327                 }
328             }
329           /* If the key is different from the name add a separate entry.  */
330           else if (strcmp (key_copy, dataset->strdata) != 0)
331             {
332               if (cache_add (GETPWBYNAME, key_copy, key_len + 1,
333                              &dataset->head, first, db, owner) < 0)
334                 {
335                   /* Could not allocate memory.  Make sure the data gets
336                      discarded.  */
337                   dataset->head.usable = false;
338                   goto out;
339                 }
340
341               first = false;
342             }
343
344           /* We have to add the value for both, byname and byuid.  */
345           if (__builtin_expect (cache_add (GETPWBYNAME, dataset->strdata,
346                                            pw_name_len, &dataset->head, first,
347                                            db, owner) == 0, 1))
348             {
349               if (req->type == GETPWBYNAME)
350                 (void) cache_add (GETPWBYUID, cp, key_offset, &dataset->head,
351                                   req->type != GETPWBYNAME, db, owner);
352             }
353           else if (first)
354             /* Could not allocate memory.  Make sure the data gets
355                discarded.  */
356             dataset->head.usable = false;
357
358         out:
359           pthread_rwlock_unlock (&db->lock);
360         }
361     }
362
363   if (__builtin_expect (written != total, 0) && debug_level > 0)
364     {
365       char buf[256];
366       dbg_log (_("short write in %s: %s"),  __FUNCTION__,
367                strerror_r (errno, buf, sizeof (buf)));
368     }
369 }
370
371
372 union keytype
373 {
374   void *v;
375   uid_t u;
376 };
377
378
379 static int
380 lookup (int type, union keytype key, struct passwd *resultbufp, char *buffer,
381         size_t buflen, struct passwd **pwd)
382 {
383   if (type == GETPWBYNAME)
384     return __getpwnam_r (key.v, resultbufp, buffer, buflen, pwd);
385   else
386     return __getpwuid_r (key.u, resultbufp, buffer, buflen, pwd);
387 }
388
389
390 static void
391 addpwbyX (struct database_dyn *db, int fd, request_header *req,
392           union keytype key, const char *keystr, uid_t c_uid,
393           struct hashentry *he, struct datahead *dh)
394 {
395   /* Search for the entry matching the key.  Please note that we don't
396      look again in the table whether the dataset is now available.  We
397      simply insert it.  It does not matter if it is in there twice.  The
398      pruning function only will look at the timestamp.  */
399   size_t buflen = 1024;
400   char *buffer = (char *) alloca (buflen);
401   struct passwd resultbuf;
402   struct passwd *pwd;
403   bool use_malloc = false;
404   int errval = 0;
405
406   if (__builtin_expect (debug_level > 0, 0))
407     {
408       if (he == NULL)
409         dbg_log (_("Haven't found \"%s\" in password cache!"), keystr);
410       else
411         dbg_log (_("Reloading \"%s\" in password cache!"), keystr);
412     }
413
414 #if 0
415   uid_t oldeuid = 0;
416   if (db->secure)
417     {
418       oldeuid = geteuid ();
419       pthread_seteuid_np (c_uid);
420     }
421 #endif
422
423   while (lookup (req->type, key, &resultbuf, buffer, buflen, &pwd) != 0
424          && (errval = errno) == ERANGE)
425     {
426       char *old_buffer = buffer;
427       errno = 0;
428
429       if (__builtin_expect (buflen > 32768, 0))
430         {
431           buflen *= 2;
432           buffer = (char *) realloc (use_malloc ? buffer : NULL, buflen);
433           if (buffer == NULL)
434             {
435               /* We ran out of memory.  We cannot do anything but
436                  sending a negative response.  In reality this should
437                  never happen.  */
438               pwd = NULL;
439               buffer = old_buffer;
440
441               /* We set the error to indicate this is (possibly) a
442                  temporary error and that it does not mean the entry
443                  is not available at all.  */
444               errval = EAGAIN;
445               break;
446             }
447           use_malloc = true;
448         }
449       else
450         /* Allocate a new buffer on the stack.  If possible combine it
451            with the previously allocated buffer.  */
452         buffer = (char *) extend_alloca (buffer, buflen, 2 * buflen);
453     }
454
455 #if 0
456   if (db->secure)
457     pthread_seteuid_np (oldeuid);
458 #endif
459
460   /* Add the entry to the cache.  */
461   cache_addpw (db, fd, req, keystr, pwd, c_uid, he, dh, errval);
462
463   if (use_malloc)
464     free (buffer);
465 }
466
467
468 void
469 addpwbyname (struct database_dyn *db, int fd, request_header *req,
470              void *key, uid_t c_uid)
471 {
472   union keytype u = { .v = key };
473
474   addpwbyX (db, fd, req, u, key, c_uid, NULL, NULL);
475 }
476
477
478 void
479 readdpwbyname (struct database_dyn *db, struct hashentry *he,
480                struct datahead *dh)
481 {
482   request_header req =
483     {
484       .type = GETPWBYNAME,
485       .key_len = he->len
486     };
487   union keytype u = { .v = db->data + he->key };
488
489   addpwbyX (db, -1, &req, u, db->data + he->key, he->owner, he, dh);
490 }
491
492
493 void
494 addpwbyuid (struct database_dyn *db, int fd, request_header *req,
495             void *key, uid_t c_uid)
496 {
497   char *ep;
498   uid_t uid = strtoul ((char *) key, &ep, 10);
499
500   if (*(char *) key == '\0' || *ep != '\0')  /* invalid numeric uid */
501     {
502       if (debug_level > 0)
503         dbg_log (_("Invalid numeric uid \"%s\"!"), (char *) key);
504
505       errno = EINVAL;
506       return;
507     }
508
509   union keytype u = { .u = uid };
510
511   addpwbyX (db, fd, req, u, key, c_uid, NULL, NULL);
512 }
513
514
515 void
516 readdpwbyuid (struct database_dyn *db, struct hashentry *he,
517               struct datahead *dh)
518 {
519   char *ep;
520   uid_t uid = strtoul (db->data + he->key, &ep, 10);
521
522   /* Since the key has been added before it must be OK.  */
523   assert (*(db->data + he->key) != '\0' && *ep == '\0');
524
525   request_header req =
526     {
527       .type = GETPWBYUID,
528       .key_len = he->len
529     };
530   union keytype u = { .u = uid };
531
532   addpwbyX (db, -1, &req, u, db->data + he->key, he->owner, he, dh);
533 }