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 * Copyright 2006 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #pragma ident "%Z%%M% %I% %E% SMI" 27 28 /* 29 * poold - dynamically adjust pool configuration according to load. 30 */ 31 #include <errno.h> 32 #include <jni.h> 33 #include <libintl.h> 34 #include <limits.h> 35 #include <link.h> 36 #include <locale.h> 37 #include <poll.h> 38 #include <pool.h> 39 #include <priv.h> 40 #include <pthread.h> 41 #include <signal.h> 42 #include <stdio.h> 43 #include <stdlib.h> 44 #include <string.h> 45 #include <syslog.h> 46 #include <unistd.h> 47 48 #include <sys/stat.h> 49 #include <sys/types.h> 50 #include <sys/ucontext.h> 51 #include "utils.h" 52 53 #define POOLD_DEF_CLASSPATH "/usr/lib/pool/JPool.jar" 54 #define POOLD_DEF_LIBPATH "/usr/lib/pool" 55 #define SMF_SVC_INSTANCE "svc:/system/pools/dynamic:default" 56 57 #if defined(sparc) 58 #define PLAT "sparc" 59 #else 60 #if defined(i386) 61 #define PLAT "i386" 62 #else 63 #error Unrecognized platform. 64 #endif 65 #endif 66 67 #define CLASS_FIELD_DESC(class_desc) "L" class_desc ";" 68 69 #define LEVEL_CLASS_DESC "java/util/logging/Level" 70 #define POOLD_CLASS_DESC "com/sun/solaris/domain/pools/Poold" 71 #define SEVERITY_CLASS_DESC "com/sun/solaris/service/logging/Severity" 72 #define STRING_CLASS_DESC "java/lang/String" 73 #define SYSTEM_CLASS_DESC "java/lang/System" 74 #define LOGGER_CLASS_DESC "java/util/logging/Logger" 75 76 extern char *optarg; 77 78 static const char *pname; 79 80 static enum { 81 LD_TERMINAL = 1, 82 LD_SYSLOG, 83 LD_JAVA 84 } log_dest = LD_SYSLOG; 85 86 static const char PNAME_FMT[] = "%s: "; 87 static const char ERRNO_FMT[] = ": %s"; 88 89 static pthread_mutex_t jvm_lock = PTHREAD_MUTEX_INITIALIZER; 90 static JavaVM *jvm; /* protected by jvm_lock */ 91 static int instance_running; /* protected by jvm_lock */ 92 static int lflag; /* specifies poold logging mode */ 93 94 static jmethodID log_mid; 95 static jobject severity_err; 96 static jobject severity_notice; 97 static jobject base_log; 98 static jclass poold_class; 99 static jobject poold_instance; 100 101 static sigset_t hdl_set; 102 103 static void pu_notice(const char *fmt, ...); 104 static void pu_die(const char *fmt, ...) __NORETURN; 105 106 static void 107 usage(void) 108 { 109 (void) fprintf(stderr, gettext("Usage:\t%s [-l <level>]\n"), pname); 110 111 exit(E_USAGE); 112 } 113 114 static void 115 check_thread_attached(JNIEnv **env) 116 { 117 int ret; 118 119 ret = (*jvm)->GetEnv(jvm, (void **)env, JNI_VERSION_1_4); 120 if (*env == NULL) { 121 if (ret == JNI_EVERSION) { 122 /* 123 * Avoid recursively calling 124 * check_thread_attached() 125 */ 126 if (log_dest == LD_JAVA) 127 log_dest = LD_TERMINAL; 128 pu_notice(gettext("incorrect JNI version")); 129 exit(E_ERROR); 130 } 131 if ((*jvm)->AttachCurrentThreadAsDaemon(jvm, (void **)env, 132 NULL) != 0) { 133 /* 134 * Avoid recursively calling 135 * check_thread_attached() 136 */ 137 if (log_dest == LD_JAVA) 138 log_dest = LD_TERMINAL; 139 pu_notice(gettext("thread attach failed")); 140 exit(E_ERROR); 141 } 142 } 143 } 144 145 /* 146 * Output a message to the designated logging destination. 147 * 148 * severity - Specified the severity level when using LD_JAVA logging 149 * fmt - specified the format of the output message 150 * alist - varargs used in the output message 151 */ 152 static void 153 pu_output(int severity, const char *fmt, va_list alist) 154 { 155 int err = errno; 156 char line[255] = ""; 157 jobject jseverity; 158 jobject jline; 159 JNIEnv *env = NULL; 160 161 if (pname != NULL && log_dest == LD_TERMINAL) 162 (void) snprintf(line, sizeof (line), gettext(PNAME_FMT), pname); 163 164 (void) vsnprintf(line + strlen(line), sizeof (line) - strlen(line), 165 fmt, alist); 166 167 if (line[strlen(line) - 1] != '\n') 168 (void) snprintf(line + strlen(line), sizeof (line) - 169 strlen(line), gettext(ERRNO_FMT), strerror(err)); 170 else 171 line[strlen(line) - 1] = 0; 172 173 switch (log_dest) { 174 case LD_TERMINAL: 175 (void) fprintf(stderr, "%s\n", line); 176 (void) fflush(stderr); 177 break; 178 case LD_SYSLOG: 179 syslog(LOG_ERR, "%s", line); 180 break; 181 case LD_JAVA: 182 if (severity == LOG_ERR) 183 jseverity = severity_err; 184 else 185 jseverity = severity_notice; 186 187 if (jvm) { 188 check_thread_attached(&env); 189 if ((jline = (*env)->NewStringUTF(env, line)) != NULL) 190 (*env)->CallVoidMethod(env, base_log, log_mid, 191 jseverity, jline); 192 } 193 } 194 } 195 196 /* 197 * Notify the user with the supplied message. 198 */ 199 /*PRINTFLIKE1*/ 200 static void 201 pu_notice(const char *fmt, ...) 202 { 203 va_list alist; 204 205 va_start(alist, fmt); 206 pu_output(LOG_NOTICE, fmt, alist); 207 va_end(alist); 208 } 209 210 /* 211 * Stop the application executing inside the JVM. Always ensure that jvm_lock 212 * is held before invoking this function. 213 */ 214 static void 215 halt_application(void) 216 { 217 JNIEnv *env = NULL; 218 jmethodID poold_shutdown_mid; 219 220 if (jvm && instance_running) { 221 check_thread_attached(&env); 222 if ((poold_shutdown_mid = (*env)->GetMethodID( 223 env, poold_class, "shutdown", "()V")) != NULL) { 224 (*env)->CallVoidMethod(env, poold_instance, 225 poold_shutdown_mid); 226 } else { 227 if (lflag && (*env)->ExceptionOccurred(env)) { 228 (*env)->ExceptionDescribe(env); 229 pu_notice("could not invoke proper shutdown\n"); 230 } 231 } 232 instance_running = 0; 233 } 234 } 235 236 /* 237 * Warn the user with the supplied error message, halt the application, 238 * destroy the JVM and then exit the process. 239 */ 240 /*PRINTFLIKE1*/ 241 static void 242 pu_die(const char *fmt, ...) 243 { 244 va_list alist; 245 246 va_start(alist, fmt); 247 pu_output(LOG_ERR, fmt, alist); 248 va_end(alist); 249 halt_application(); 250 if (jvm) { 251 (*jvm)->DestroyJavaVM(jvm); 252 jvm = NULL; 253 } 254 exit(E_ERROR); 255 } 256 257 /* 258 * Warn the user with the supplied error message and halt the 259 * application. This function is very similar to pu_die(). However, 260 * this function is designed to be called from the signal handling 261 * routine (handle_sig()) where although we wish to let the user know 262 * that an error has occurred, we do not wish to destroy the JVM or 263 * exit the process. 264 */ 265 /*PRINTFLIKE1*/ 266 static void 267 pu_terminate(const char *fmt, ...) 268 { 269 va_list alist; 270 271 va_start(alist, fmt); 272 pu_output(LOG_ERR, fmt, alist); 273 va_end(alist); 274 halt_application(); 275 } 276 277 /* 278 * If SIGHUP is invoked, we should just re-initialize poold. Since 279 * there is no easy way to determine when it's safe to re-initialzie 280 * poold, simply update a dummy property on the system element to 281 * force pool_conf_update() to detect a change. 282 * 283 * Both SIGTERM and SIGINT are interpreted as instructions to 284 * shutdown. 285 */ 286 /*ARGSUSED*/ 287 static void * 288 handle_sig(void *arg) 289 { 290 pool_conf_t *conf = NULL; 291 pool_elem_t *pe; 292 pool_value_t *val; 293 const char *err_desc; 294 int keep_handling = 1; 295 296 while (keep_handling) { 297 int sig; 298 char buf[SIG2STR_MAX]; 299 300 if ((sig = sigwait(&hdl_set)) < 0) { 301 /* 302 * We used forkall() previously to ensure that 303 * all threads started by the JVM are 304 * duplicated in the child. Since forkall() 305 * can cause blocking system calls to be 306 * interrupted, check to see if the errno is 307 * EINTR and if it is wait again. 308 */ 309 if (errno == EINTR) 310 continue; 311 (void) pthread_mutex_lock(&jvm_lock); 312 pu_terminate("unexpected error: %d\n", errno); 313 keep_handling = 0; 314 } else 315 (void) pthread_mutex_lock(&jvm_lock); 316 (void) sig2str(sig, buf); 317 switch (sig) { 318 case SIGHUP: 319 if ((conf = pool_conf_alloc()) == NULL) { 320 err_desc = pool_strerror(pool_error()); 321 goto destroy; 322 } 323 if (pool_conf_open(conf, pool_dynamic_location(), 324 PO_RDWR) != 0) { 325 err_desc = pool_strerror(pool_error()); 326 goto destroy; 327 } 328 329 if ((val = pool_value_alloc()) == NULL) { 330 err_desc = pool_strerror(pool_error()); 331 goto destroy; 332 } 333 pe = pool_conf_to_elem(conf); 334 pool_value_set_bool(val, 1); 335 if (pool_put_property(conf, pe, "system.poold.sighup", 336 val) != PO_SUCCESS) { 337 err_desc = pool_strerror(pool_error()); 338 pool_value_free(val); 339 goto destroy; 340 } 341 pool_value_free(val); 342 (void) pool_rm_property(conf, pe, 343 "system.poold.sighup"); 344 if (pool_conf_commit(conf, 0) != PO_SUCCESS) { 345 err_desc = pool_strerror(pool_error()); 346 goto destroy; 347 } 348 (void) pool_conf_close(conf); 349 pool_conf_free(conf); 350 break; 351 destroy: 352 if (conf) { 353 (void) pool_conf_close(conf); 354 pool_conf_free(conf); 355 } 356 pu_terminate(err_desc); 357 keep_handling = 0; 358 break; 359 case SIGINT: 360 case SIGTERM: 361 default: 362 pu_terminate("terminating due to signal: SIG%s\n", buf); 363 keep_handling = 0; 364 break; 365 } 366 (void) pthread_mutex_unlock(&jvm_lock); 367 } 368 pthread_exit(NULL); 369 /*NOTREACHED*/ 370 return (NULL); 371 } 372 373 /* 374 * Return the name of the process 375 */ 376 static const char * 377 pu_getpname(const char *arg0) 378 { 379 char *p; 380 381 /* 382 * Guard against '/' at end of command invocation. 383 */ 384 for (;;) { 385 p = strrchr(arg0, '/'); 386 if (p == NULL) { 387 pname = arg0; 388 break; 389 } else { 390 if (*(p + 1) == '\0') { 391 *p = '\0'; 392 continue; 393 } 394 395 pname = p + 1; 396 break; 397 } 398 } 399 400 return (pname); 401 } 402 403 int 404 main(int argc, char *argv[]) 405 { 406 char c; 407 char log_severity[16] = ""; 408 JavaVMInitArgs vm_args; 409 JavaVMOption vm_opts[5]; 410 int nopts = 0; 411 const char *classpath; 412 const char *libpath; 413 size_t len; 414 const char *err_desc; 415 JNIEnv *env; 416 jmethodID poold_getinstancewcl_mid; 417 jmethodID poold_run_mid; 418 jobject log_severity_string = NULL; 419 jobject log_severity_obj = NULL; 420 jclass severity_class; 421 jmethodID severity_cons_mid; 422 jfieldID base_log_fid; 423 pthread_t hdl_thread; 424 FILE *p; 425 426 (void) pthread_mutex_lock(&jvm_lock); 427 pname = pu_getpname(argv[0]); 428 openlog(pname, 0, LOG_DAEMON); 429 (void) chdir("/"); 430 431 (void) setlocale(LC_ALL, ""); 432 #if !defined(TEXT_DOMAIN) /* Should be defined with cc -D. */ 433 #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it wasn't. */ 434 #endif 435 (void) textdomain(TEXT_DOMAIN); 436 437 opterr = 0; 438 while ((c = getopt(argc, argv, "l:P")) != EOF) { 439 switch (c) { 440 case 'l': /* -l option */ 441 lflag++; 442 (void) strlcpy(log_severity, optarg, 443 sizeof (log_severity)); 444 log_dest = LD_TERMINAL; 445 break; 446 default: 447 usage(); 448 /*NOTREACHED*/ 449 } 450 } 451 452 /* 453 * Check permission 454 */ 455 if (!priv_ineffect(PRIV_SYS_RES_CONFIG)) 456 pu_die(gettext(ERR_PRIVILEGE), PRIV_SYS_RES_CONFIG); 457 458 /* 459 * In order to avoid problems with arbitrary thread selection 460 * when handling asynchronous signals, dedicate a thread to 461 * look after these signals. 462 */ 463 if (sigemptyset(&hdl_set) < 0 || 464 sigaddset(&hdl_set, SIGHUP) < 0 || 465 sigaddset(&hdl_set, SIGTERM) < 0 || 466 sigaddset(&hdl_set, SIGINT) < 0 || 467 pthread_sigmask(SIG_BLOCK, &hdl_set, NULL) || 468 pthread_create(&hdl_thread, NULL, handle_sig, NULL)) 469 pu_die(gettext("can't install signal handler")); 470 471 /* 472 * If the -l flag is supplied, terminate the SMF service and 473 * run interactively from the command line. 474 */ 475 if (lflag) { 476 char *cmd = "/usr/sbin/svcadm disable -st " SMF_SVC_INSTANCE; 477 478 if (getenv("SMF_FMRI") != NULL) 479 pu_die("-l option illegal: %s\n", SMF_SVC_INSTANCE); 480 /* 481 * Since disabling a service isn't synchronous, use the 482 * synchronous option from svcadm to achieve synchronous 483 * behaviour. 484 * This is not very satisfactory, but since this is only 485 * for use in debugging scenarios, it will do until there 486 * is a C API to synchronously shutdown a service in SMF. 487 */ 488 if ((p = popen(cmd, "w")) == NULL || pclose(p) != 0) 489 pu_die("could not temporarily disable service: %s\n", 490 SMF_SVC_INSTANCE); 491 } else { 492 /* 493 * Check if we are running as a SMF service. If we 494 * aren't, terminate this process after enabling the 495 * service. 496 */ 497 if (getenv("SMF_FMRI") == NULL) { 498 char *cmd = "/usr/sbin/svcadm enable -s " \ 499 SMF_SVC_INSTANCE; 500 if ((p = popen(cmd, "w")) == NULL || pclose(p) != 0) 501 pu_die("could not enable " 502 "service: %s\n", SMF_SVC_INSTANCE); 503 return (E_PO_SUCCESS); 504 } 505 } 506 507 /* 508 * Establish the classpath and LD_LIBRARY_PATH for native 509 * methods, and get the interpreter going. 510 */ 511 if ((classpath = getenv("POOLD_CLASSPATH")) == NULL) { 512 classpath = POOLD_DEF_CLASSPATH; 513 } else { 514 const char *cur = classpath; 515 516 /* 517 * Check the components to make sure they're absolute 518 * paths. 519 */ 520 while (cur != NULL && *cur) { 521 if (*cur != '/') 522 pu_die(gettext( 523 "POOLD_CLASSPATH must contain absolute " 524 "components\n")); 525 cur = strchr(cur + 1, ':'); 526 } 527 } 528 vm_opts[nopts].optionString = malloc(len = strlen(classpath) + 529 strlen("-Djava.class.path=") + 1); 530 (void) strlcpy(vm_opts[nopts].optionString, "-Djava.class.path=", len); 531 (void) strlcat(vm_opts[nopts++].optionString, classpath, len); 532 533 if ((libpath = getenv("POOLD_LD_LIBRARY_PATH")) == NULL) 534 libpath = POOLD_DEF_LIBPATH; 535 vm_opts[nopts].optionString = malloc(len = strlen(libpath) + 536 strlen("-Djava.library.path=") + 1); 537 (void) strlcpy(vm_opts[nopts].optionString, "-Djava.library.path=", 538 len); 539 (void) strlcat(vm_opts[nopts++].optionString, libpath, len); 540 541 vm_opts[nopts++].optionString = "-Xrs"; 542 vm_opts[nopts++].optionString = "-enableassertions"; 543 544 vm_args.options = vm_opts; 545 vm_args.nOptions = nopts; 546 vm_args.ignoreUnrecognized = JNI_FALSE; 547 vm_args.version = 0x00010002; 548 549 if (JNI_CreateJavaVM(&jvm, (void **)&env, &vm_args) < 0) 550 pu_die(gettext("can't create Java VM")); 551 552 /* 553 * Locate the Poold class and construct an instance. A side 554 * effect of this is that the poold instance's logHelper will be 555 * initialized, establishing loggers for logging errors from 556 * this point on. (Note, in the event of an unanticipated 557 * exception, poold will invoke die() itself.) 558 */ 559 err_desc = gettext("JVM-related error initializing poold\n"); 560 if ((poold_class = (*env)->FindClass(env, POOLD_CLASS_DESC)) == NULL) 561 goto destroy; 562 if ((poold_getinstancewcl_mid = (*env)->GetStaticMethodID(env, 563 poold_class, "getInstanceWithConsoleLogging", "(" 564 CLASS_FIELD_DESC(SEVERITY_CLASS_DESC) ")" 565 CLASS_FIELD_DESC(POOLD_CLASS_DESC))) == NULL) 566 goto destroy; 567 if ((poold_run_mid = (*env)->GetMethodID(env, poold_class, "run", 568 "()V")) == NULL) 569 goto destroy; 570 if ((severity_class = (*env)->FindClass(env, SEVERITY_CLASS_DESC)) 571 == NULL) 572 goto destroy; 573 if ((severity_cons_mid = (*env)->GetStaticMethodID(env, severity_class, 574 "getSeverityWithName", "(" CLASS_FIELD_DESC(STRING_CLASS_DESC) ")" 575 CLASS_FIELD_DESC(SEVERITY_CLASS_DESC))) == NULL) 576 goto destroy; 577 578 /* 579 * -l <level> was specified, indicating that messages are to be 580 * logged to the console only. 581 */ 582 if (strlen(log_severity) > 0) { 583 if ((log_severity_string = (*env)->NewStringUTF(env, 584 log_severity)) == NULL) 585 goto destroy; 586 if ((log_severity_obj = (*env)->CallStaticObjectMethod(env, 587 severity_class, severity_cons_mid, log_severity_string)) == 588 NULL) { 589 err_desc = gettext("invalid level specified\n"); 590 goto destroy; 591 } 592 } else 593 log_severity_obj = NULL; 594 595 if ((poold_instance = (*env)->CallStaticObjectMethod(env, poold_class, 596 poold_getinstancewcl_mid, log_severity_obj)) == NULL) 597 goto destroy; 598 599 /* 600 * Grab a global reference to poold for use in our signal 601 * handlers. 602 */ 603 poold_instance = (*env)->NewGlobalRef(env, poold_instance); 604 605 /* 606 * Ready LD_JAVA logging. 607 */ 608 err_desc = gettext("cannot initialize logging\n"); 609 if ((log_severity_string = (*env)->NewStringUTF(env, "err")) == NULL) 610 goto destroy; 611 if (!(severity_err = (*env)->CallStaticObjectMethod(env, severity_class, 612 severity_cons_mid, log_severity_string))) 613 goto destroy; 614 if (!(severity_err = (*env)->NewGlobalRef(env, severity_err))) 615 goto destroy; 616 617 if ((log_severity_string = (*env)->NewStringUTF(env, "notice")) == NULL) 618 goto destroy; 619 if (!(severity_notice = (*env)->CallStaticObjectMethod(env, 620 severity_class, severity_cons_mid, log_severity_string))) 621 goto destroy; 622 if (!(severity_notice = (*env)->NewGlobalRef(env, severity_notice))) 623 goto destroy; 624 625 if (!(base_log_fid = (*env)->GetStaticFieldID(env, poold_class, 626 "BASE_LOG", CLASS_FIELD_DESC(LOGGER_CLASS_DESC)))) 627 goto destroy; 628 if (!(base_log = (*env)->GetStaticObjectField(env, poold_class, 629 base_log_fid))) 630 goto destroy; 631 if (!(base_log = (*env)->NewGlobalRef(env, base_log))) 632 goto destroy; 633 if (!(log_mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, 634 base_log), "log", "(" CLASS_FIELD_DESC(LEVEL_CLASS_DESC) 635 CLASS_FIELD_DESC(STRING_CLASS_DESC) ")V"))) 636 goto destroy; 637 log_dest = LD_JAVA; 638 639 /* 640 * If invoked directly and -l is specified, forking is not 641 * desired. 642 */ 643 if (!lflag) 644 switch (forkall()) { 645 case 0: 646 (void) setsid(); 647 (void) fclose(stdin); 648 (void) fclose(stdout); 649 (void) fclose(stderr); 650 break; 651 case -1: 652 pu_die(gettext("cannot fork")); 653 /*NOTREACHED*/ 654 default: 655 return (E_PO_SUCCESS); 656 } 657 658 instance_running = 1; 659 (void) pthread_mutex_unlock(&jvm_lock); 660 661 (*env)->CallVoidMethod(env, poold_instance, poold_run_mid); 662 663 (void) pthread_mutex_lock(&jvm_lock); 664 if ((*env)->ExceptionOccurred(env)) { 665 goto destroy; 666 } 667 if (jvm) { 668 (*jvm)->DestroyJavaVM(jvm); 669 jvm = NULL; 670 } 671 (void) pthread_mutex_unlock(&jvm_lock); 672 return (E_PO_SUCCESS); 673 674 destroy: 675 if (lflag && (*env)->ExceptionOccurred(env)) 676 (*env)->ExceptionDescribe(env); 677 pu_die(err_desc); 678 } 679