Add a few casts and __builtin_expect.
[kopensolaris-gnu/glibc.git] / nscd / pwdcache.c
1 /* Cache handling for passwd lookup.
2    Copyright (C) 1998, 1999, 2000, 2001, 2002 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 <errno.h>
22 #include <error.h>
23 #include <pwd.h>
24 #include <stdbool.h>
25 #include <stddef.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <time.h>
30 #include <unistd.h>
31 #include <libintl.h>
32 #include <stackinfo.h>
33
34 #include "nscd.h"
35 #include "dbg_log.h"
36
37 /* This is the standard reply in case the service is disabled.  */
38 static const pw_response_header disabled =
39 {
40   version: NSCD_VERSION,
41   found: -1,
42   pw_name_len: 0,
43   pw_passwd_len: 0,
44   pw_uid: -1,
45   pw_gid: -1,
46   pw_gecos_len: 0,
47   pw_dir_len: 0,
48   pw_shell_len: 0
49 };
50
51 /* This is the struct describing how to write this record.  */
52 const struct iovec pwd_iov_disabled =
53 {
54   iov_base: (void *) &disabled,
55   iov_len: sizeof (disabled)
56 };
57
58
59 /* This is the standard reply in case we haven't found the dataset.  */
60 static const pw_response_header notfound =
61 {
62   version: NSCD_VERSION,
63   found: 0,
64   pw_name_len: 0,
65   pw_passwd_len: 0,
66   pw_uid: -1,
67   pw_gid: -1,
68   pw_gecos_len: 0,
69   pw_dir_len: 0,
70   pw_shell_len: 0
71 };
72
73 /* This is the struct describing how to write this record.  */
74 static const struct iovec iov_notfound =
75 {
76   iov_base: (void *) &notfound,
77   iov_len: sizeof (notfound)
78 };
79
80
81 struct passwddata
82 {
83   pw_response_header resp;
84   char strdata[0];
85 };
86
87
88 static void
89 cache_addpw (struct database *db, int fd, request_header *req, void *key,
90              struct passwd *pwd, uid_t owner)
91 {
92   ssize_t total;
93   ssize_t written;
94   time_t t = time (NULL);
95
96   if (pwd == NULL)
97     {
98       /* We have no data.  This means we send the standard reply for this
99          case.  */
100       void *copy;
101
102       total = sizeof (notfound);
103
104       written = writev (fd, &iov_notfound, 1);
105
106       copy = malloc (req->key_len);
107       if (copy == NULL)
108         error (EXIT_FAILURE, errno, _("while allocating key copy"));
109       memcpy (copy, key, req->key_len);
110
111       /* Compute the timeout time.  */
112       t += db->negtimeout;
113
114       /* Now get the lock to safely insert the records.  */
115       pthread_rwlock_rdlock (&db->lock);
116
117       cache_add (req->type, copy, req->key_len, &notfound,
118                  sizeof (notfound), (void *) -1, 0, t, db, owner);
119
120       pthread_rwlock_unlock (&db->lock);
121     }
122   else
123     {
124       /* Determine the I/O structure.  */
125       struct passwddata *data;
126       size_t pw_name_len = strlen (pwd->pw_name) + 1;
127       size_t pw_passwd_len = strlen (pwd->pw_passwd) + 1;
128       size_t pw_gecos_len = strlen (pwd->pw_gecos) + 1;
129       size_t pw_dir_len = strlen (pwd->pw_dir) + 1;
130       size_t pw_shell_len = strlen (pwd->pw_shell) + 1;
131       char *cp;
132       char buf[12];
133       ssize_t n;
134
135       /* We need this to insert the `byuid' entry.  */
136       n = snprintf (buf, sizeof (buf), "%d", pwd->pw_uid) + 1;
137
138       /* We allocate all data in one memory block: the iov vector,
139          the response header and the dataset itself.  */
140       total = (sizeof (struct passwddata) + pw_name_len + pw_passwd_len
141                + pw_gecos_len + pw_dir_len + pw_shell_len);
142       data = (struct passwddata *) malloc (total + n);
143       if (data == NULL)
144         /* There is no reason to go on.  */
145         error (EXIT_FAILURE, errno, _("while allocating cache entry"));
146
147       data->resp.found = 1;
148       data->resp.pw_name_len = pw_name_len;
149       data->resp.pw_passwd_len = pw_passwd_len;
150       data->resp.pw_uid = pwd->pw_uid;
151       data->resp.pw_gid = pwd->pw_gid;
152       data->resp.pw_gecos_len = pw_gecos_len;
153       data->resp.pw_dir_len = pw_dir_len;
154       data->resp.pw_shell_len = pw_shell_len;
155
156       cp = data->strdata;
157
158       /* Copy the strings over into the buffer.  */
159       cp = mempcpy (cp, pwd->pw_name, pw_name_len);
160       cp = mempcpy (cp, pwd->pw_passwd, pw_passwd_len);
161       cp = mempcpy (cp, pwd->pw_gecos, pw_gecos_len);
162       cp = mempcpy (cp, pwd->pw_dir, pw_dir_len);
163       cp = mempcpy (cp, pwd->pw_shell, pw_shell_len);
164
165       /* Finally the stringified UID value.  */
166       memcpy (cp, buf, n);
167
168       /* We write the dataset before inserting it to the database
169          since while inserting this thread might block and so would
170          unnecessarily let the receiver wait.  */
171       written = TEMP_FAILURE_RETRY (write (fd, &data->resp, total));
172
173       /* Compute the timeout time.  */
174       t += db->postimeout;
175
176       /* Now get the lock to safely insert the records.  */
177       pthread_rwlock_rdlock (&db->lock);
178
179       /* We have to add the value for both, byname and byuid.  */
180       cache_add (GETPWBYNAME, data->strdata, pw_name_len, data,
181                  total, data, 0, t, db, owner);
182
183       cache_add (GETPWBYUID, cp, n, data, total, data, 1, t, db, owner);
184
185       pthread_rwlock_unlock (&db->lock);
186     }
187
188   if (__builtin_expect (written != total, 0) && debug_level > 0)
189     {
190       char buf[256];
191       dbg_log (_("short write in %s: %s"),  __FUNCTION__,
192                strerror_r (errno, buf, sizeof (buf)));
193     }
194 }
195
196
197 void
198 addpwbyname (struct database *db, int fd, request_header *req,
199              void *key, uid_t c_uid)
200 {
201   /* Search for the entry matching the key.  Please note that we don't
202      look again in the table whether the dataset is now available.  We
203      simply insert it.  It does not matter if it is in there twice.  The
204      pruning function only will look at the timestamp.  */
205   int buflen = 1024;
206   char *buffer = (char *) alloca (buflen);
207   struct passwd resultbuf;
208   struct passwd *pwd;
209   uid_t oldeuid = 0;
210   bool use_malloc = false;
211
212   if (__builtin_expect (debug_level > 0, 0))
213     dbg_log (_("Haven't found \"%s\" in password cache!"), (char *) key);
214
215   if (secure[pwddb])
216     {
217       oldeuid = geteuid ();
218       seteuid (c_uid);
219     }
220
221   while (__getpwnam_r (key, &resultbuf, buffer, buflen, &pwd) != 0
222          && errno == ERANGE)
223     {
224       char *old_buffer = buffer;
225       errno = 0;
226       buflen += 1024;
227
228       if (__builtin_expect (buflen > 32768, 0))
229         {
230           buffer = (char *) realloc (use_malloc ? buffer : NULL, buflen);
231           if (buffer == NULL)
232             {
233               /* We ran out of memory.  We cannot do anything but
234                  sending a negative response.  In reality this should
235                  never happen.  */
236               pwd = NULL;
237               buffer = old_buffer;
238               break;
239             }
240           use_malloc = true;
241         }
242       else
243         {
244           buffer = (char *) alloca (buflen);
245 #if _STACK_GROWS_DOWN
246           if (buffer + buflen == old_buffer)
247             buflen = 2 * buflen - 1024;
248 #elif _STACK_GROWS_UP
249           if (old_buffer + buflen - 1024 == buffer)
250             {
251               buffer = old_buffer;
252               buflen = 2 * buflen - 1024;
253             }
254 #endif
255         }
256     }
257
258   if (secure[pwddb])
259     seteuid (oldeuid);
260
261   cache_addpw (db, fd, req, key, pwd, c_uid);
262
263   if (use_malloc)
264     free (buffer);
265 }
266
267
268 void
269 addpwbyuid (struct database *db, int fd, request_header *req,
270             void *key, uid_t c_uid)
271 {
272   /* Search for the entry matching the key.  Please note that we don't
273      look again in the table whether the dataset is now available.  We
274      simply insert it.  It does not matter if it is in there twice.  The
275      pruning function only will look at the timestamp.  */
276   int buflen = 256;
277   char *buffer = (char *) alloca (buflen);
278   struct passwd resultbuf;
279   struct passwd *pwd;
280   uid_t oldeuid = 0;
281   char *ep;
282   uid_t uid = strtoul ((char *) key, &ep, 10);
283   bool use_malloc = false;
284
285   if (*(char *) key == '\0' || *ep != '\0')  /* invalid numeric uid */
286     {
287       if (debug_level > 0)
288         dbg_log (_("Invalid numeric uid \"%s\"!"), (char *) key);
289
290       errno = EINVAL;
291       return;
292     }
293
294   if (__builtin_expect (debug_level > 0, 0))
295     dbg_log (_("Haven't found \"%d\" in password cache!"), uid);
296
297   if (secure[pwddb])
298     {
299       oldeuid = geteuid ();
300       seteuid (c_uid);
301     }
302
303   while (__getpwuid_r (uid, &resultbuf, buffer, buflen, &pwd) != 0
304          && errno == ERANGE)
305     {
306       char *old_buffer = buffer;
307       errno = 0;
308       buflen += 1024;
309
310       if (__builtin_expect (buflen > 32768, 0))
311         {
312           buffer = (char *) realloc (use_malloc ? buffer : NULL, buflen);
313           if (buffer == NULL)
314             {
315               /* We ran out of memory.  We cannot do anything but
316                  sending a negative response.  In reality this should
317                  never happen.  */
318               pwd = NULL;
319               buffer = old_buffer;
320               break;
321             }
322           use_malloc = true;
323         }
324       else
325         {
326           buffer = (char *) alloca (buflen);
327 #if _STACK_GROWS_DOWN
328           if (buffer + buflen == old_buffer)
329             buflen = 2 * buflen - 1024;
330 #elif _STACK_GROWS_UP
331           if (old_buffer + buflen - 1024 == buffer)
332             {
333               buffer = old_buffer;
334               buflen = 2 * buflen - 1024;
335             }
336 #endif
337         }
338     }
339
340   if (secure[pwddb])
341     seteuid (oldeuid);
342
343   cache_addpw (db, fd, req, key, pwd, c_uid);
344
345   if (use_malloc)
346     free (buffer);
347 }