xref: /illumos-gate/usr/src/cmd/pools/poold/poold.c (revision c54c769d4c1cde75dd28975fb0090a8f944651a6)
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 
76 extern char *optarg;
77 
78 static const char *pname;
79 
80 static enum {
81 	LD_TERMINAL = 1,
82 	LD_SYSLOG,
83 	LD_JAVA
84 } log_dest = LD_SYSLOG;
85 
86 static const char PNAME_FMT[] = "%s: ";
87 static const char ERRNO_FMT[] = ": %s";
88 
89 static pthread_mutex_t jvm_lock = PTHREAD_MUTEX_INITIALIZER;
90 static JavaVM *jvm;		/* protected by jvm_lock */
91 static int instance_running;	/* protected by jvm_lock */
92 static int lflag;		/* specifies poold logging mode */
93 
94 static jmethodID log_mid;
95 static jobject severity_err;
96 static jobject severity_notice;
97 static jobject base_log;
98 static jclass poold_class;
99 static jobject poold_instance;
100 
101 static sigset_t hdl_set;
102 
103 static void pu_notice(const char *fmt, ...);
104 static void pu_die(const char *fmt, ...) __NORETURN;
105 
106 static void
107 usage(void)
108 {
109 	(void) fprintf(stderr, gettext("Usage:\t%s [-l <level>]\n"), pname);
110 
111 	exit(E_USAGE);
112 }
113 
114 static void
115 check_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  */
152 static void
153 pu_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*/
200 static void
201 pu_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  */
214 static void
215 halt_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*/
241 static void
242 pu_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*/
266 static void
267 pu_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*/
287 static void *
288 handle_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;
351 destroy:
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  */
376 static const char *
377 pu_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 
403 int
404 main(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 
674 destroy:
675 	if (lflag && (*env)->ExceptionOccurred(env))
676 		(*env)->ExceptionDescribe(env);
677 	pu_die(err_desc);
678 }
679