xref: /illumos-gate/usr/src/cmd/pools/poold/poold.c (revision b10f758d69dd151326d3859af7e1d857ec9a6355)
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