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