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