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
ses_start(int fd)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
ses_close(int err_code)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
ses_abort(long ses_id)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
ses_abort_enable(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
ses_abort_disable(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
ses_setlocale(char * locale)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
ses_init_signals(sigset_t * mask)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
ses_sleep(int sec)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
ses_wait(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
ses_poll(struct pollfd fds[],nfds_t nfds,int timeout)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 *
curr_ses(void)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
curr_ses_id(void)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 *
ses_handler(void * arg)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
abort_handler(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
exit_handler(int sig,siginfo_t * info,void * context)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
ses_alloc(void)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
ses_free(void)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
ses_thr_exit()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