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