xref: /illumos-gate/usr/src/cmd/dcs/sparc/sun4u/dcs_ses.c (revision 7a6d80f1660abd4755c68cbd094d4a914681d26e)
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 /*
23  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*
28  * This file is a module that provides an interface to managing
29  * concurrent sessions executed in either a separate thread or a
30  * separate process. Threads are used only if the compile time flag
31  * DCS_MULTI_THREAD is set. Otherwise, a new process is forked for
32  * each session.
33  *
34  * Multiple processes are used to enable full Internationalization
35  * support. This support requires that each session is able to set
36  * its own locale for use in reporting errors to the user. Currently,
37  * this is not possible using multiple threads because the locale
38  * can not be set for an individual thread. For this reason, multiple
39  * processes are supported until proper locale support is provided
40  * for multiple threads.
41  *
42  * When Solaris supports a different locale in each thread, all
43  * code used to enable using multiple processes should be removed.
44  * To simplify this process, all references to DCS_MULTI_THREAD can
45  * be found in this file.
46  */
47 
48 #include <stdlib.h>
49 #include <stdio.h>
50 #include <unistd.h>
51 #include <string.h>
52 #include <errno.h>
53 #include <signal.h>
54 #include <syslog.h>
55 #include <locale.h>
56 #include <sys/socket.h>
57 
58 #ifdef DCS_MULTI_THREAD
59 #include <thread.h>
60 #include <pthread.h>
61 #else /* DCS_MULTI_THREAD */
62 #include <sys/types.h>
63 #include <sys/wait.h>
64 #endif /* DCS_MULTI_THREAD */
65 
66 #include "dcs.h"
67 #include "rdr_messages.h"
68 #include "rdr_param_types.h"
69 
70 
71 #define	DCS_DEFAULT_LOCALE		"C"
72 
73 
74 /* session allocation/deallocation functions */
75 static int ses_alloc(void);
76 static int ses_free(void);
77 
78 /* handler functions */
79 static void *ses_handler(void *arg);
80 #ifndef DCS_MULTI_THREAD
81 static void exit_handler(int sig, siginfo_t *info, void *context);
82 #endif /* !DCS_MULTI_THREAD */
83 
84 /* session accounting functions */
85 #ifdef DCS_MULTI_THREAD
86 static void ses_thr_exit(void);
87 #endif /* DCS_MULTI_THREAD */
88 
89 
90 /*
91  * Global structure that holds all relevant information
92  * about the current session. If multiple threads are
93  * used, the thread specific data mechanism is used. This
94  * requires a data key to access the thread's private
95  * session information.
96  */
97 #ifdef DCS_MULTI_THREAD
98 thread_key_t	ses_key = THR_ONCE_KEY;
99 #else /* DCS_MULTI_THREAD */
100 session_t	*ses;
101 #endif /* DCS_MULTI_THREAD */
102 
103 
104 /*
105  * Information about the current number of active sessions.
106  * If multiple threads are used, synchronization objects
107  * are required.
108  */
109 static ulong_t sessions = 0;
110 
111 #ifdef DCS_MULTI_THREAD
112 static mutex_t	sessions_lock = DEFAULTMUTEX;
113 static cond_t	sessions_cv   = DEFAULTCV;
114 #endif /* DCS_MULTI_THREAD */
115 
116 
117 /*
118  * ses_start:
119  *
120  * Start the session handler. If multiple threads are used, create a new
121  * thread that runs the ses_handler() function. If multiple processes
122  * are used, fork a new process and call ses_handler().
123  */
124 int
125 ses_start(int fd)
126 {
127 #ifdef DCS_MULTI_THREAD
128 
129 	int	thr_err;
130 
131 
132 	mutex_lock(&sessions_lock);
133 	sessions++;
134 	mutex_unlock(&sessions_lock);
135 
136 	thr_err = thr_create(NULL, 0, ses_handler, (void *)fd,
137 	    THR_DETACHED | THR_NEW_LWP, NULL);
138 
139 	return ((thr_err) ? -1 : 0);
140 
141 #else /* DCS_MULTI_THREAD */
142 
143 	int 	pid;
144 
145 
146 	pid = fork();
147 
148 	if (pid == -1) {
149 		(void) rdr_close(fd);
150 		return (-1);
151 	}
152 
153 	/*
154 	 * Parent:
155 	 */
156 	if (pid) {
157 		/* close the child's fd */
158 		(void) close(fd);
159 
160 		sessions++;
161 
162 		return (0);
163 	}
164 
165 	/*
166 	 * Child:
167 	 */
168 	ses_handler((void *)fd);
169 
170 	/*
171 	 * Prevent return to parent's loop
172 	 */
173 	exit(0);
174 
175 	/* NOTREACHED */
176 
177 #endif /* DCS_MULTI_THREAD */
178 }
179 
180 
181 /*
182  * ses_close:
183  *
184  * Initiate the closure of a session by sending an RDR_SES_END message
185  * to the client. It does not attempt to close the network connection.
186  */
187 int
188 ses_close(int err_code)
189 {
190 	session_t	*sp;
191 	cfga_params_t	req_data;
192 	rdr_msg_hdr_t	req_hdr;
193 	int		snd_status;
194 	static char	*op_name = "session close";
195 
196 
197 	/* get the current session information */
198 	if ((sp = curr_ses()) == NULL) {
199 		ses_close(DCS_ERROR);
200 		return (-1);
201 	}
202 
203 	/* check if already sent session end */
204 	if (sp->state == DCS_SES_END) {
205 		return (0);
206 	}
207 
208 	/* prepare header information */
209 	init_msg(&req_hdr);
210 	req_hdr.message_opcode = RDR_SES_END;
211 	req_hdr.data_type = RDR_REQUEST;
212 	req_hdr.status = err_code;
213 
214 	/* no operation specific data */
215 	(void) memset(&req_data, 0, sizeof (req_data));
216 
217 	PRINT_MSG_DBG(DCS_SEND, &req_hdr);
218 
219 	/* send the message */
220 	snd_status = rdr_snd_msg(sp->fd, &req_hdr, &req_data, DCS_SND_TIMEOUT);
221 
222 	if (snd_status == RDR_ABORTED) {
223 		abort_handler();
224 	}
225 
226 	if (snd_status != RDR_OK) {
227 		dcs_log_msg(LOG_ERR, DCS_OP_REPLY_ERR, op_name);
228 	}
229 
230 	/*
231 	 * Setting the session state to DCS_SES_END will
232 	 * cause the session handler to terminate the
233 	 * network connection. This should happen whether
234 	 * or not the session end message that was just
235 	 * sent was received successfully.
236 	 */
237 	sp->state = DCS_SES_END;
238 	return (0);
239 }
240 
241 
242 /*
243  * ses_abort:
244  *
245  * Attempt to abort an active session. If multiple threads are used,
246  * the parameter represents a thread_t identifier. If multiple
247  * processes are used, the parameter represents a pid. In either
248  * case, use this identifier to send a SIGINT signal to the approprate
249  * session.
250  */
251 int
252 ses_abort(long ses_id)
253 {
254 	DCS_DBG(DBG_SES, "killing session %d", ses_id);
255 
256 #ifdef DCS_MULTI_THREAD
257 
258 	if (thr_kill(ses_id, SIGINT) != 0) {
259 		/*
260 		 * If the thread cannot be found, we will assume
261 		 * that the session was able to exit normally. In
262 		 * this case, there is no error since the desired
263 		 * result has already been achieved.
264 		 */
265 		if (errno == ESRCH) {
266 			return (0);
267 		}
268 		return (-1);
269 	}
270 
271 #else /* DCS_MULTI_THREAD */
272 
273 	if (kill(ses_id, SIGINT) == -1) {
274 		/*
275 		 * If the process cannot be found, we will assume
276 		 * that the session was able to exit normally. In
277 		 * this case, there is no error since the desired
278 		 * result has already been achieved.
279 		 */
280 		if (errno == ESRCH) {
281 			return (0);
282 		}
283 		return (-1);
284 	}
285 
286 #endif /* DCS_MULTI_THREAD */
287 
288 	return (0);
289 }
290 
291 
292 /*
293  * ses_abort_enable:
294  *
295  * Enter a mode where the current session can be aborted. This mode
296  * will persist until ses_abort_disable() is called.
297  *
298  * A signal handler for SIGINT must be installed prior to calling this
299  * function. If this is not the case, and multiple threads are used,
300  * the default handler for SIGINT will cause the entire process to
301  * exit, rather than just the current session. If multiple processes
302  * are used, the default handler for SIGINT will not affect the main
303  * process, but it will prevent both sides from gracefully closing
304  * the session.
305  */
306 void
307 ses_abort_enable(void)
308 {
309 	sigset_t	unblock_set;
310 
311 
312 	/* unblock SIGINT */
313 	sigemptyset(&unblock_set);
314 	sigaddset(&unblock_set, SIGINT);
315 	(void) sigprocmask(SIG_UNBLOCK, &unblock_set, NULL);
316 }
317 
318 
319 /*
320  * ses_abort_disable:
321  *
322  * Exit the mode where the current session can be aborted. This
323  * will leave the mode entered by ses_abort_enable().
324  */
325 void
326 ses_abort_disable(void)
327 {
328 	sigset_t	block_set;
329 
330 
331 	/* block SIGINT */
332 	sigemptyset(&block_set);
333 	sigaddset(&block_set, SIGINT);
334 	(void) sigprocmask(SIG_BLOCK, &block_set, NULL);
335 }
336 
337 
338 /*
339  * ses_setlocale:
340  *
341  * Set the locale for the current session. Currently, if multiple threads
342  * are used, the 'C' locale is specified for all cases. Once there is support
343  * for setting a thread specific locale, the requested locale will be used.
344  * If multiple processes are used, an attempt is made to set the locale of
345  * the process to the locale passed in as a parameter.
346  */
347 int
348 ses_setlocale(char *locale)
349 {
350 	char	*new_locale;
351 
352 	/* sanity check */
353 	if (locale == NULL) {
354 		locale = DCS_DEFAULT_LOCALE;
355 	}
356 
357 #ifdef DCS_MULTI_THREAD
358 
359 	/*
360 	 * Reserved for setting the locale on a per thread
361 	 * basis. Currently there is no Solaris support for
362 	 * this, so use the default locale.
363 	 */
364 	new_locale = setlocale(LC_ALL, DCS_DEFAULT_LOCALE);
365 
366 #else /* DCS_MULTI_THREAD */
367 
368 	new_locale = setlocale(LC_ALL, locale);
369 
370 #endif /* DCS_MULTI_THREAD */
371 
372 	if ((new_locale == NULL) || (strcmp(new_locale, locale) != 0)) {
373 		/* silently fall back to C locale */
374 		new_locale = setlocale(LC_ALL, DCS_DEFAULT_LOCALE);
375 	}
376 
377 	DCS_DBG(DBG_SES, "using '%s' locale", new_locale);
378 
379 	return (0);
380 }
381 
382 
383 /*
384  * ses_init_signals:
385  *
386  * Initialize the set of signals to be blocked. It is assumed that the
387  * mask parameter initially contains all signals. If multiple threads
388  * are used, this is the correct behavior and the mask is not altered.
389  * If multiple processes are used, session accounting is performed in
390  * a SIGCHLD handler and so SIGCHLD must not be blocked. The action of
391  * initializing this handler is also performed in this function.
392  */
393 /* ARGSUSED */
394 void
395 ses_init_signals(sigset_t *mask)
396 {
397 #ifndef DCS_MULTI_THREAD
398 
399 	struct sigaction	act;
400 
401 
402 	/* unblock SIGCHLD */
403 	(void) sigdelset(mask, SIGCHLD);
404 
405 	/*
406 	 * Establish a handler for SIGCHLD
407 	 */
408 	(void) memset(&act, 0, sizeof (act));
409 	act.sa_sigaction = exit_handler;
410 	act.sa_flags = SA_SIGINFO;
411 
412 	(void) sigaction(SIGCHLD, &act, NULL);
413 
414 #endif /* !DCS_MULTI_THREAD */
415 }
416 
417 
418 /*
419  * ses_sleep:
420  *
421  * Sleep for a specified amount of time, but don't prevent the
422  * session from being aborted.
423  */
424 void
425 ses_sleep(int sec)
426 {
427 	ses_abort_enable();
428 	sleep(sec);
429 	ses_abort_disable();
430 }
431 
432 
433 /*
434  * ses_wait:
435  *
436  * Wait for the number of active sessions to drop below the maximum
437  * allowed number of active sessions. If multiple threads are used,
438  * the thread waits on a condition variable until a child thread
439  * signals that it is going to exit. If multiple processes are used,
440  * the process waits until at least one child process exits.
441  */
442 static void
443 ses_wait(void)
444 {
445 #ifdef DCS_MULTI_THREAD
446 
447 	mutex_lock(&sessions_lock);
448 
449 	while (sessions >= max_sessions) {
450 		cond_wait(&sessions_cv, &sessions_lock);
451 	}
452 
453 	mutex_unlock(&sessions_lock);
454 
455 #else /* DCS_MULTI_THREAD */
456 
457 	if (sessions >= max_sessions) {
458 		(void) wait(NULL);
459 	}
460 
461 #endif /* DCS_MULTI_THREAD */
462 }
463 
464 
465 /*
466  * ses_poll:
467  *
468  * Poll on the file descriptors passed in as a parameter. Before polling,
469  * a check is performed to see if the number of active sessions is less
470  * than the maximum number of active sessions allowed. If the limit for
471  * active sessions is reached, the poll will be delayed until at least
472  * one session exits.
473  */
474 int
475 ses_poll(struct pollfd fds[], nfds_t nfds, int timeout)
476 {
477 	int	err;
478 
479 
480 	ses_wait();
481 
482 	err = poll(fds, nfds, timeout);
483 
484 	return (err);
485 }
486 
487 
488 /*
489  * curr_ses:
490  *
491  * Return a pointer to the global session information. If multiple threads
492  * are being used, this will point to a thread specific instance of a
493  * session structure.
494  */
495 session_t *
496 curr_ses(void)
497 {
498 #ifdef DCS_MULTI_THREAD
499 
500 	return (pthread_getspecific(ses_key));
501 
502 #else /* DCS_MULTI_THREAD */
503 
504 	return (ses);
505 
506 #endif /* DCS_MULTI_THREAD */
507 }
508 
509 
510 /*
511  * curr_ses_id:
512  *
513  * Return the session identifier. This is either the thread_t identifier
514  * of the thread, or the pid of the process.
515  */
516 long
517 curr_ses_id(void)
518 {
519 #ifdef DCS_MULTI_THREAD
520 
521 	return (thr_self());
522 
523 #else /* DCS_MULTI_THREAD */
524 
525 	return (getpid());
526 
527 #endif /* DCS_MULTI_THREAD */
528 }
529 
530 
531 /*
532  * ses_handler:
533  *
534  * Handle initialization and processing of a session. Initializes a session
535  * and enters a loop which waits for requests. When a request comes in, it
536  * is dispatched. When the session is terminated, the loop exits and the
537  * session is cleaned up.
538  */
539 static void *
540 ses_handler(void *arg)
541 {
542 	session_t		*sp;
543 	rdr_msg_hdr_t		op_hdr;
544 	cfga_params_t		op_data;
545 	int			rcv_status;
546 	sigset_t		block_set;
547 	struct sigaction	act;
548 
549 	static char *dcs_state_str[] = {
550 		"unknown state",
551 		"DCS_CONNECTED",
552 		"DCS_SES_REQ",
553 		"DCS_SES_ESTBL",
554 		"DCS_CONF_PENDING",
555 		"DCS_CONF_DONE",
556 		"DCS_SES_END"
557 	};
558 
559 
560 	if (ses_alloc() == -1) {
561 		(void) rdr_close((int)arg);
562 		return ((void *)-1);
563 	}
564 
565 	if ((sp = curr_ses()) == NULL) {
566 		ses_close(DCS_ERROR);
567 		return (NULL);
568 	}
569 
570 	/* initialize session information */
571 	memset(sp, 0, sizeof (session_t));
572 	sp->state = DCS_CONNECTED;
573 	sp->random_resp = lrand48();
574 	sp->fd = (int)arg;
575 	sp->id = curr_ses_id();
576 
577 	/* initially, block all signals and cancels */
578 	(void) sigfillset(&block_set);
579 	(void) sigprocmask(SIG_BLOCK, &block_set, NULL);
580 
581 	/* set the abort handler for this session */
582 	(void) memset(&act, 0, sizeof (act));
583 	act.sa_handler = abort_handler;
584 	(void) sigaction(SIGINT, &act, NULL);
585 
586 	DCS_DBG(DBG_SES, "session handler starting...");
587 
588 	/*
589 	 * Process all requests in the session until the
590 	 * session is terminated
591 	 */
592 	for (;;) {
593 
594 		DCS_DBG(DBG_STATE, "session state: %s",
595 		    dcs_state_str[sp->state]);
596 
597 		if (sp->state == DCS_SES_END) {
598 			break;
599 		}
600 
601 		(void) memset(&op_hdr, 0, sizeof (op_hdr));
602 		(void) memset(&op_data, 0, sizeof (op_data));
603 
604 		rcv_status = rdr_rcv_msg(sp->fd, &op_hdr, &op_data,
605 		    DCS_RCV_TIMEOUT);
606 
607 		if (rcv_status != RDR_OK) {
608 
609 			switch (rcv_status) {
610 
611 			case RDR_TIMEOUT:
612 				DCS_DBG(DBG_SES, "receive timed out");
613 				break;
614 
615 			case RDR_DISCONNECT:
616 				dcs_log_msg(LOG_NOTICE, DCS_DISCONNECT);
617 				break;
618 
619 			case RDR_ABORTED:
620 				dcs_log_msg(LOG_INFO, DCS_SES_ABORTED);
621 				break;
622 
623 			case RDR_MSG_INVAL:
624 				/*
625 				 * Only log invalid messages if a session has
626 				 * already been established. Logging invalid
627 				 * session request messages could flood syslog.
628 				 */
629 				if (sp->state != DCS_CONNECTED) {
630 					dcs_log_msg(LOG_WARNING, DCS_MSG_INVAL);
631 				} else {
632 					DCS_DBG(DBG_SES, "received an invalid "
633 					    "message");
634 				}
635 
636 				break;
637 
638 			default:
639 				dcs_log_msg(LOG_ERR, DCS_RECEIVE_ERR);
640 				break;
641 			}
642 
643 			/*
644 			 * We encountered an unrecoverable error,
645 			 * so exit this session handler.
646 			 */
647 			break;
648 
649 		} else {
650 			/* handle the message */
651 			dcs_dispatch_message(&op_hdr, &op_data);
652 			rdr_cleanup_params(op_hdr.message_opcode, &op_data);
653 		}
654 	}
655 
656 	DCS_DBG(DBG_SES, "connection closed");
657 
658 	/* clean up */
659 	(void) rdr_close(sp->fd);
660 	(void) ses_free();
661 
662 #ifdef DCS_MULTI_THREAD
663 	ses_thr_exit();
664 #endif /* DCS_MULTI_THREAD */
665 
666 	return (0);
667 }
668 
669 
670 /*
671  * abort_handler:
672  *
673  * Handle a request to abort a session. This function should be installed
674  * as the signal handler for SIGINT. It sends a message to the client
675  * indicating that the session was aborted, and that the operation failed
676  * as a result. The session then terminates, and the thread or process
677  * handling the session exits.
678  */
679 void
680 abort_handler(void)
681 {
682 	session_t	*sp;
683 	rdr_msg_hdr_t	op_hdr;
684 	cfga_params_t	op_data;
685 
686 
687 	/* get the current session information */
688 	if ((sp = curr_ses()) == NULL) {
689 		ses_close(DCS_ERROR);
690 #ifdef DCS_MULTI_THREAD
691 		ses_thr_exit();
692 		thr_exit(0);
693 #else /* DCS_MULTI_THREAD */
694 		exit(0);
695 #endif /* DCS_MULTI_THREAD */
696 	}
697 
698 	DCS_DBG(DBG_MSG, "abort_handler()");
699 
700 	/* prepare header information */
701 	init_msg(&op_hdr);
702 	op_hdr.message_opcode = sp->curr_msg.hdr->message_opcode;
703 	op_hdr.data_type = RDR_REPLY;
704 	op_hdr.status = DCS_SES_ABORTED;
705 
706 	/* no operation specific data */
707 	(void) memset(&op_data, 0, sizeof (op_data));
708 
709 	PRINT_MSG_DBG(DCS_SEND, &op_hdr);
710 
711 	(void) rdr_snd_msg(sp->fd, &op_hdr, &op_data, DCS_SND_TIMEOUT);
712 
713 	DCS_DBG(DBG_INFO, "abort_handler: connection closed");
714 
715 	/* clean up */
716 	rdr_cleanup_params(op_hdr.message_opcode, sp->curr_msg.params);
717 	(void) rdr_close(sp->fd);
718 	(void) ses_free();
719 
720 	dcs_log_msg(LOG_INFO, DCS_SES_ABORTED);
721 
722 #ifdef DCS_MULTI_THREAD
723 	ses_thr_exit();
724 	thr_exit(0);
725 #else /* DCS_MULTI_THREAD */
726 	exit(0);
727 #endif /* DCS_MULTI_THREAD */
728 }
729 
730 
731 #ifndef DCS_MULTI_THREAD
732 
733 /*
734  * exit_handler:
735  *
736  * If multiple processes are used, this function is used to record
737  * the fact that a child process has exited. In order to make sure
738  * that all zombie processes are released, a waitpid() is performed
739  * for the child that has exited.
740  */
741 /* ARGSUSED */
742 static void
743 exit_handler(int sig, siginfo_t *info, void *context)
744 {
745 	sessions--;
746 
747 	if (info != NULL) {
748 		(void) waitpid(info->si_pid, NULL, 0);
749 	}
750 }
751 
752 #endif /* !DCS_MULTI_THREAD */
753 
754 
755 /*
756  * ses_alloc:
757  *
758  * Allocate the memory required for the global session structure.
759  * If multiple threads are used, create a thread specific data
760  * key. This will only occur the first time that this function
761  * gets called.
762  */
763 static int
764 ses_alloc(void)
765 {
766 	session_t	*sp;
767 
768 #ifdef DCS_MULTI_THREAD
769 
770 	int		thr_err;
771 
772 	thr_err = thr_keycreate_once(&ses_key, NULL);
773 	if (thr_err)
774 		return (-1);
775 
776 #endif /* DCS_MULTI_THREAD */
777 
778 	DCS_DBG(DBG_SES, "allocating session memory");
779 
780 	sp = (session_t *)malloc(sizeof (session_t));
781 
782 	if (!sp) {
783 		dcs_log_msg(LOG_ERR, DCS_INT_ERR, "malloc", strerror(errno));
784 		return (-1);
785 	}
786 
787 #ifdef DCS_MULTI_THREAD
788 
789 	thr_err = thr_setspecific(ses_key, sp);
790 
791 	return ((thr_err) ? -1 : 0);
792 
793 #else /* DCS_MULTI_THREAD */
794 
795 	/* make the data global */
796 	ses = sp;
797 
798 	return (0);
799 
800 #endif /* DCS_MULTI_THREAD */
801 }
802 
803 
804 /*
805  * ses_free:
806  *
807  * Deallocate the memory associated with the global session structure.
808  */
809 static int
810 ses_free(void)
811 {
812 	session_t	*sp;
813 
814 
815 	DCS_DBG(DBG_SES, "freeing session memory");
816 
817 	if ((sp = curr_ses()) == NULL) {
818 		ses_close(DCS_ERROR);
819 		return (-1);
820 	}
821 
822 	if (sp) {
823 		(void) free((void *)sp);
824 	}
825 
826 	return (0);
827 }
828 
829 
830 #ifdef DCS_MULTI_THREAD
831 
832 /*
833  * ses_thr_exit:
834  *
835  * If multiple threads are used, this function is used to record the
836  * fact that a child thread has exited. In addition, the condition
837  * variable is signaled so that the main thread can wakeup and begin
838  * accepting connections again.
839  */
840 static void
841 ses_thr_exit()
842 {
843 	mutex_lock(&sessions_lock);
844 
845 	sessions--;
846 
847 	cond_signal(&sessions_cv);
848 
849 	mutex_unlock(&sessions_lock);
850 }
851 
852 #endif /* DCS_MULTI_THREAD */
853