xref: /titanic_50/usr/src/cmd/nscd/nscd_getentctx.c (revision 407eb7cc0bcf1bc14f8cd498d68af0782e777f74)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 #include <stdlib.h>
29 #include <assert.h>
30 #include <string.h>
31 #include <errno.h>
32 #include <fcntl.h>
33 #include "nscd_db.h"
34 #include "nscd_log.h"
35 #include "nscd_switch.h"
36 #include "nscd_door.h"
37 
38 extern int		_whoami;
39 static mutex_t		getent_monitor_mutex = DEFAULTMUTEX;
40 static int		getent_monitor_started = 0;
41 
42 static rwlock_t		getent_ctxDB_rwlock = DEFAULTRWLOCK;
43 static nscd_db_t	*getent_ctxDB = NULL;
44 
45 /*
46  * internal structure representing a nscd getent context
47  */
48 typedef struct nscd_getent_ctx {
49 	int			to_delete; /* this ctx no longer valid */
50 	nscd_getent_context_t	*ptr;
51 	nscd_cookie_t		cookie;
52 } nscd_getent_ctx_t;
53 
54 /*
55  * nscd_getent_context_t list for each nss database. Protected
56  * by the readers/writer lock nscd_getent_ctx_lock.
57  */
58 nscd_getent_ctx_base_t **nscd_getent_ctx_base;
59 static rwlock_t nscd_getent_ctx_base_lock = DEFAULTRWLOCK;
60 
61 extern nscd_db_entry_t *_nscd_walk_db(nscd_db_t *db, void **cookie);
62 
63 static nscd_rc_t _nscd_init_getent_ctx_monitor();
64 
65 /*
66  * FUNCTION: _nscd_create_getent_ctxDB
67  *
68  * Create the internal getent context database to keep track of the
69  * getent contexts currently being used.
70  */
71 nscd_db_t *
72 _nscd_create_getent_ctxDB()
73 {
74 
75 	nscd_db_t	*ret;
76 
77 	(void) rw_wrlock(&getent_ctxDB_rwlock);
78 
79 	if (getent_ctxDB != NULL) {
80 		(void) rw_unlock(&getent_ctxDB_rwlock);
81 		return (getent_ctxDB);
82 	}
83 
84 	ret = _nscd_alloc_db(NSCD_DB_SIZE_LARGE);
85 
86 	if (ret != NULL)
87 		getent_ctxDB = ret;
88 
89 	(void) rw_unlock(&getent_ctxDB_rwlock);
90 
91 	return (ret);
92 }
93 
94 /*
95  * FUNCTION: _nscd_add_getent_ctx
96  *
97  * Add a getent context to the internal context database.
98  */
99 static nscd_rc_t
100 _nscd_add_getent_ctx(
101 	nscd_getent_context_t	*ptr,
102 	nscd_cookie_t		cookie)
103 {
104 	int			size;
105 	char			buf[2 * sizeof (cookie) + 1];
106 	nscd_db_entry_t		*db_entry;
107 	nscd_getent_ctx_t	*gnctx;
108 
109 	if (ptr == NULL)
110 		return (NSCD_INVALID_ARGUMENT);
111 
112 	(void) snprintf(buf, sizeof (buf), "%lld", cookie);
113 
114 	size = sizeof (*gnctx);
115 
116 	db_entry = _nscd_alloc_db_entry(NSCD_DATA_CTX_ADDR,
117 			(const char *)buf, size, 1, 1);
118 	if (db_entry == NULL)
119 		return (NSCD_NO_MEMORY);
120 
121 	gnctx = (nscd_getent_ctx_t *)*(db_entry->data_array);
122 	gnctx->ptr = ptr;
123 	gnctx->cookie = cookie;
124 
125 	(void) rw_wrlock(&getent_ctxDB_rwlock);
126 	(void) _nscd_add_db_entry(getent_ctxDB, buf, db_entry,
127 		NSCD_ADD_DB_ENTRY_FIRST);
128 	(void) rw_unlock(&getent_ctxDB_rwlock);
129 
130 	return (NSCD_SUCCESS);
131 }
132 
133 /*
134  * FUNCTION: _nscd_is_getent_ctx
135  *
136  * Check to see if a getent context can be found in the internal
137  * getent context database.
138  */
139 nscd_getent_context_t *
140 _nscd_is_getent_ctx(
141 	nscd_cookie_t		cookie)
142 {
143 	char			ptrstr[1 + 2 * sizeof (cookie)];
144 	const nscd_db_entry_t	*db_entry;
145 	nscd_getent_context_t	*ret = NULL;
146 
147 	(void) snprintf(ptrstr, sizeof (ptrstr), "%lld", cookie);
148 
149 	(void) rw_rdlock(&getent_ctxDB_rwlock);
150 
151 	db_entry = _nscd_get_db_entry(getent_ctxDB, NSCD_DATA_CTX_ADDR,
152 		(const char *)ptrstr, NSCD_GET_FIRST_DB_ENTRY, 0);
153 
154 	if (db_entry != NULL) {
155 		nscd_getent_ctx_t *gnctx;
156 
157 		gnctx = (nscd_getent_ctx_t *)*(db_entry->data_array);
158 
159 		/*
160 		 * If the ctx is not to be deleted and
161 		 * the cookie numbers match, return the ctx.
162 		 * Otherwise return NULL.
163 		 */
164 		if (gnctx->to_delete == 0 && gnctx->cookie == cookie)
165 			ret = gnctx->ptr;
166 	}
167 
168 	(void) rw_unlock(&getent_ctxDB_rwlock);
169 
170 	return (ret);
171 }
172 
173 /*
174  * FUNCTION: _nscd_del_getent_ctx
175  *
176  * Delete a getent context from the internal getent context database.
177  */
178 static void
179 _nscd_del_getent_ctx(
180 	nscd_getent_context_t	*ptr,
181 	nscd_cookie_t		cookie)
182 {
183 	char			ptrstr[1 + 2 * sizeof (cookie)];
184 	nscd_getent_ctx_t	*gnctx;
185 	const nscd_db_entry_t	*db_entry;
186 
187 	if (ptr == NULL)
188 		return;
189 
190 	(void) snprintf(ptrstr, sizeof (ptrstr), "%lld", cookie);
191 
192 	(void) rw_rdlock(&getent_ctxDB_rwlock);
193 	/*
194 	 * first find the db entry and make sure the
195 	 * sequence number matched, then delete it from
196 	 * the database.
197 	 */
198 	db_entry = _nscd_get_db_entry(getent_ctxDB,
199 		NSCD_DATA_CTX_ADDR,
200 		(const char *)ptrstr,
201 		NSCD_GET_FIRST_DB_ENTRY, 0);
202 	if (db_entry != NULL) {
203 		gnctx = (nscd_getent_ctx_t *)*(db_entry->data_array);
204 		if (gnctx->ptr == ptr && gnctx->cookie  == cookie) {
205 
206 			(void) rw_unlock(&getent_ctxDB_rwlock);
207 			(void) rw_wrlock(&getent_ctxDB_rwlock);
208 
209 			(void) _nscd_delete_db_entry(getent_ctxDB,
210 				NSCD_DATA_CTX_ADDR,
211 				(const char *)ptrstr,
212 				NSCD_DEL_FIRST_DB_ENTRY, 0);
213 		}
214 	}
215 	(void) rw_unlock(&getent_ctxDB_rwlock);
216 }
217 
218 static void
219 _nscd_free_getent_ctx(
220 	nscd_getent_context_t	*gnctx)
221 {
222 
223 	char			*me = "_nscd_free_getent_ctx";
224 
225 	_NSCD_LOG(NSCD_LOG_GETENT_CTX, NSCD_LOG_LEVEL_DEBUG)
226 	(me, "getent context %p\n", gnctx);
227 
228 	_nscd_put_nsw_state(gnctx->nsw_state);
229 	_nscd_del_getent_ctx(gnctx, gnctx->cookie);
230 	free(gnctx);
231 }
232 
233 
234 static void
235 _nscd_free_getent_ctx_base(
236 	nscd_acc_data_t		*data)
237 {
238 	nscd_getent_ctx_base_t	*base = (nscd_getent_ctx_base_t *)data;
239 	nscd_getent_context_t	*c, *tc;
240 	char			*me = "_nscd_free_getent_ctx_base";
241 
242 	_NSCD_LOG(NSCD_LOG_GETENT_CTX | NSCD_LOG_CONFIG, NSCD_LOG_LEVEL_DEBUG)
243 	(me, "getent context base %p\n", base);
244 
245 	if (base == NULL)
246 		return;
247 
248 	c = base->first;
249 	while (c != NULL) {
250 		tc = c->next;
251 		_nscd_free_getent_ctx(c);
252 		c = tc;
253 	}
254 }
255 
256 void
257 _nscd_free_all_getent_ctx_base()
258 {
259 	nscd_getent_ctx_base_t	*base;
260 	int			i;
261 	char			*me = "_nscd_free_all_getent_ctx_base";
262 
263 	_NSCD_LOG(NSCD_LOG_GETENT_CTX | NSCD_LOG_CONFIG, NSCD_LOG_LEVEL_DEBUG)
264 	(me, "entering ..\n");
265 
266 	(void) rw_wrlock(&nscd_getent_ctx_base_lock);
267 
268 	for (i = 0; i < NSCD_NUM_DB; i++) {
269 
270 		base = nscd_getent_ctx_base[i];
271 		if (base == NULL)
272 			continue;
273 
274 		nscd_getent_ctx_base[i] = (nscd_getent_ctx_base_t *)
275 			_nscd_set((nscd_acc_data_t *)base, NULL);
276 	}
277 	(void) rw_unlock(&nscd_getent_ctx_base_lock);
278 }
279 
280 static nscd_getent_context_t *
281 _nscd_create_getent_ctx(
282 	nscd_nsw_params_t	*params)
283 {
284 	nscd_getent_context_t	*gnctx;
285 	nss_db_root_t		db_root;
286 	char			*me = "_nscd_create_getent_ctx";
287 
288 	gnctx = calloc(1, sizeof (nscd_getent_context_t));
289 	if (gnctx == NULL)
290 		return (NULL);
291 	else {
292 		_NSCD_LOG(NSCD_LOG_GETENT_CTX, NSCD_LOG_LEVEL_DEBUG)
293 		(me, "getent context allocated %p\n", gnctx);
294 	}
295 
296 	gnctx->dbi = params->dbi;
297 	gnctx->cookie = _nscd_get_cookie();
298 
299 	if (_nscd_get_nsw_state(&db_root, params) != NSCD_SUCCESS) {
300 		free(gnctx);
301 		return (NULL);
302 	}
303 	gnctx->nsw_state = (nscd_nsw_state_t *)db_root.s;
304 	/* this is a nsw_state used for getent processing */
305 	gnctx->nsw_state->getent = 1;
306 
307 	_NSCD_LOG(NSCD_LOG_GETENT_CTX, NSCD_LOG_LEVEL_DEBUG)
308 	(me, "got nsw_state %p\n", gnctx->nsw_state);
309 
310 	return (gnctx);
311 }
312 
313 
314 nscd_rc_t
315 _nscd_get_getent_ctx(
316 	nss_getent_t		*contextpp,
317 	nscd_nsw_params_t	*params)
318 {
319 
320 	nscd_getent_context_t	*c;
321 	nscd_getent_ctx_base_t	*base, *tmp;
322 	nscd_rc_t		rc;
323 	char			*me = "_nscd_get_getent_ctx";
324 
325 	_NSCD_LOG(NSCD_LOG_GETENT_CTX, NSCD_LOG_LEVEL_DEBUG)
326 	(me, "entering ...\n");
327 
328 	(void) rw_rdlock(&nscd_getent_ctx_base_lock);
329 	base = nscd_getent_ctx_base[params->dbi];
330 	(void) rw_unlock(&nscd_getent_ctx_base_lock);
331 	assert(base != NULL);
332 
333 	/*
334 	 * If the context list is not empty, return the first one
335 	 * on the list. Otherwise, create and return a new one if
336 	 * limit is not reached. if reacehed, wait for the 'one is
337 	 * available' signal.
338 	 */
339 	tmp = (nscd_getent_ctx_base_t *)_nscd_mutex_lock(
340 		(nscd_acc_data_t *)base);
341 	assert(base == tmp);
342 	if (base->first == NULL) {
343 		if (base->num_getent_ctx == base->max_getent_ctx) {
344 			base->num_waiter++;
345 			while (base->first == NULL) {
346 
347 				_NSCD_LOG(NSCD_LOG_GETENT_CTX,
348 					NSCD_LOG_LEVEL_DEBUG)
349 				(me, "waiting for signal\n");
350 
351 				_nscd_cond_wait((nscd_acc_data_t *)base, NULL);
352 
353 				_NSCD_LOG(NSCD_LOG_GETENT_CTX,
354 					NSCD_LOG_LEVEL_DEBUG)
355 				(me, "woke up\n");
356 			}
357 			base->num_waiter--;
358 		} else {
359 			base->first = _nscd_create_getent_ctx(params);
360 			if (base->first != NULL) {
361 				base->first->base = base;
362 				base->num_getent_ctx++;
363 			} else {
364 				/* not able to create an getent ctx */
365 
366 				_NSCD_LOG(NSCD_LOG_GETENT_CTX,
367 					NSCD_LOG_LEVEL_ERROR)
368 				(me, "create getent ctx failed\n");
369 
370 				_nscd_mutex_unlock((nscd_acc_data_t *)base);
371 				return (NSCD_CREATE_GETENT_CTX_FAILED);
372 			}
373 
374 			_NSCD_LOG(NSCD_LOG_GETENT_CTX, NSCD_LOG_LEVEL_DEBUG)
375 			(me, "got a new getent ctx %p\n", base->first);
376 		}
377 	}
378 
379 	assert(base->first != NULL);
380 
381 	c = base->first;
382 	base->first = c->next;
383 	c->next = NULL;
384 	c->seq_num = 1;
385 
386 	_NSCD_LOG(NSCD_LOG_GETENT_CTX, NSCD_LOG_LEVEL_DEBUG)
387 	(me, "got a getent ctx %p\n", c);
388 
389 	_nscd_mutex_unlock((nscd_acc_data_t *)base);
390 
391 	_NSCD_LOG(NSCD_LOG_GETENT_CTX, NSCD_LOG_LEVEL_DEBUG)
392 	(me, "adding new ctx %p, cookie = %lld\n", c, c->cookie);
393 
394 	if ((rc = _nscd_add_getent_ctx(c, c->cookie)) != NSCD_SUCCESS) {
395 		_nscd_put_getent_ctx(c);
396 		return (rc);
397 	}
398 	contextpp->ctx = (struct nss_getent_context *)c;
399 
400 	/* start monitor and reclaim orphan getent context */
401 	if (getent_monitor_started == 0) {
402 		(void) mutex_lock(&getent_monitor_mutex);
403 		if (getent_monitor_started == 0) {
404 			getent_monitor_started = 1;
405 			(void) _nscd_init_getent_ctx_monitor();
406 		}
407 		(void) mutex_unlock(&getent_monitor_mutex);
408 	}
409 
410 	return (NSCD_SUCCESS);
411 }
412 
413 void
414 _nscd_put_getent_ctx(
415 	nscd_getent_context_t	*gnctx)
416 {
417 
418 	nscd_getent_ctx_base_t	*base;
419 	char			*me = "_nscd_put_getent_ctx";
420 
421 	base = gnctx->base;
422 	gnctx->seq_num = 0;
423 
424 	/* if context base is gone, so should this context */
425 	if ((_nscd_mutex_lock((nscd_acc_data_t *)base)) == NULL) {
426 		_nscd_free_getent_ctx(gnctx);
427 		return;
428 	}
429 
430 	if (base->first != NULL) {
431 		gnctx->next = base->first;
432 		base->first = gnctx;
433 	} else
434 		base->first = gnctx;
435 
436 	/* put back the db state */
437 	_NSCD_LOG(NSCD_LOG_GETENT_CTX, NSCD_LOG_LEVEL_DEBUG)
438 	(me, "putting back nsw state %p\n", gnctx->nsw_state);
439 
440 	/* this nsw_state is no longer used for getent processing */
441 	if (gnctx->nsw_state != NULL)
442 		gnctx->nsw_state->getent = 0;
443 	_nscd_put_nsw_state(gnctx->nsw_state);
444 	gnctx->nsw_state = NULL;
445 
446 	_nscd_del_getent_ctx(gnctx, gnctx->cookie);
447 
448 	_NSCD_LOG(NSCD_LOG_GETENT_CTX, NSCD_LOG_LEVEL_DEBUG)
449 	(me, "ctx (%p seq# = %lld) removed from getent ctx DB\n",
450 		gnctx, gnctx->cookie);
451 
452 	if (base->num_waiter > 0) {
453 		_NSCD_LOG(NSCD_LOG_GETENT_CTX, NSCD_LOG_LEVEL_DEBUG)
454 		(me, "signaling (waiter = %d)\n", base->num_waiter);
455 
456 		_nscd_cond_signal((nscd_acc_data_t *)base);
457 	}
458 
459 	_nscd_mutex_unlock((nscd_acc_data_t *)base);
460 }
461 
462 nscd_rc_t
463 _nscd_init_getent_ctx_base(
464 	int			dbi,
465 	int			lock)
466 {
467 	nscd_getent_ctx_base_t	*base = NULL;
468 	char			*me = "_nscd_init_getent_ctx_base";
469 
470 	if (lock)
471 		(void) rw_rdlock(&nscd_getent_ctx_base_lock);
472 
473 	base = (nscd_getent_ctx_base_t *)_nscd_alloc(
474 		NSCD_DATA_GETENT_CTX_BASE,
475 		sizeof (nscd_getent_ctx_base_t),
476 		_nscd_free_getent_ctx_base,
477 		NSCD_ALLOC_MUTEX | NSCD_ALLOC_COND);
478 
479 	if (base == NULL) {
480 		if (lock)
481 			(void) rw_unlock(&nscd_getent_ctx_base_lock);
482 		return (NSCD_NO_MEMORY);
483 	}
484 	_NSCD_LOG(NSCD_LOG_GETENT_CTX | NSCD_LOG_CONFIG, NSCD_LOG_LEVEL_DEBUG)
485 	(me, "base %p allocated\n", base);
486 
487 	/*
488 	 * initialize and activate the new getent_ctx base
489 	 */
490 	base->dbi = dbi;
491 	base->max_getent_ctx = NSCD_SW_CFG(dbi).max_getent_ctx_per_db;
492 	nscd_getent_ctx_base[dbi] =
493 		(nscd_getent_ctx_base_t *)_nscd_set(
494 		(nscd_acc_data_t *)nscd_getent_ctx_base[dbi],
495 		(nscd_acc_data_t *)base);
496 
497 	if (lock)
498 		(void) rw_unlock(&nscd_getent_ctx_base_lock);
499 
500 	return (NSCD_SUCCESS);
501 }
502 
503 nscd_rc_t
504 _nscd_init_all_getent_ctx_base()
505 {
506 	int			i;
507 	nscd_rc_t		rc;
508 	char			*me = "_nscd_init_all_getent_ctx_base";
509 
510 	(void) rw_wrlock(&nscd_getent_ctx_base_lock);
511 
512 	for (i = 0; i < NSCD_NUM_DB; i++) {
513 
514 		rc = _nscd_init_getent_ctx_base(i, 0);
515 
516 		if (rc != NSCD_SUCCESS) {
517 			(void) rw_unlock(&nscd_getent_ctx_base_lock);
518 			return (rc);
519 		}
520 	}
521 
522 	_NSCD_LOG(NSCD_LOG_GETENT_CTX | NSCD_LOG_CONFIG, NSCD_LOG_LEVEL_DEBUG)
523 	(me, "all getent context base initialized\n");
524 
525 	(void) rw_unlock(&nscd_getent_ctx_base_lock);
526 
527 	return (NSCD_SUCCESS);
528 }
529 nscd_rc_t
530 _nscd_alloc_getent_ctx_base()
531 {
532 
533 	(void) rw_wrlock(&nscd_getent_ctx_base_lock);
534 
535 	nscd_getent_ctx_base = calloc(NSCD_NUM_DB,
536 			sizeof (nscd_getent_ctx_base_t *));
537 	if (nscd_getent_ctx_base == NULL) {
538 		(void) rw_unlock(&nscd_getent_ctx_base_lock);
539 		return (NSCD_NO_MEMORY);
540 	}
541 
542 	(void) rw_unlock(&nscd_getent_ctx_base_lock);
543 
544 	return (NSCD_SUCCESS);
545 }
546 
547 static int
548 process_exited(pid_t pid)
549 {
550 	char	pname[PATH_MAX];
551 	int	fd;
552 
553 	(void) snprintf(pname, sizeof (pname), "/proc/%d/psinfo", pid);
554 	if ((fd = open(pname, O_RDONLY)) == -1)
555 		return (1);
556 	else {
557 		(void) close(fd);
558 		return (0);
559 	}
560 }
561 
562 /*
563  * FUNCTION: reclaim_getent_ctx
564  */
565 /*ARGSUSED*/
566 static void *
567 reclaim_getent_ctx(void *arg)
568 {
569 	void			*cookie = NULL;
570 	nscd_db_entry_t		*ep;
571 	nscd_getent_ctx_t	*ctx;
572 	nscd_getent_context_t	*gctx, *c;
573 	nscd_getent_context_t	*first = NULL, *last = NULL;
574 	char			*me = "reclaim_getent_ctx";
575 
576 	/*CONSTCOND*/
577 	while (1) {
578 
579 		(void) rw_rdlock(&getent_ctxDB_rwlock);
580 
581 		for (ep = _nscd_walk_db(getent_ctxDB, &cookie); ep != NULL;
582 				ep = _nscd_walk_db(getent_ctxDB, &cookie)) {
583 
584 			ctx = (nscd_getent_ctx_t *)*(ep->data_array);
585 
586 			gctx = ctx->ptr;
587 
588 			/*
589 			 * if the client process, which did the setent,
590 			 * exited, add the context to the orphan list
591 			 */
592 			if (gctx->pid != -1 && process_exited(gctx->pid)) {
593 
594 				_NSCD_LOG(NSCD_LOG_GETENT_CTX,
595 					NSCD_LOG_LEVEL_DEBUG)
596 				(me, "process  %d exited, "
597 				"getent context = %p, "
598 				"db index = %d, cookie = %lld, "
599 				"sequence # = %lld\n",
600 				gctx->pid, gctx, gctx->dbi,
601 				gctx->cookie, gctx->seq_num);
602 
603 				if (first != NULL) {
604 					last->next = gctx;
605 					last = gctx;
606 				} else {
607 					first = gctx;
608 					last = gctx;
609 				}
610 			}
611 		}
612 
613 		(void) rw_unlock(&getent_ctxDB_rwlock);
614 
615 
616 		/*
617 		 * return all the orphan getent contexts to the pool
618 		 */
619 		for (gctx = first; gctx; ) {
620 			c = gctx->next;
621 			gctx->next = NULL;
622 			_nscd_put_getent_ctx(gctx);
623 			gctx = c;
624 		}
625 		first = last = NULL;
626 
627 		(void) sleep(60);
628 	}
629 	/*NOTREACHED*/
630 	/*LINTED E_FUNC_HAS_NO_RETURN_STMT*/
631 }
632 
633 static nscd_rc_t
634 _nscd_init_getent_ctx_monitor() {
635 
636 	int	errnum;
637 	char	*me = "_nscd_init_getent_ctx_monitor";
638 
639 	_NSCD_LOG(NSCD_LOG_GETENT_CTX, NSCD_LOG_LEVEL_DEBUG)
640 	(me, "initializing the getent context monitor\n");
641 
642 	/*
643 	 * the forker nscd does not process getent requests
644 	 * so no need to monitor orphan getent contexts
645 	 */
646 	if (_whoami == NSCD_FORKER)
647 		return (NSCD_SUCCESS);
648 
649 	/*
650 	 * start a thread to reclaim unused getent contexts
651 	 */
652 	if (thr_create(NULL, NULL, reclaim_getent_ctx,
653 		NULL, THR_DETACHED, NULL) != 0) {
654 		errnum = errno;
655 		_NSCD_LOG(NSCD_LOG_GETENT_CTX, NSCD_LOG_LEVEL_ERROR)
656 		(me, "thr_create: %s\n", strerror(errnum));
657 		return (NSCD_THREAD_CREATE_ERROR);
658 	}
659 
660 	return (NSCD_SUCCESS);
661 }
662