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