Back to index

glibc  2.9
initgrcache.c
Go to the documentation of this file.
00001 /* Cache handling for host lookup.
00002    Copyright (C) 2004, 2005, 2006, 2008 Free Software Foundation, Inc.
00003    This file is part of the GNU C Library.
00004    Contributed by Ulrich Drepper <drepper@redhat.com>, 2004.
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 <assert.h>
00021 #include <errno.h>
00022 #include <grp.h>
00023 #include <libintl.h>
00024 #include <string.h>
00025 #include <time.h>
00026 #include <unistd.h>
00027 #include <sys/mman.h>
00028 
00029 #include "dbg_log.h"
00030 #include "nscd.h"
00031 #ifdef HAVE_SENDFILE
00032 # include <kernel-features.h>
00033 #endif
00034 
00035 #include "../nss/nsswitch.h"
00036 
00037 
00038 /* Type of the lookup function.  */
00039 typedef enum nss_status (*initgroups_dyn_function) (const char *, gid_t,
00040                                               long int *, long int *,
00041                                               gid_t **, long int, int *);
00042 
00043 
00044 static const initgr_response_header notfound =
00045 {
00046   .version = NSCD_VERSION,
00047   .found = 0,
00048   .ngrps = 0
00049 };
00050 
00051 
00052 #include "../grp/compat-initgroups.c"
00053 
00054 
00055 static void
00056 addinitgroupsX (struct database_dyn *db, int fd, request_header *req,
00057               void *key, uid_t uid, struct hashentry *he,
00058               struct datahead *dh)
00059 {
00060   /* Search for the entry matching the key.  Please note that we don't
00061      look again in the table whether the dataset is now available.  We
00062      simply insert it.  It does not matter if it is in there twice.  The
00063      pruning function only will look at the timestamp.  */
00064 
00065 
00066   /* We allocate all data in one memory block: the iov vector,
00067      the response header and the dataset itself.  */
00068   struct dataset
00069   {
00070     struct datahead head;
00071     initgr_response_header resp;
00072     char strdata[0];
00073   } *dataset = NULL;
00074 
00075   if (__builtin_expect (debug_level > 0, 0))
00076     {
00077       if (he == NULL)
00078        dbg_log (_("Haven't found \"%s\" in group cache!"), (char *) key);
00079       else
00080        dbg_log (_("Reloading \"%s\" in group cache!"), (char *) key);
00081     }
00082 
00083   static service_user *group_database;
00084   service_user *nip = NULL;
00085   int no_more;
00086 
00087   if (group_database != NULL)
00088     {
00089       nip = group_database;
00090       no_more = 0;
00091     }
00092   else
00093     no_more = __nss_database_lookup ("group", NULL,
00094                                  "compat [NOTFOUND=return] files", &nip);
00095 
00096  /* We always use sysconf even if NGROUPS_MAX is defined.  That way, the
00097      limit can be raised in the kernel configuration without having to
00098      recompile libc.  */
00099   long int limit = __sysconf (_SC_NGROUPS_MAX);
00100 
00101   long int size;
00102   if (limit > 0)
00103     /* We limit the size of the intially allocated array.  */
00104     size = MIN (limit, 64);
00105   else
00106     /* No fixed limit on groups.  Pick a starting buffer size.  */
00107     size = 16;
00108 
00109   long int start = 0;
00110   bool all_tryagain = true;
00111   bool any_success = false;
00112 
00113   /* This is temporary memory, we need not (and must not) call
00114      mempool_alloc.  */
00115   // XXX This really should use alloca.  need to change the backends.
00116   gid_t *groups = (gid_t *) malloc (size * sizeof (gid_t));
00117   if (__builtin_expect (groups == NULL, 0))
00118     /* No more memory.  */
00119     goto out;
00120 
00121   /* Nothing added yet.  */
00122   while (! no_more)
00123     {
00124       long int prev_start = start;
00125       enum nss_status status;
00126       initgroups_dyn_function fct;
00127       fct = __nss_lookup_function (nip, "initgroups_dyn");
00128 
00129       if (fct == NULL)
00130        {
00131          status = compat_call (nip, key, -1, &start, &size, &groups,
00132                             limit, &errno);
00133 
00134          if (nss_next_action (nip, NSS_STATUS_UNAVAIL) != NSS_ACTION_CONTINUE)
00135            break;
00136        }
00137       else
00138        status = DL_CALL_FCT (fct, (key, -1, &start, &size, &groups,
00139                                 limit, &errno));
00140 
00141       /* Remove duplicates.  */
00142       long int cnt = prev_start;
00143       while (cnt < start)
00144        {
00145          long int inner;
00146          for (inner = 0; inner < prev_start; ++inner)
00147            if (groups[inner] == groups[cnt])
00148              break;
00149 
00150          if (inner < prev_start)
00151            groups[cnt] = groups[--start];
00152          else
00153            ++cnt;
00154        }
00155 
00156       if (status != NSS_STATUS_TRYAGAIN)
00157        all_tryagain = false;
00158 
00159       /* This is really only for debugging.  */
00160       if (NSS_STATUS_TRYAGAIN > status || status > NSS_STATUS_RETURN)
00161        __libc_fatal ("illegal status in internal_getgrouplist");
00162 
00163       any_success |= status == NSS_STATUS_SUCCESS;
00164 
00165       if (status != NSS_STATUS_SUCCESS
00166          && nss_next_action (nip, status) == NSS_ACTION_RETURN)
00167         break;
00168 
00169       if (nip->next == NULL)
00170        no_more = -1;
00171       else
00172        nip = nip->next;
00173     }
00174 
00175   ssize_t total;
00176   ssize_t written;
00177  out:
00178   if (!any_success)
00179     {
00180       /* Nothing found.  Create a negative result record.  */
00181       written = total = sizeof (notfound);
00182 
00183       if (he != NULL && all_tryagain)
00184        {
00185          /* If we have an old record available but cannot find one now
00186             because the service is not available we keep the old record
00187             and make sure it does not get removed.  */
00188          if (reload_count != UINT_MAX && dh->nreloads == reload_count)
00189            /* Do not reset the value if we never not reload the record.  */
00190            dh->nreloads = reload_count - 1;
00191        }
00192       else
00193        {
00194          /* We have no data.  This means we send the standard reply for this
00195             case.  */
00196          if (fd != -1)
00197            written = TEMP_FAILURE_RETRY (send (fd, &notfound, total,
00198                                           MSG_NOSIGNAL));
00199 
00200          dataset = mempool_alloc (db, sizeof (struct dataset) + req->key_len,
00201                                IDX_result_data);
00202          /* If we cannot permanently store the result, so be it.  */
00203          if (dataset != NULL)
00204            {
00205              dataset->head.allocsize = sizeof (struct dataset) + req->key_len;
00206              dataset->head.recsize = total;
00207              dataset->head.notfound = true;
00208              dataset->head.nreloads = 0;
00209              dataset->head.usable = true;
00210 
00211              /* Compute the timeout time.  */
00212              dataset->head.timeout = time (NULL) + db->negtimeout;
00213 
00214              /* This is the reply.  */
00215              memcpy (&dataset->resp, &notfound, total);
00216 
00217              /* Copy the key data.  */
00218              char *key_copy = memcpy (dataset->strdata, key, req->key_len);
00219 
00220              /* If necessary, we also propagate the data to disk.  */
00221              if (db->persistent)
00222               {
00223                 // XXX async OK?
00224                 uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
00225                 msync ((void *) pval,
00226                       ((uintptr_t) dataset & pagesize_m1)
00227                       + sizeof (struct dataset) + req->key_len, MS_ASYNC);
00228               }
00229 
00230              /* Now get the lock to safely insert the records.  */
00231              pthread_rwlock_rdlock (&db->lock);
00232 
00233              (void) cache_add (req->type, key_copy, req->key_len,
00234                             &dataset->head, true, db, uid, he == NULL);
00235 
00236              pthread_rwlock_unlock (&db->lock);
00237 
00238              /* Mark the old entry as obsolete.  */
00239              if (dh != NULL)
00240               dh->usable = false;
00241            }
00242          else
00243            ++db->head->addfailed;
00244        }
00245     }
00246   else
00247     {
00248 
00249       written = total = (offsetof (struct dataset, strdata)
00250                       + start * sizeof (int32_t));
00251 
00252       /* If we refill the cache, first assume the reconrd did not
00253         change.  Allocate memory on the cache since it is likely
00254         discarded anyway.  If it turns out to be necessary to have a
00255         new record we can still allocate real memory.  */
00256       bool alloca_used = false;
00257       dataset = NULL;
00258 
00259       if (he == NULL)
00260        {
00261          dataset = (struct dataset *) mempool_alloc (db,
00262                                                 total + req->key_len,
00263                                                 IDX_result_data);
00264          if (dataset == NULL)
00265            ++db->head->addfailed;
00266        }
00267 
00268       if (dataset == NULL)
00269        {
00270          /* We cannot permanently add the result in the moment.  But
00271             we can provide the result as is.  Store the data in some
00272             temporary memory.  */
00273          dataset = (struct dataset *) alloca (total + req->key_len);
00274 
00275          /* We cannot add this record to the permanent database.  */
00276          alloca_used = true;
00277        }
00278 
00279       dataset->head.allocsize = total + req->key_len;
00280       dataset->head.recsize = total - offsetof (struct dataset, resp);
00281       dataset->head.notfound = false;
00282       dataset->head.nreloads = he == NULL ? 0 : (dh->nreloads + 1);
00283       dataset->head.usable = true;
00284 
00285       /* Compute the timeout time.  */
00286       dataset->head.timeout = time (NULL) + db->postimeout;
00287 
00288       dataset->resp.version = NSCD_VERSION;
00289       dataset->resp.found = 1;
00290       dataset->resp.ngrps = start;
00291 
00292       char *cp = dataset->strdata;
00293 
00294       /* Copy the GID values.  If the size of the types match this is
00295         very simple.  */
00296       if (sizeof (gid_t) == sizeof (int32_t))
00297        cp = mempcpy (cp, groups, start * sizeof (gid_t));
00298       else
00299        {
00300          gid_t *gcp = (gid_t *) cp;
00301 
00302          for (int i = 0; i < start; ++i)
00303            *gcp++ = groups[i];
00304 
00305          cp = (char *) gcp;
00306        }
00307 
00308       /* Finally the user name.  */
00309       memcpy (cp, key, req->key_len);
00310 
00311       assert (cp == dataset->strdata + total - offsetof (struct dataset,
00312                                                   strdata));
00313 
00314       /* Now we can determine whether on refill we have to create a new
00315         record or not.  */
00316       if (he != NULL)
00317        {
00318          assert (fd == -1);
00319 
00320          if (total + req->key_len == dh->allocsize
00321              && total - offsetof (struct dataset, resp) == dh->recsize
00322              && memcmp (&dataset->resp, dh->data,
00323                       dh->allocsize - offsetof (struct dataset, resp)) == 0)
00324            {
00325              /* The data has not changed.  We will just bump the
00326                timeout value.  Note that the new record has been
00327                allocated on the stack and need not be freed.  */
00328              dh->timeout = dataset->head.timeout;
00329              ++dh->nreloads;
00330            }
00331          else
00332            {
00333              /* We have to create a new record.  Just allocate
00334                appropriate memory and copy it.  */
00335              struct dataset *newp
00336               = (struct dataset *) mempool_alloc (db, total + req->key_len,
00337                                               IDX_result_data);
00338              if (newp != NULL)
00339               {
00340                 /* Adjust pointer into the memory block.  */
00341                 cp = (char *) newp + (cp - (char *) dataset);
00342 
00343                 dataset = memcpy (newp, dataset, total + req->key_len);
00344                 alloca_used = false;
00345               }
00346              else
00347               ++db->head->addfailed;
00348 
00349              /* Mark the old record as obsolete.  */
00350              dh->usable = false;
00351            }
00352        }
00353       else
00354        {
00355          /* We write the dataset before inserting it to the database
00356             since while inserting this thread might block and so would
00357             unnecessarily let the receiver wait.  */
00358          assert (fd != -1);
00359 
00360 #ifdef HAVE_SENDFILE
00361          if (__builtin_expect (db->mmap_used, 1) && !alloca_used)
00362            {
00363              assert (db->wr_fd != -1);
00364              assert ((char *) &dataset->resp > (char *) db->data);
00365              assert ((char *) &dataset->resp - (char *) db->head
00366                     + total
00367                     <= (sizeof (struct database_pers_head)
00368                        + db->head->module * sizeof (ref_t)
00369                        + db->head->data_size));
00370              written = sendfileall (fd, db->wr_fd,
00371                                  (char *) &dataset->resp
00372                                  - (char *) db->head, total);
00373 # ifndef __ASSUME_SENDFILE
00374              if (written == -1 && errno == ENOSYS)
00375               goto use_write;
00376 # endif
00377            }
00378          else
00379 # ifndef __ASSUME_SENDFILE
00380          use_write:
00381 # endif
00382 #endif
00383            written = writeall (fd, &dataset->resp, total);
00384        }
00385 
00386 
00387       /* Add the record to the database.  But only if it has not been
00388         stored on the stack.  */
00389       if (! alloca_used)
00390        {
00391          /* If necessary, we also propagate the data to disk.  */
00392          if (db->persistent)
00393            {
00394              // XXX async OK?
00395              uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
00396              msync ((void *) pval,
00397                    ((uintptr_t) dataset & pagesize_m1) + total +
00398                    req->key_len, MS_ASYNC);
00399            }
00400 
00401          /* Now get the lock to safely insert the records.  */
00402          pthread_rwlock_rdlock (&db->lock);
00403 
00404          (void) cache_add (INITGROUPS, cp, req->key_len, &dataset->head, true,
00405                          db, uid, he == NULL);
00406 
00407          pthread_rwlock_unlock (&db->lock);
00408        }
00409     }
00410 
00411   free (groups);
00412 
00413   if (__builtin_expect (written != total, 0) && debug_level > 0)
00414     {
00415       char buf[256];
00416       dbg_log (_("short write in %s: %s"), __FUNCTION__,
00417               strerror_r (errno, buf, sizeof (buf)));
00418     }
00419 }
00420 
00421 
00422 void
00423 addinitgroups (struct database_dyn *db, int fd, request_header *req, void *key,
00424               uid_t uid)
00425 {
00426   addinitgroupsX (db, fd, req, key, uid, NULL, NULL);
00427 }
00428 
00429 
00430 void
00431 readdinitgroups (struct database_dyn *db, struct hashentry *he,
00432                struct datahead *dh)
00433 {
00434   request_header req =
00435     {
00436       .type = INITGROUPS,
00437       .key_len = he->len
00438     };
00439 
00440   addinitgroupsX (db, -1, &req, db->data + he->key, he->owner, he, dh);
00441 }