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