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