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