Back to index

glibc  2.9
grpcache.c
Go to the documentation of this file.
00001 /* Cache handling for group lookup.
00002    Copyright (C) 1998-2005, 2006, 2007, 2008 Free Software Foundation, Inc.
00003    This file is part of the GNU C Library.
00004    Contributed by Ulrich Drepper <drepper@cygnus.com>, 1998.
00005 
00006    This program is free software; you can redistribute it and/or modify
00007    it under the terms of the GNU General Public License as published
00008    by the Free Software Foundation; version 2 of the License, or
00009    (at your option) any later version.
00010 
00011    This program is distributed in the hope that it will be useful,
00012    but WITHOUT ANY WARRANTY; without even the implied warranty of
00013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00014    GNU General Public License for more details.
00015 
00016    You should have received a copy of the GNU General Public License
00017    along with this program; if not, write to the Free Software Foundation,
00018    Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
00019 
00020 #include <alloca.h>
00021 #include <assert.h>
00022 #include <errno.h>
00023 #include <error.h>
00024 #include <grp.h>
00025 #include <libintl.h>
00026 #include <stdbool.h>
00027 #include <stddef.h>
00028 #include <stdio.h>
00029 #include <stdint.h>
00030 #include <stdlib.h>
00031 #include <string.h>
00032 #include <unistd.h>
00033 #include <sys/mman.h>
00034 #include <sys/socket.h>
00035 #include <stackinfo.h>
00036 
00037 #include "nscd.h"
00038 #include "dbg_log.h"
00039 #ifdef HAVE_SENDFILE
00040 # include <kernel-features.h>
00041 #endif
00042 
00043 /* This is the standard reply in case the service is disabled.  */
00044 static const gr_response_header disabled =
00045 {
00046   .version = NSCD_VERSION,
00047   .found = -1,
00048   .gr_name_len = 0,
00049   .gr_passwd_len = 0,
00050   .gr_gid = -1,
00051   .gr_mem_cnt = 0,
00052 };
00053 
00054 /* This is the struct describing how to write this record.  */
00055 const struct iovec grp_iov_disabled =
00056 {
00057   .iov_base = (void *) &disabled,
00058   .iov_len = sizeof (disabled)
00059 };
00060 
00061 
00062 /* This is the standard reply in case we haven't found the dataset.  */
00063 static const gr_response_header notfound =
00064 {
00065   .version = NSCD_VERSION,
00066   .found = 0,
00067   .gr_name_len = 0,
00068   .gr_passwd_len = 0,
00069   .gr_gid = -1,
00070   .gr_mem_cnt = 0,
00071 };
00072 
00073 
00074 static void
00075 cache_addgr (struct database_dyn *db, int fd, request_header *req,
00076             const void *key, struct group *grp, uid_t owner,
00077             struct hashentry *he, struct datahead *dh, int errval)
00078 {
00079   ssize_t total;
00080   ssize_t written;
00081   time_t t = time (NULL);
00082 
00083   /* We allocate all data in one memory block: the iov vector,
00084      the response header and the dataset itself.  */
00085   struct dataset
00086   {
00087     struct datahead head;
00088     gr_response_header resp;
00089     char strdata[0];
00090   } *dataset;
00091 
00092   assert (offsetof (struct dataset, resp) == offsetof (struct datahead, data));
00093 
00094   if (grp == NULL)
00095     {
00096       if (he != NULL && errval == EAGAIN)
00097        {
00098          /* If we have an old record available but cannot find one
00099             now because the service is not available we keep the old
00100             record and make sure it does not get removed.  */
00101          if (reload_count != UINT_MAX)
00102            /* Do not reset the value if we never not reload the record.  */
00103            dh->nreloads = reload_count - 1;
00104 
00105          written = total = 0;
00106        }
00107       else
00108        {
00109          /* We have no data.  This means we send the standard reply for this
00110             case.  */
00111          total = sizeof (notfound);
00112 
00113          written = TEMP_FAILURE_RETRY (send (fd, &notfound, total,
00114                                          MSG_NOSIGNAL));
00115 
00116          dataset = mempool_alloc (db, sizeof (struct dataset) + req->key_len,
00117                                IDX_result_data);
00118          /* If we cannot permanently store the result, so be it.  */
00119          if (dataset != NULL)
00120            {
00121              dataset->head.allocsize = sizeof (struct dataset) + req->key_len;
00122              dataset->head.recsize = total;
00123              dataset->head.notfound = true;
00124              dataset->head.nreloads = 0;
00125              dataset->head.usable = true;
00126 
00127              /* Compute the timeout time.  */
00128              dataset->head.timeout = t + db->negtimeout;
00129 
00130              /* This is the reply.  */
00131              memcpy (&dataset->resp, &notfound, total);
00132 
00133              /* Copy the key data.  */
00134              memcpy (dataset->strdata, key, req->key_len);
00135 
00136              /* If necessary, we also propagate the data to disk.  */
00137              if (db->persistent)
00138               {
00139                 // XXX async OK?
00140                 uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
00141                 msync ((void *) pval,
00142                       ((uintptr_t) dataset & pagesize_m1)
00143                       + sizeof (struct dataset) + req->key_len, MS_ASYNC);
00144               }
00145 
00146              /* Now get the lock to safely insert the records.  */
00147              pthread_rwlock_rdlock (&db->lock);
00148 
00149              (void) cache_add (req->type, &dataset->strdata, req->key_len,
00150                             &dataset->head, true, db, owner, he == NULL);
00151 
00152              pthread_rwlock_unlock (&db->lock);
00153 
00154              /* Mark the old entry as obsolete.  */
00155              if (dh != NULL)
00156               dh->usable = false;
00157            }
00158          else
00159            ++db->head->addfailed;
00160        }
00161     }
00162   else
00163     {
00164       /* Determine the I/O structure.  */
00165       size_t gr_name_len = strlen (grp->gr_name) + 1;
00166       size_t gr_passwd_len = strlen (grp->gr_passwd) + 1;
00167       size_t gr_mem_cnt = 0;
00168       uint32_t *gr_mem_len;
00169       size_t gr_mem_len_total = 0;
00170       char *gr_name;
00171       char *cp;
00172       const size_t key_len = strlen (key);
00173       const size_t buf_len = 3 * sizeof (grp->gr_gid) + key_len + 1;
00174       char *buf = alloca (buf_len);
00175       ssize_t n;
00176       size_t cnt;
00177 
00178       /* We need this to insert the `bygid' entry.  */
00179       int key_offset;
00180       n = snprintf (buf, buf_len, "%d%c%n%s", grp->gr_gid, '\0',
00181                   &key_offset, (char *) key) + 1;
00182 
00183       /* Determine the length of all members.  */
00184       while (grp->gr_mem[gr_mem_cnt])
00185        ++gr_mem_cnt;
00186       gr_mem_len = (uint32_t *) alloca (gr_mem_cnt * sizeof (uint32_t));
00187       for (gr_mem_cnt = 0; grp->gr_mem[gr_mem_cnt]; ++gr_mem_cnt)
00188        {
00189          gr_mem_len[gr_mem_cnt] = strlen (grp->gr_mem[gr_mem_cnt]) + 1;
00190          gr_mem_len_total += gr_mem_len[gr_mem_cnt];
00191        }
00192 
00193       written = total = (offsetof (struct dataset, strdata)
00194                       + gr_mem_cnt * sizeof (uint32_t)
00195                       + gr_name_len + gr_passwd_len + gr_mem_len_total);
00196 
00197       /* If we refill the cache, first assume the reconrd did not
00198         change.  Allocate memory on the cache since it is likely
00199         discarded anyway.  If it turns out to be necessary to have a
00200         new record we can still allocate real memory.  */
00201       bool alloca_used = false;
00202       dataset = NULL;
00203 
00204       if (he == NULL)
00205        {
00206          dataset = (struct dataset *) mempool_alloc (db, total + n,
00207                                                 IDX_result_data);
00208          if (dataset == NULL)
00209            ++db->head->addfailed;
00210        }
00211 
00212       if (dataset == NULL)
00213        {
00214          /* We cannot permanently add the result in the moment.  But
00215             we can provide the result as is.  Store the data in some
00216             temporary memory.  */
00217          dataset = (struct dataset *) alloca (total + n);
00218 
00219          /* We cannot add this record to the permanent database.  */
00220          alloca_used = true;
00221        }
00222 
00223       dataset->head.allocsize = total + n;
00224       dataset->head.recsize = total - offsetof (struct dataset, resp);
00225       dataset->head.notfound = false;
00226       dataset->head.nreloads = he == NULL ? 0 : (dh->nreloads + 1);
00227       dataset->head.usable = true;
00228 
00229       /* Compute the timeout time.  */
00230       dataset->head.timeout = t + db->postimeout;
00231 
00232       dataset->resp.version = NSCD_VERSION;
00233       dataset->resp.found = 1;
00234       dataset->resp.gr_name_len = gr_name_len;
00235       dataset->resp.gr_passwd_len = gr_passwd_len;
00236       dataset->resp.gr_gid = grp->gr_gid;
00237       dataset->resp.gr_mem_cnt = gr_mem_cnt;
00238 
00239       cp = dataset->strdata;
00240 
00241       /* This is the member string length array.  */
00242       cp = mempcpy (cp, gr_mem_len, gr_mem_cnt * sizeof (uint32_t));
00243       gr_name = cp;
00244       cp = mempcpy (cp, grp->gr_name, gr_name_len);
00245       cp = mempcpy (cp, grp->gr_passwd, gr_passwd_len);
00246 
00247       for (cnt = 0; cnt < gr_mem_cnt; ++cnt)
00248        cp = mempcpy (cp, grp->gr_mem[cnt], gr_mem_len[cnt]);
00249 
00250       /* Finally the stringified GID value.  */
00251       memcpy (cp, buf, n);
00252       char *key_copy = cp + key_offset;
00253       assert (key_copy == (char *) rawmemchr (cp, '\0') + 1);
00254 
00255       assert (cp == dataset->strdata + total - offsetof (struct dataset,
00256                                                   strdata));
00257 
00258       /* Now we can determine whether on refill we have to create a new
00259         record or not.  */
00260       if (he != NULL)
00261        {
00262          assert (fd == -1);
00263 
00264          if (total + n == dh->allocsize
00265              && total - offsetof (struct dataset, resp) == dh->recsize
00266              && memcmp (&dataset->resp, dh->data,
00267                       dh->allocsize - offsetof (struct dataset, resp)) == 0)
00268            {
00269              /* The data has not changed.  We will just bump the
00270                timeout value.  Note that the new record has been
00271                allocated on the stack and need not be freed.  */
00272              dh->timeout = dataset->head.timeout;
00273              ++dh->nreloads;
00274            }
00275          else
00276            {
00277              /* We have to create a new record.  Just allocate
00278                appropriate memory and copy it.  */
00279              struct dataset *newp
00280               = (struct dataset *) mempool_alloc (db, total + n,
00281                                               IDX_result_data);
00282              if (newp != NULL)
00283               {
00284                 /* Adjust pointers into the memory block.  */
00285                 gr_name = (char *) newp + (gr_name - (char *) dataset);
00286                 cp = (char *) newp + (cp - (char *) dataset);
00287                 key_copy = (char *) newp + (key_copy - (char *) dataset);
00288 
00289                 dataset = memcpy (newp, dataset, total + n);
00290                 alloca_used = false;
00291               }
00292              else
00293               ++db->head->addfailed;
00294 
00295              /* Mark the old record as obsolete.  */
00296              dh->usable = false;
00297            }
00298        }
00299       else
00300        {
00301          /* We write the dataset before inserting it to the database
00302             since while inserting this thread might block and so would
00303             unnecessarily let the receiver wait.  */
00304          assert (fd != -1);
00305 
00306 #ifdef HAVE_SENDFILE
00307          if (__builtin_expect (db->mmap_used, 1) && !alloca_used)
00308            {
00309              assert (db->wr_fd != -1);
00310              assert ((char *) &dataset->resp > (char *) db->data);
00311              assert ((char *) &dataset->resp - (char *) db->head
00312                     + total
00313                     <= (sizeof (struct database_pers_head)
00314                        + db->head->module * sizeof (ref_t)
00315                        + db->head->data_size));
00316              written = sendfileall (fd, db->wr_fd,
00317                                  (char *) &dataset->resp
00318                                  - (char *) db->head, total);
00319 # ifndef __ASSUME_SENDFILE
00320              if (written == -1 && errno == ENOSYS)
00321               goto use_write;
00322 # endif
00323            }
00324          else
00325 # ifndef __ASSUME_SENDFILE
00326          use_write:
00327 # endif
00328 #endif
00329            written = writeall (fd, &dataset->resp, total);
00330        }
00331 
00332       /* Add the record to the database.  But only if it has not been
00333         stored on the stack.  */
00334       if (! alloca_used)
00335        {
00336          /* If necessary, we also propagate the data to disk.  */
00337          if (db->persistent)
00338            {
00339              // XXX async OK?
00340              uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
00341              msync ((void *) pval,
00342                    ((uintptr_t) dataset & pagesize_m1) + total + n,
00343                    MS_ASYNC);
00344            }
00345 
00346          /* Now get the lock to safely insert the records.  */
00347          pthread_rwlock_rdlock (&db->lock);
00348 
00349          /* NB: in the following code we always must add the entry
00350             marked with FIRST first.  Otherwise we end up with
00351             dangling "pointers" in case a latter hash entry cannot be
00352             added.  */
00353          bool first = true;
00354 
00355          /* If the request was by GID, add that entry first.  */
00356          if (req->type == GETGRBYGID)
00357            {
00358              if (cache_add (GETGRBYGID, cp, key_offset, &dataset->head, true,
00359                           db, owner, he == NULL) < 0)
00360               goto out;
00361 
00362              first = false;
00363            }
00364          /* If the key is different from the name add a separate entry.  */
00365          else if (strcmp (key_copy, gr_name) != 0)
00366            {
00367              if (cache_add (GETGRBYNAME, key_copy, key_len + 1,
00368                           &dataset->head, true, db, owner, he == NULL) < 0)
00369               goto out;
00370 
00371              first = false;
00372            }
00373 
00374          /* We have to add the value for both, byname and byuid.  */
00375          if ((req->type == GETGRBYNAME || db->propagate)
00376              && __builtin_expect (cache_add (GETGRBYNAME, gr_name,
00377                                          gr_name_len,
00378                                          &dataset->head, first, db, owner,
00379                                          he == NULL)
00380                                == 0, 1))
00381            {
00382              if (req->type == GETGRBYNAME && db->propagate)
00383               (void) cache_add (GETGRBYGID, cp, key_offset, &dataset->head,
00384                               false, db, owner, false);
00385            }
00386 
00387        out:
00388          pthread_rwlock_unlock (&db->lock);
00389        }
00390     }
00391 
00392   if (__builtin_expect (written != total, 0) && debug_level > 0)
00393     {
00394       char buf[256];
00395       dbg_log (_("short write in %s: %s"),  __FUNCTION__,
00396               strerror_r (errno, buf, sizeof (buf)));
00397     }
00398 }
00399 
00400 
00401 union keytype
00402 {
00403   void *v;
00404   gid_t g;
00405 };
00406 
00407 
00408 static int
00409 lookup (int type, union keytype key, struct group *resultbufp, char *buffer,
00410        size_t buflen, struct group **grp)
00411 {
00412   if (type == GETGRBYNAME)
00413     return __getgrnam_r (key.v, resultbufp, buffer, buflen, grp);
00414   else
00415     return __getgrgid_r (key.g, resultbufp, buffer, buflen, grp);
00416 }
00417 
00418 
00419 static void
00420 addgrbyX (struct database_dyn *db, int fd, request_header *req,
00421          union keytype key, const char *keystr, uid_t uid,
00422          struct hashentry *he, struct datahead *dh)
00423 {
00424   /* Search for the entry matching the key.  Please note that we don't
00425      look again in the table whether the dataset is now available.  We
00426      simply insert it.  It does not matter if it is in there twice.  The
00427      pruning function only will look at the timestamp.  */
00428   size_t buflen = 1024;
00429   char *buffer = (char *) alloca (buflen);
00430   struct group resultbuf;
00431   struct group *grp;
00432   bool use_malloc = false;
00433   int errval = 0;
00434 
00435   if (__builtin_expect (debug_level > 0, 0))
00436     {
00437       if (he == NULL)
00438        dbg_log (_("Haven't found \"%s\" in group cache!"), keystr);
00439       else
00440        dbg_log (_("Reloading \"%s\" in group cache!"), keystr);
00441     }
00442 
00443   while (lookup (req->type, key, &resultbuf, buffer, buflen, &grp) != 0
00444         && (errval = errno) == ERANGE)
00445     {
00446       errno = 0;
00447 
00448       if (__builtin_expect (buflen > 32768, 0))
00449        {
00450          char *old_buffer = buffer;
00451          buflen *= 2;
00452          buffer = (char *) realloc (use_malloc ? buffer : NULL, buflen);
00453          if (buffer == NULL)
00454            {
00455              /* We ran out of memory.  We cannot do anything but
00456                sending a negative response.  In reality this should
00457                never happen.  */
00458              grp = NULL;
00459              buffer = old_buffer;
00460 
00461              /* We set the error to indicate this is (possibly) a
00462                temporary error and that it does not mean the entry
00463                is not available at all.  */
00464              errval = EAGAIN;
00465              break;
00466            }
00467          use_malloc = true;
00468        }
00469       else
00470        /* Allocate a new buffer on the stack.  If possible combine it
00471           with the previously allocated buffer.  */
00472        buffer = (char *) extend_alloca (buffer, buflen, 2 * buflen);
00473     }
00474 
00475   cache_addgr (db, fd, req, keystr, grp, uid, he, dh, errval);
00476 
00477   if (use_malloc)
00478     free (buffer);
00479 }
00480 
00481 
00482 void
00483 addgrbyname (struct database_dyn *db, int fd, request_header *req,
00484             void *key, uid_t uid)
00485 {
00486   union keytype u = { .v = key };
00487 
00488   addgrbyX (db, fd, req, u, key, uid, NULL, NULL);
00489 }
00490 
00491 
00492 void
00493 readdgrbyname (struct database_dyn *db, struct hashentry *he,
00494               struct datahead *dh)
00495 {
00496   request_header req =
00497     {
00498       .type = GETGRBYNAME,
00499       .key_len = he->len
00500     };
00501   union keytype u = { .v = db->data + he->key };
00502 
00503   addgrbyX (db, -1, &req, u, db->data + he->key, he->owner, he, dh);
00504 }
00505 
00506 
00507 void
00508 addgrbygid (struct database_dyn *db, int fd, request_header *req,
00509            void *key, uid_t uid)
00510 {
00511   char *ep;
00512   gid_t gid = strtoul ((char *) key, &ep, 10);
00513 
00514   if (*(char *) key == '\0' || *ep != '\0')  /* invalid numeric uid */
00515     {
00516       if (debug_level > 0)
00517         dbg_log (_("Invalid numeric gid \"%s\"!"), (char *) key);
00518 
00519       errno = EINVAL;
00520       return;
00521     }
00522 
00523   union keytype u = { .g = gid };
00524 
00525   addgrbyX (db, fd, req, u, key, uid, NULL, NULL);
00526 }
00527 
00528 
00529 void
00530 readdgrbygid (struct database_dyn *db, struct hashentry *he,
00531              struct datahead *dh)
00532 {
00533   char *ep;
00534   gid_t gid = strtoul (db->data + he->key, &ep, 10);
00535 
00536   /* Since the key has been added before it must be OK.  */
00537   assert (*(db->data + he->key) != '\0' && *ep == '\0');
00538 
00539   request_header req =
00540     {
00541       .type = GETGRBYGID,
00542       .key_len = he->len
00543     };
00544   union keytype u = { .g = gid };
00545 
00546   addgrbyX (db, -1, &req, u, db->data + he->key, he->owner, he, dh);
00547 }