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