8158 Want named threads API
[unleashed.git] / usr / src / cmd / ldapcachemgr / cachemgr_change.c
blob727851ffbcad49a9960597655a3183b2b2462be2
1 /*
2 * CDDL HEADER START
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]
19 * CDDL HEADER END
22 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
25 * Copyright 2018 Joyent, Inc.
28 #include <strings.h>
29 #include <stdlib.h>
30 #include <syslog.h>
31 #include <errno.h>
32 #include <libintl.h>
33 #include <door.h>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <fcntl.h>
37 #include <procfs.h>
38 #include <pthread.h>
39 #include "cachemgr.h"
41 extern admin_t current_admin;
43 #define CLEANUP_WAIT_TIME 60
45 typedef enum cleanup_type {
46 CLEANUP_ALL = 1,
47 CLEANUP_BY_PID = 2
48 } cleanup_type_t;
50 typedef struct cleanup_op {
51 pid_t pid;
52 cleanup_type_t type;
53 } cleanup_op_t;
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 */
60 /* copying data */
61 int is_waiting_cleanup; /* A main nscd thread is */
62 /* waiting for another main */
63 /* nscd thread to be cleaned */
64 /* up */
65 } main_nscd_t;
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);
81 ldap_get_chg_cookie_t
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);
88 return (cookie);
91 static void
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);
99 void
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);
107 static boolean_t
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)
111 return (B_TRUE);
112 else
113 return (B_FALSE);
116 * Create a node in the list and output the node. The caller can NOT free it.
118 static int
119 waiting_list_add(chg_info_t *info, pid_t pid, thread_t tid,
120 waiting_list_t **wlp)
123 waiting_list_t *wl;
125 *wlp = NULL;
127 if ((wl = (waiting_list_t *)calloc(1, sizeof (waiting_list_t)))
128 == NULL) {
129 logit("waiting_list_add: No memory. pid %ld tid %d\n",
130 pid, tid);
131 return (CHG_NO_MEMORY);
134 wl->pid = pid;
135 wl->tid = tid;
137 if (info->chg_w_first == NULL) {
138 info->chg_w_first = wl;
139 info->chg_w_last = wl;
140 } else {
141 info->chg_w_last->next = wl;
142 wl->prev = info->chg_w_last;
143 info->chg_w_last = wl;
145 *wlp = wl;
146 return (CHG_SUCCESS);
150 * Find a node with matching tid in the list and remove it from the list.
152 static int
153 waiting_list_delete(chg_info_t *info, thread_t tid)
155 waiting_list_t *wl;
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;
163 } else {
164 wl->prev->next = NULL;
165 info->chg_w_last = wl->prev;
167 } else {
168 if (wl->prev == NULL) {
169 wl->next->prev = NULL;
170 info->chg_w_first = wl->next;
171 } else {
172 wl->prev->next = wl->next;
173 wl->next->prev = wl->prev;
176 free(wl);
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
185 * is empty.
187 static void
188 waiting_list_cleanup(chg_info_t *chg, thread_t tid)
190 int rc;
192 rc = waiting_list_delete(chg, tid);
194 if (rc == CHG_SUCCESS && chg->chg_w_first == NULL) {
195 free(chg->chg_data);
196 chg->chg_data = NULL;
197 chg->chg_wakeup = 0;
202 * Set flag by pid so it can be cleaned up.
204 static void
205 waiting_list_set_cleanup(chg_info_t *info, pid_t pid)
207 waiting_list_t *wl;
209 for (wl = info->chg_w_first; wl != NULL; wl = wl->next) {
210 if (wl->pid == pid) {
211 wl->cleanup = 1;
212 break;
218 * Return: 1 - door client is dead, 0 - door client is alive
220 static int
221 door_client_dead(void)
223 ucred_t *uc = NULL;
224 int rc;
226 if (door_ucred(&uc) == -1 && errno == EINVAL) {
227 rc = 1;
228 } else {
229 rc = 0;
231 if (uc)
232 ucred_free(uc);
234 return (rc);
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
256 * door return.
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;
262 int len, return_now;
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;
268 info->str = NULL;
269 info->len = 0;
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(
290 chg_main_nscd.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) {
334 rc = CHG_NO_MEMORY;
335 } else {
336 cout->type = NS_STATUS_CHANGE_TYPE_CONFIG;
337 cout->cookie = cookie;
338 info->str = (char *)cout;
339 info->len = len;
342 } else {
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) {
348 return_now = 0;
349 while (!chg.chg_wakeup) {
350 if (wl->cleanup ||
351 door_client_dead()) {
352 return_now = 1;
353 break;
355 (void) cond_wait(&chg.chg_cv,
356 &chg.chg_lock);
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) {
364 rc = CHG_NO_MEMORY;
365 } else {
366 (void) memcpy(info->str,
367 chg.chg_data,
368 chg.chg_data_size);
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);
393 rc = CHG_SUCCESS;
395 } else {
396 rc = CHG_INVALID_PARAM;
398 if (rc == CHG_EXCEED_MAX_THREADS)
399 cleanup_thread_by_pid(0);
401 return (rc);
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) {
418 if (chg.chg_data) {
419 free(chg.chg_data);
420 chg.chg_data = NULL;
423 chg.chg_data = str;
425 if (cout->type == NS_STATUS_CHANGE_TYPE_CONFIG)
426 chg.chg_data_size = sizeof (ldap_get_change_out_t);
427 else
428 /* NS_STATUS_CHANGE_TYPE_SERVER */
429 chg.chg_data_size = sizeof (ldap_get_change_out_t) -
430 sizeof (int) + cout->data_size;
432 chg.chg_wakeup = 1;
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.
445 void
446 chg_test_config_change(ns_config_t *new, int *change_status)
448 int changed = 0;
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 */
463 changed = 1;
464 } else if ((errp = __ns_ldap_LoadDoorInfo(&cur_cfg, NULL, NULL, 0))
465 != NULL) {
466 __ns_ldap_freeError(&errp);
467 /* error, assume the config is changed */
468 changed = 1;
470 if (changed == 0) {
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) {
474 changed = 1;
475 if (current_admin.debug_level >= DBG_PROFILE_REFRESH) {
476 logit("config changed.\n");
480 if (cur_cfg.str)
481 free(cur_cfg.str);
482 if (new_cfg.str)
483 free(new_cfg.str);
485 if (changed) {
487 if ((cout = calloc(1, sizeof (ldap_get_change_out_t)))
488 == NULL) {
489 logit("chg_test_config_change: No Memory\n");
490 } else {
492 * Replace the currentdefault config with the new
493 * config
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);
504 } else {
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.
516 static void
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.
533 void *
534 chg_cleanup_waiting_threads(void *arg)
536 cleanup_op_t *op = (cleanup_op_t *)arg;
537 cleanup_type_t type = 0;
538 pid_t pid;
539 int always = 1, waiting;
541 (void) pthread_setname_np(pthread_self(), "chg_cleanup_thr");
543 if (op == NULL) {
544 waiting = 1;
545 type = CLEANUP_ALL;
546 pid = 0;
547 } else {
548 waiting = 0;
549 type = op->type;
550 pid = op->pid;
553 while (always) {
554 if (waiting)
555 (void) sleep(CLEANUP_WAIT_TIME);
556 cleanup_threads(&chg, pid, type);
557 if (!waiting)
558 break;
561 if (op)
562 free(op);
564 thr_exit(NULL);
565 return (NULL);
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.
571 static void
572 cleanup_thread_by_pid(pid_t pid)
574 cleanup_op_t *op;
576 if ((op = malloc(sizeof (cleanup_op_t))) == NULL)
577 return;
579 op->pid = pid;
580 /* clean up all if pid is 0 */
581 if (pid == 0)
582 op->type = CLEANUP_ALL;
583 else
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) {
588 free(op);
589 logit("thr_create failed for cleanup_thread_by_pid(%ld)\n",
590 pid);
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
597 * 1 - psinfo found
598 * Note: If info is NULL, returns 0 or 1 only and no output from info.
600 static int
601 get_nscd_psinfo(pid_t pid, psinfo_t *info)
603 psinfo_t pinfo;
604 char fname[MAXPATHLEN];
605 ssize_t ret;
606 int fd;
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));
611 (void) close(fd);
612 if ((ret == sizeof (psinfo_t)) &&
613 (strcmp(pinfo.pr_fname, "nscd") == 0)) {
614 if (info)
615 *info = pinfo;
616 return (1);
620 return (0);
623 * If the parent process is nscd and euid is 0, it's a peruser nscd.
625 static int
626 is_peruser_nscd(pid_t pid)
628 pid_t ppid;
629 psinfo_t pinfo;
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
637 return (1);
638 else
639 return (0);
640 } else {
641 return (0);
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)
651 int rc;
652 uid_t euid;
653 pid_t pid;
654 ucred_t *uc = NULL;
656 if (door_ucred(&uc) != 0) {
657 rc = errno;
658 logit("door_ucred() call failed %s\n", strerror(rc));
659 return (0);
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);
670 rc = 1;
671 } else {
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);
677 rc = 0;
680 ucred_free(uc);
682 return (rc);