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.
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.
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]
22 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
25 * Copyright 2018 Joyent, Inc.
34 #include <sys/types.h>
41 extern admin_t current_admin
;
43 #define CLEANUP_WAIT_TIME 60
45 typedef enum cleanup_type
{
50 typedef struct cleanup_op
{
55 typedef struct main_nscd_struct
{
56 pid_t pid
; /* main nscd pid */
57 thread_t tid
; /* main nscd tid */
58 int in_progress
; /* A main nscd thread is */
59 /* waiting for change or */
61 int is_waiting_cleanup
; /* A main nscd thread is */
62 /* waiting for another main */
63 /* nscd thread to be cleaned */
67 static chg_info_t chg
= { DEFAULTMUTEX
, DEFAULTCV
, 0, NULL
, NULL
, NULL
, 0 };
69 static main_nscd_t chg_main_nscd
= {0, 0, 0, 0};
70 static mutex_t chg_main_nscd_lock
= DEFAULTMUTEX
;
71 static cond_t chg_main_nscd_cv
= DEFAULTCV
;
74 * The cookie of the configuration and its mutex
76 static ldap_get_chg_cookie_t config_cookie
= {0, 0};
77 static mutex_t config_cookie_lock
= DEFAULTMUTEX
;
79 static void cleanup_thread_by_pid(pid_t pid
);
82 chg_config_cookie_get(void)
84 ldap_get_chg_cookie_t cookie
;
85 (void) mutex_lock(&config_cookie_lock
);
86 cookie
= config_cookie
;
87 (void) mutex_unlock(&config_cookie_lock
);
92 chg_config_cookie_increment_seq_num(void)
94 (void) mutex_lock(&config_cookie_lock
);
95 config_cookie
.seq_num
++;
96 (void) mutex_unlock(&config_cookie_lock
);
100 chg_config_cookie_set(ldap_get_chg_cookie_t
*cookie
)
102 (void) mutex_lock(&config_cookie_lock
);
103 config_cookie
.mgr_pid
= cookie
->mgr_pid
;
104 config_cookie
.seq_num
= cookie
->seq_num
;
105 (void) mutex_unlock(&config_cookie_lock
);
108 chg_cookie_equal(ldap_get_chg_cookie_t
*c1
, ldap_get_chg_cookie_t
*c2
)
110 if (c1
->mgr_pid
== c2
->mgr_pid
&& c1
->seq_num
== c2
->seq_num
)
116 * Create a node in the list and output the node. The caller can NOT free it.
119 waiting_list_add(chg_info_t
*info
, pid_t pid
, thread_t tid
,
120 waiting_list_t
**wlp
)
127 if ((wl
= (waiting_list_t
*)calloc(1, sizeof (waiting_list_t
)))
129 logit("waiting_list_add: No memory. pid %ld tid %d\n",
131 return (CHG_NO_MEMORY
);
137 if (info
->chg_w_first
== NULL
) {
138 info
->chg_w_first
= wl
;
139 info
->chg_w_last
= wl
;
141 info
->chg_w_last
->next
= wl
;
142 wl
->prev
= info
->chg_w_last
;
143 info
->chg_w_last
= wl
;
146 return (CHG_SUCCESS
);
150 * Find a node with matching tid in the list and remove it from the list.
153 waiting_list_delete(chg_info_t
*info
, thread_t tid
)
157 for (wl
= info
->chg_w_first
; wl
!= NULL
; wl
= wl
->next
) {
158 if (wl
->tid
== tid
) {
159 if (wl
->next
== NULL
) {
160 if (wl
->prev
== NULL
) {
161 info
->chg_w_first
= NULL
;
162 info
->chg_w_last
= NULL
;
164 wl
->prev
->next
= NULL
;
165 info
->chg_w_last
= wl
->prev
;
168 if (wl
->prev
== NULL
) {
169 wl
->next
->prev
= NULL
;
170 info
->chg_w_first
= wl
->next
;
172 wl
->prev
->next
= wl
->next
;
173 wl
->next
->prev
= wl
->prev
;
177 return (CHG_SUCCESS
);
180 return (CHG_NOT_FOUND_IN_WAITING_LIST
);
184 * Delete the thread from the waiting list and remove data when the list
188 waiting_list_cleanup(chg_info_t
*chg
, thread_t tid
)
192 rc
= waiting_list_delete(chg
, tid
);
194 if (rc
== CHG_SUCCESS
&& chg
->chg_w_first
== NULL
) {
196 chg
->chg_data
= NULL
;
202 * Set flag by pid so it can be cleaned up.
205 waiting_list_set_cleanup(chg_info_t
*info
, pid_t pid
)
209 for (wl
= info
->chg_w_first
; wl
!= NULL
; wl
= wl
->next
) {
210 if (wl
->pid
== pid
) {
218 * Return: 1 - door client is dead, 0 - door client is alive
221 door_client_dead(void)
226 if (door_ucred(&uc
) == -1 && errno
== EINVAL
) {
238 * This function handles GETSTATUSCHANGE call from main nscd.
239 * The call can be a START op or STOP op. A cookie is sent from main nscd too.
240 * The static global variable main_nscd keeps record of pid, tid and some flags.
241 * If the thread is door_return(), main_nscd.pid, main_nscd.tid are set to 0.
242 * When the call is START op, it checks if main_nscd.pid is 0. If it is, it
243 * proceeds to wait for the change notification. If it's not, which means
244 * another main nscd handling thread is still around. It sends broadcast to
245 * clean up that thread and wait until the cleanup is done then proceeds to
246 * wait for the change notification. If same main nscd sends START op
247 * repeatedly, it'll be rejected.
248 * It also checks the cookie from main nscd. If it's not the same as
249 * ldap_cachemgr's cookie, door returns config change.
250 * If the door call is STOP op, it creates a thread to clean up main nscd START
251 * thread so it won't be blocking.
252 * In waiting for the change notification phase, the thread is waken up by
253 * the notification threads or by the cleanup threads.
254 * If it's a notification, it copies data to the stack then door return.
255 * If it's a cleanup, door_client_dead() is called to verify it then
259 chg_get_statusChange(LineBuf
*info
, ldap_call_t
*in
, pid_t nscd_pid
)
261 int rc
= CHG_SUCCESS
, another_main_nscd_thread_alive
= 0;
263 thread_t this_tid
= thr_self();
264 waiting_list_t
*wl
= NULL
;
265 ldap_get_change_out_t
*cout
;
266 ldap_get_chg_cookie_t cookie
;
271 if (in
->ldap_u
.get_change
.op
== NS_STATUS_CHANGE_OP_START
) {
273 (void) mutex_lock(&chg_main_nscd_lock
);
274 if (chg_main_nscd
.pid
!= 0) {
275 if (nscd_pid
!= chg_main_nscd
.pid
) {
277 * This is the case that nscd doesn't shut down
278 * properly(e.g. core) and STOP op is not sent,
279 * the thread handling it is still around and
280 * not cleaned up yet.
281 * Test if the thread is still alive.
282 * If it is, clean it up.
283 * For thr_kill, if sig is 0, a validity check
284 * is done for the existence of the target
285 * thread; no signal is sent.
287 if (thr_kill(chg_main_nscd
.tid
, 0) == 0) {
288 another_main_nscd_thread_alive
= 1;
289 cleanup_thread_by_pid(
292 } else if (chg_main_nscd
.in_progress
||
293 chg_main_nscd
.is_waiting_cleanup
) {
295 * Same nscd pid can only send door call
296 * one at a time and wait for ldap_cachemgr to
297 * return change data. If it's the same pid
298 * again, it's an nscd error.
300 (void) mutex_unlock(&chg_main_nscd_lock
);
301 return (CHG_NSCD_REPEATED_CALL
);
305 * Wait for another thread to be cleaned up if it's alive.
306 * After that this cond var is waken up.
308 if (another_main_nscd_thread_alive
) {
309 while (chg_main_nscd
.in_progress
) {
310 chg_main_nscd
.is_waiting_cleanup
= 1;
311 (void) cond_wait(&chg_main_nscd_cv
,
312 &chg_main_nscd_lock
);
317 * Replace pid and tid and set the flag.
319 chg_main_nscd
.is_waiting_cleanup
= 0;
320 chg_main_nscd
.pid
= nscd_pid
;
321 chg_main_nscd
.tid
= this_tid
;
322 chg_main_nscd
.in_progress
= 1;
323 (void) mutex_unlock(&chg_main_nscd_lock
);
325 cookie
= chg_config_cookie_get();
327 if (!chg_cookie_equal(&cookie
, &in
->ldap_u
.get_change
.cookie
)) {
329 * different cookie, set new cookie and
330 * return door call right away
332 len
= sizeof (ldap_get_change_out_t
);
333 if ((cout
= calloc(1, len
)) == NULL
) {
336 cout
->type
= NS_STATUS_CHANGE_TYPE_CONFIG
;
337 cout
->cookie
= cookie
;
338 info
->str
= (char *)cout
;
343 (void) mutex_lock(&chg
.chg_lock
);
345 /* wait for the change notification */
346 rc
= waiting_list_add(&chg
, nscd_pid
, this_tid
, &wl
);
347 if (rc
== CHG_SUCCESS
) {
349 while (!chg
.chg_wakeup
) {
351 door_client_dead()) {
355 (void) cond_wait(&chg
.chg_cv
,
358 /* Check if door client is still alive again */
359 if (!return_now
&& !wl
->cleanup
&&
360 !door_client_dead()) {
361 /* copy data to buffer */
362 if ((info
->str
= malloc(
363 chg
.chg_data_size
)) == NULL
) {
366 (void) memcpy(info
->str
,
369 info
->len
= chg
.chg_data_size
;
372 waiting_list_cleanup(&chg
, this_tid
);
374 (void) mutex_unlock(&chg
.chg_lock
);
379 * Reset pid, tid and flag, send wakeup signal.
381 (void) mutex_lock(&chg_main_nscd_lock
);
382 chg_main_nscd
.pid
= 0;
383 chg_main_nscd
.tid
= 0;
384 chg_main_nscd
.in_progress
= 0;
385 if (chg_main_nscd
.is_waiting_cleanup
)
386 (void) cond_broadcast(&chg_main_nscd_cv
);
388 (void) mutex_unlock(&chg_main_nscd_lock
);
390 } else if (in
->ldap_u
.get_change
.op
== NS_STATUS_CHANGE_OP_STOP
) {
392 cleanup_thread_by_pid(nscd_pid
);
396 rc
= CHG_INVALID_PARAM
;
398 if (rc
== CHG_EXCEED_MAX_THREADS
)
399 cleanup_thread_by_pid(0);
405 * This function copies the header and data stream to the buffer
406 * then send broadcast to wake up the chg_get_statusChange() threads.
409 chg_notify_statusChange(char *str
)
411 ldap_get_change_out_t
*cout
= (ldap_get_change_out_t
*)str
;
413 cout
->cookie
= chg_config_cookie_get();
415 (void) mutex_lock(&chg
.chg_lock
);
416 if (chg
.chg_w_first
!= NULL
&& chg
.chg_wakeup
== 0) {
425 if (cout
->type
== NS_STATUS_CHANGE_TYPE_CONFIG
)
426 chg
.chg_data_size
= sizeof (ldap_get_change_out_t
);
428 /* NS_STATUS_CHANGE_TYPE_SERVER */
429 chg
.chg_data_size
= sizeof (ldap_get_change_out_t
) -
430 sizeof (int) + cout
->data_size
;
433 (void) cond_broadcast(&chg
.chg_cv
);
435 (void) mutex_unlock(&chg
.chg_lock
);
437 return (CHG_SUCCESS
);
441 * This is called when the configuration is refreshed.
442 * The new configuration is different from the current one, a notification
443 * is sent tochg_get_statusChange() threads.
446 chg_test_config_change(ns_config_t
*new, int *change_status
)
449 LineBuf new_cfg
, cur_cfg
;
450 ns_ldap_error_t
*errp
= NULL
;
451 ldap_config_out_t
*new_out
, *cur_out
;
452 ldap_get_change_out_t
*cout
;
454 (void) memset(&new_cfg
, 0, sizeof (LineBuf
));
455 (void) memset(&cur_cfg
, 0, sizeof (LineBuf
));
457 * Flatten the config data of the newly downloaded config and
458 * current default config and compare both.
460 if ((errp
= __ns_ldap_LoadDoorInfo(&new_cfg
, NULL
, new, 0)) != NULL
) {
461 __ns_ldap_freeError(&errp
);
462 /* error, assume the config is changed */
464 } else if ((errp
= __ns_ldap_LoadDoorInfo(&cur_cfg
, NULL
, NULL
, 0))
466 __ns_ldap_freeError(&errp
);
467 /* error, assume the config is changed */
471 new_out
= (ldap_config_out_t
*)new_cfg
.str
;
472 cur_out
= (ldap_config_out_t
*)cur_cfg
.str
;
473 if (strcmp(new_out
->config_str
, cur_out
->config_str
) != 0) {
475 if (current_admin
.debug_level
>= DBG_PROFILE_REFRESH
) {
476 logit("config changed.\n");
487 if ((cout
= calloc(1, sizeof (ldap_get_change_out_t
)))
489 logit("chg_test_config_change: No Memory\n");
492 * Replace the currentdefault config with the new
495 __s_api_init_config(new);
496 chg_config_cookie_increment_seq_num();
497 cout
->type
= NS_STATUS_CHANGE_TYPE_CONFIG
;
499 * cout->cookie is set by
500 * chg_notify_statusChange
502 (void) chg_notify_statusChange((char *)cout
);
505 __s_api_destroy_config(new);
508 *change_status
= changed
;
512 * Wake up chg_get_statusChange() threads to clean up the threads
513 * that main nscd doesn't exist on the other of door anymore or
514 * the thread is marked as cleanup.
517 cleanup_threads(chg_info_t
*chg
, pid_t pid
, cleanup_type_t type
)
519 (void) mutex_lock(&chg
->chg_lock
);
520 if (type
== CLEANUP_BY_PID
)
521 waiting_list_set_cleanup(chg
, pid
);
523 * wake up threads without setting chg.chg_wakeup.
524 * It's for cleanup purpose, not for notifying changes.
526 (void) cond_broadcast(&chg
->chg_cv
);
527 (void) mutex_unlock(&chg
->chg_lock
);
530 * If arg is NULL, it loops forever,
531 * else it calls cleanup_threads once and exits.
534 chg_cleanup_waiting_threads(void *arg
)
536 cleanup_op_t
*op
= (cleanup_op_t
*)arg
;
537 cleanup_type_t type
= 0;
539 int always
= 1, waiting
;
541 (void) pthread_setname_np(pthread_self(), "chg_cleanup_thr");
555 (void) sleep(CLEANUP_WAIT_TIME
);
556 cleanup_threads(&chg
, pid
, type
);
568 * The door server thead which has the door client pid will be marked
569 * as to be clean up. If pid is 0, no marking and just clean up all.
572 cleanup_thread_by_pid(pid_t pid
)
576 if ((op
= malloc(sizeof (cleanup_op_t
))) == NULL
)
580 /* clean up all if pid is 0 */
582 op
->type
= CLEANUP_ALL
;
584 op
->type
= CLEANUP_BY_PID
;
586 if (thr_create(NULL
, 0, chg_cleanup_waiting_threads
,
587 (void *)op
, THR_BOUND
|THR_DETACHED
, NULL
) != 0) {
589 logit("thr_create failed for cleanup_thread_by_pid(%ld)\n",
595 * Output a psinfo of an nscd process with process id pid
596 * Return: 0 - Can't find the process or it's not nscd
598 * Note: If info is NULL, returns 0 or 1 only and no output from info.
601 get_nscd_psinfo(pid_t pid
, psinfo_t
*info
)
604 char fname
[MAXPATHLEN
];
608 if (snprintf(fname
, MAXPATHLEN
, "/proc/%d/psinfo", pid
) > 0) {
609 if ((fd
= open(fname
, O_RDONLY
)) >= 0) {
610 ret
= read(fd
, &pinfo
, sizeof (psinfo_t
));
612 if ((ret
== sizeof (psinfo_t
)) &&
613 (strcmp(pinfo
.pr_fname
, "nscd") == 0)) {
623 * If the parent process is nscd and euid is 0, it's a peruser nscd.
626 is_peruser_nscd(pid_t pid
)
631 if (get_nscd_psinfo(pid
, &pinfo
)) {
632 ppid
= pinfo
.pr_ppid
;
633 if (get_nscd_psinfo(ppid
, &pinfo
) && pinfo
.pr_euid
== 0)
635 * get psinfo of parent forker nscd
645 * Check if the door client making door call is a nscd or peruser nscd and
646 * output door client's pid.
649 chg_is_called_from_nscd_or_peruser_nscd(char *dc_str
, pid_t
*pidp
)
656 if (door_ucred(&uc
) != 0) {
658 logit("door_ucred() call failed %s\n", strerror(rc
));
661 euid
= ucred_geteuid(uc
);
662 pid
= *pidp
= ucred_getpid(uc
);
664 if ((euid
== 0 && is_called_from_nscd(pid
)) ||
665 is_peruser_nscd(pid
)) {
666 if (current_admin
.debug_level
>= DBG_ALL
)
667 logit("ldap_cachemgr received %s call from pid %ld, "
668 "uid %u, euid %u\n", dc_str
, pid
,
669 ucred_getruid(uc
), euid
);
672 if (current_admin
.debug_level
>= DBG_CANT_FIND
)
673 logit("%s call failed(cred): caller pid %ld, uid %u, "
674 "euid %u\n", dc_str
, pid
,
675 ucred_getruid(uc
), euid
);