xref: /onnv-gate/usr/src/cmd/cmd-inet/usr.sadm/dhcpmgr/lib/service.c (revision 11590:38f29d9bce75)
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 2010 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #include <wordexp.h>
27 #include <string.h>
28 #include <malloc.h>
29 #include <sys/signal.h>
30 #include <libintl.h>
31 #include <arpa/inet.h>
32 #include <errno.h>
33 #include <dhcp_svc_private.h>
34 #include <dhcp_svc_confkey.h>
35 #include <jni.h>
36 #include <libscf.h>
37 #include <com_sun_dhcpmgr_bridge_Bridge.h>
38 
39 #include "exception.h"
40 #include "dd_misc.h"
41 #include "class_cache.h"
42 
43 #define	DHCP_SERVER_INST	"svc:/network/dhcp-server:default"
44 
45 #define	DHCPD_FNAME	"in.dhcpd"
46 #define	CONFOPT_MODE	0644
47 
48 /*
49  * Gets called when the library is loaded.
50  */
51 /*ARGSUSED*/
52 JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM * jvm,void * reserved)53 JNI_OnLoad(
54     JavaVM *jvm,
55     void *reserved)
56 {
57 	JNIEnv *env;
58 
59 	if ((*jvm)->GetEnv(jvm, (void**)&env, JNI_VERSION_1_2)) {
60 		return (JNI_ERR);
61 	}
62 
63 	init_class_cache();
64 	return (JNI_VERSION_1_2);
65 }
66 
67 /*
68  * Determine whether an upgrade of the datastore is necessary.
69  */
70 /*ARGSUSED*/
71 JNIEXPORT jboolean JNICALL
Java_com_sun_dhcpmgr_bridge_Bridge_isVersionCurrent(JNIEnv * env,jobject obj)72 Java_com_sun_dhcpmgr_bridge_Bridge_isVersionCurrent(
73     JNIEnv *env,
74     jobject obj)
75 {
76 	dsvc_datastore_t datastore;
77 	int cfgVersion;
78 	int curVersion;
79 	int rcode;
80 	jboolean result = JNI_FALSE;
81 
82 	/* Get the data store configuration */
83 	if (dd_get_conf_datastore_t(env, &datastore)) {
84 		cfgVersion = datastore.d_conver;
85 
86 		datastore.d_conver = DSVC_CUR_CONVER;
87 		free(datastore.d_location);
88 		datastore.d_location = NULL;
89 		rcode = status_dd(&datastore);
90 		if (rcode != DSVC_SUCCESS) {
91 			throw_libdhcpsvc_exception(env, rcode);
92 		} else {
93 			curVersion = datastore.d_conver;
94 
95 			if (curVersion == cfgVersion) {
96 				result = JNI_TRUE;
97 			}
98 		}
99 		dd_free_datastore_t(&datastore);
100 	}
101 
102 	return (result);
103 }
104 
105 /*
106  * Retrieve the data store object for the specified resource.
107  */
108 /*ARGSUSED*/
109 JNIEXPORT jobject JNICALL
Java_com_sun_dhcpmgr_bridge_Bridge_getDataStore(JNIEnv * env,jobject obj,jstring jresource)110 Java_com_sun_dhcpmgr_bridge_Bridge_getDataStore(
111     JNIEnv *env,
112     jobject obj,
113     jstring jresource)
114 {
115 	jclass ds_class;
116 	jmethodID ds_cons;
117 	jobject dsObject;
118 	jboolean avail;
119 	jint version;
120 	dsvc_datastore_t datastore;
121 	char *resource;
122 
123 	/* Make sure we have the classes & methods we need */
124 	ds_class = find_class(env, DS_CLASS);
125 	if (ds_class == NULL) {
126 		/* exception thrown */
127 		return (NULL);
128 	}
129 	ds_cons = get_methodID(env, ds_class, DS_CONS);
130 	if (ds_cons == NULL) {
131 		/* exception thrown */
132 		return (NULL);
133 	}
134 
135 	/* Retrieve the resource argument */
136 	if (!dd_jstring_to_UTF(env, jresource, &resource)) {
137 		/* exception thrown */
138 		return (NULL);
139 	}
140 
141 	datastore.d_conver = DSVC_CUR_CONVER;
142 	datastore.d_resource = resource;
143 	datastore.d_location = NULL;
144 	avail = JNI_FALSE;
145 	if (status_dd(&datastore) == DSVC_SUCCESS) {
146 		avail = JNI_TRUE;
147 		version = datastore.d_conver;
148 	}
149 
150 	dsObject = (*env)->NewObject(env, ds_class, ds_cons,
151 	    jresource, version, avail);
152 
153 	free(resource);
154 	return (dsObject);
155 }
156 
157 /*
158  * Retrieve the list of data stores available for DHCP.  Returns an array of
159  * DHCP datastore names.
160  */
161 /*ARGSUSED*/
162 JNIEXPORT jobjectArray JNICALL
Java_com_sun_dhcpmgr_bridge_Bridge_getDataStores(JNIEnv * env,jobject obj)163 Java_com_sun_dhcpmgr_bridge_Bridge_getDataStores(
164     JNIEnv *env,
165     jobject obj)
166 {
167 	jclass ds_class;
168 	jmethodID ds_cons;
169 	jobjectArray jlist = NULL;
170 	jobject jobj;
171 	jstring jstr;
172 	jboolean avail;
173 	jint version;
174 	char **list;
175 	dsvc_datastore_t datastore;
176 	int i, len;
177 
178 	/* Make sure we have the classes & methods we need */
179 	ds_class = find_class(env, DS_CLASS);
180 	if (ds_class == NULL) {
181 		/* exception thrown */
182 		return (NULL);
183 	}
184 	ds_cons = get_methodID(env, ds_class, DS_CONS);
185 	if (ds_cons == NULL) {
186 		/* exception thrown */
187 		return (NULL);
188 	}
189 
190 	/* Get the list */
191 	list = dd_data_stores(env);
192 	if ((*env)->ExceptionOccurred(env) != NULL) {
193 		return (NULL);
194 	}
195 
196 	/* Compute the length of the array, store in len */
197 	ARRAY_LENGTH(list, len);
198 
199 	/* Construct the array */
200 	jlist = (*env)->NewObjectArray(env, len, ds_class, NULL);
201 	if (jlist == NULL) {
202 		/* exception thrown */
203 		dd_free_data_stores(list);
204 		return (NULL);
205 	}
206 
207 	/* For each store, create an object and add it to the array */
208 	for (i = 0; i < len; ++i) {
209 
210 		jstr = (*env)->NewStringUTF(env, list[i]);
211 		if (jstr == NULL) {
212 			/* exception thrown */
213 			break;
214 		}
215 
216 		datastore.d_conver = DSVC_CUR_CONVER;
217 		datastore.d_resource = list[i];
218 		datastore.d_location = NULL;
219 		avail = JNI_FALSE;
220 		if (status_dd(&datastore) == DSVC_SUCCESS) {
221 			avail = JNI_TRUE;
222 			version = datastore.d_conver;
223 		}
224 
225 		jobj = (*env)->NewObject(env, ds_class, ds_cons,
226 		    jstr, version, avail);
227 		if (jobj == NULL) {
228 			/* exception thrown */
229 			break;
230 		}
231 
232 		(*env)->SetObjectArrayElement(env, jlist, i, jobj);
233 		if ((*env)->ExceptionOccurred(env) != NULL) {
234 			break;
235 		}
236 	}
237 
238 	dd_free_data_stores(list);
239 	return (jlist);
240 }
241 
242 /*
243  * Read the config file for DHCP and return its contents as a DhcpdOptions
244  * object.
245  */
246 /*ARGSUSED*/
247 JNIEXPORT jobject JNICALL
Java_com_sun_dhcpmgr_bridge_Bridge_readDefaults(JNIEnv * env,jobject obj)248 Java_com_sun_dhcpmgr_bridge_Bridge_readDefaults(
249     JNIEnv *env,
250     jobject obj)
251 {
252 	jclass cfg_class;
253 	jmethodID cfg_cons;
254 	jmethodID cfg_set;
255 	jobject cfgobj = NULL;
256 	dhcp_confopt_t *cfgs, *tcfgs;
257 
258 	/* Make sure we have the classes & methods we need */
259 	cfg_class = find_class(env, CFG_CLASS);
260 	if (cfg_class == NULL) {
261 		/* exception thrown */
262 		return (NULL);
263 	}
264 	cfg_cons = get_methodID(env, cfg_class, CFG_CONS);
265 	if (cfg_cons == NULL) {
266 		/* exception thrown */
267 		return (NULL);
268 	}
269 	cfg_set = get_methodID(env, cfg_class, CFG_SET);
270 	if (cfg_set == NULL) {
271 		/* exception thrown */
272 		return (NULL);
273 	}
274 
275 	/* Get the data */
276 	if (read_dsvc_conf(&cfgs) != 0) {
277 		throw_bridge_exception(env, strerror(errno));
278 	} else {
279 		/* Construct returned options object */
280 		cfgobj = (*env)->NewObject(env, cfg_class, cfg_cons);
281 		if (cfgobj == NULL) {
282 			/* exception thrown */
283 			free_dsvc_conf(cfgs);
284 			return (NULL);
285 		}
286 
287 		/* Load the option settings into the options object */
288 		tcfgs = cfgs;
289 		for (;;) {
290 			if (cfgs->co_type == DHCP_COMMENT) {
291 				(*env)->CallVoidMethod(env, cfgobj, cfg_set,
292 				    (*env)->NewStringUTF(env, cfgs->co_key),
293 				    (*env)->NewStringUTF(env, ""), JNI_TRUE);
294 			} else {
295 				if (cfgs->co_key == NULL) {
296 					break;
297 				}
298 				(*env)->CallVoidMethod(env, cfgobj, cfg_set,
299 				    (*env)->NewStringUTF(env, cfgs->co_key),
300 				    (*env)->NewStringUTF(env, cfgs->co_value),
301 				    JNI_FALSE);
302 			}
303 			if ((*env)->ExceptionOccurred(env) != NULL) {
304 				free_dsvc_conf(tcfgs);
305 				return (NULL);
306 			}
307 			++cfgs;
308 		}
309 		free_dsvc_conf(tcfgs);
310 	}
311 	return (cfgobj);
312 }
313 
314 /*
315  * Write the DHCP config file.  Takes a DhcpdOptions object as input
316  */
317 /*ARGSUSED*/
318 JNIEXPORT void JNICALL
Java_com_sun_dhcpmgr_bridge_Bridge_writeDefaults(JNIEnv * env,jobject obj,jobject jcfgs)319 Java_com_sun_dhcpmgr_bridge_Bridge_writeDefaults(
320     JNIEnv *env,
321     jobject obj,
322     jobject jcfgs)
323 {
324 	jclass cfg_class;
325 	jmethodID cfg_getall;
326 	jclass res_class;
327 	jmethodID res_getkey;
328 	jmethodID res_getval;
329 	jmethodID res_iscom;
330 	jobjectArray resArray;
331 	jsize reslen;
332 	jobject jobj, resobj;
333 	dhcp_confopt_t *cfgs;
334 	int i;
335 	jboolean comment;
336 	const char *tmpstr;
337 
338 	/* Make sure we can get at the classes we need */
339 	cfg_class = find_class(env, CFG_CLASS);
340 	if (cfg_class == NULL) {
341 		/* exception thrown */
342 		return;
343 	}
344 	cfg_getall = get_methodID(env, cfg_class, CFG_GETALL);
345 	if (cfg_getall == NULL) {
346 		/* exception thrown */
347 		return;
348 	}
349 	res_class = find_class(env, RES_CLASS);
350 	if (res_class == NULL) {
351 		/* exception thrown */
352 		return;
353 	}
354 	res_getkey = get_methodID(env, res_class, RES_GETKEY);
355 	res_getval = get_methodID(env, res_class, RES_GETVAL);
356 	res_iscom = get_methodID(env, res_class, RES_ISCOM);
357 	if (res_getkey == NULL || res_getval == NULL || res_iscom == NULL) {
358 		/* exception thrown */
359 		return;
360 	}
361 
362 	/* Get the resource array from the config object */
363 	resArray = (*env)->CallObjectMethod(env, jcfgs, cfg_getall);
364 	if ((*env)->ExceptionOccurred(env) != NULL) {
365 		return;
366 	}
367 	reslen = (*env)->GetArrayLength(env, resArray);
368 	/* Allocate array to convert into; extra zero'd item to signal end */
369 	cfgs = calloc(reslen+1, sizeof (dhcp_confopt_t));
370 	if (cfgs == NULL) {
371 		throw_memory_exception(env);
372 		return;
373 	}
374 
375 	/* Now copy data into local array */
376 	for (i = 0; i < reslen; ++i) {
377 		jobj = (*env)->GetObjectArrayElement(env, resArray, i);
378 		if (jobj == NULL) {
379 			/* exception thrown */
380 			free_dsvc_conf(cfgs);
381 			return;
382 		}
383 		/* Set record type */
384 		comment = (*env)->CallBooleanMethod(env, jobj, res_iscom);
385 		if ((*env)->ExceptionOccurred(env) != NULL) {
386 			return;
387 		}
388 		if (comment == JNI_TRUE) {
389 			cfgs[i].co_type = DHCP_COMMENT;
390 		} else {
391 			cfgs[i].co_type = DHCP_KEY;
392 		}
393 		/*
394 		 * Get the key from the object, convert to a char *,
395 		 * and then duplicate into the cfgs array so that
396 		 * free_dsvc_conf can be used correctly.
397 		 * Do the same thing for the value.
398 		 */
399 		resobj = (*env)->CallObjectMethod(env, jobj, res_getkey);
400 		tmpstr = (*env)->GetStringUTFChars(env, resobj, NULL);
401 		if (tmpstr == NULL) {
402 			/* exception thrown */
403 			free_dsvc_conf(cfgs);
404 			throw_bridge_exception(env,
405 			    gettext("Error converting key"));
406 			return;
407 		}
408 		cfgs[i].co_key = strdup(tmpstr);
409 		(*env)->ReleaseStringUTFChars(env, resobj, tmpstr);
410 		if (cfgs[i].co_key == NULL) {
411 			/* Out of memory, fail */
412 			free_dsvc_conf(cfgs);
413 			throw_memory_exception(env);
414 			return;
415 		}
416 		resobj = (*env)->CallObjectMethod(env, jobj, res_getval);
417 		tmpstr = (*env)->GetStringUTFChars(env, resobj, NULL);
418 		if (tmpstr == NULL) {
419 			free_dsvc_conf(cfgs);
420 			throw_bridge_exception(env,
421 			    gettext("Error converting value"));
422 			return;
423 		}
424 		cfgs[i].co_value = strdup(tmpstr);
425 		(*env)->ReleaseStringUTFChars(env, resobj, tmpstr);
426 		if (cfgs[i].co_value == NULL) {
427 			/* Out of memory, fail */
428 			free_dsvc_conf(cfgs);
429 			throw_memory_exception(env);
430 			return;
431 		}
432 	}
433 
434 	/* Now write the new data */
435 	if (write_dsvc_conf(cfgs, CONFOPT_MODE) != 0) {
436 		throw_bridge_exception(env, strerror(errno));
437 	}
438 	free_dsvc_conf(cfgs);
439 }
440 
441 /*
442  * Remove the DHCP config file
443  */
444 /*ARGSUSED*/
445 JNIEXPORT void JNICALL
Java_com_sun_dhcpmgr_bridge_Bridge_removeDefaults(JNIEnv * env,jobject obj)446 Java_com_sun_dhcpmgr_bridge_Bridge_removeDefaults(
447     JNIEnv *env,
448     jobject obj)
449 {
450 	if (delete_dsvc_conf() != 0) {
451 		throw_bridge_exception(env, strerror(errno));
452 	}
453 }
454 
455 /*
456  * Start up the daemon.
457  */
458 /*ARGSUSED*/
459 JNIEXPORT void JNICALL
Java_com_sun_dhcpmgr_bridge_Bridge_startup(JNIEnv * env,jobject obj)460 Java_com_sun_dhcpmgr_bridge_Bridge_startup(
461     JNIEnv *env,
462     jobject obj)
463 {
464 	char *s;
465 	int ret;
466 
467 	/*
468 	 * We first get the current state of the server according to
469 	 * svc.startd; if it's "disabled", we can just enable it.
470 	 * In any other case, we want to send a refresh so that
471 	 * dependencies are re-evaluated, which will be the case if the
472 	 * service was marked enabled by the profile, yet the
473 	 * config file didn't exist to allow it to run.
474 	 */
475 	if ((s = smf_get_state(DHCP_SERVER_INST)) != NULL) {
476 		if (strcmp(SCF_STATE_STRING_DISABLED, s) == 0)
477 			ret = smf_enable_instance(DHCP_SERVER_INST, 0);
478 		else
479 			ret = smf_refresh_instance(DHCP_SERVER_INST);
480 		free(s);
481 		if (ret == 0)
482 			return;
483 	}
484 
485 	/* Something wasn't right, return exception with error from smf */
486 	throw_bridge_exception(env, scf_strerror(scf_error()));
487 }
488 
489 /*
490  * Shut down the daemon.
491  */
492 /*ARGSUSED*/
493 JNIEXPORT void JNICALL
Java_com_sun_dhcpmgr_bridge_Bridge_shutdown(JNIEnv * env,jobject obj)494 Java_com_sun_dhcpmgr_bridge_Bridge_shutdown(
495     JNIEnv *env,
496     jobject obj)
497 {
498 	if (smf_disable_instance(DHCP_SERVER_INST, 0) != 0) {
499 		throw_bridge_exception(env, scf_strerror(scf_error()));
500 	}
501 }
502 
503 /*
504  * Tell the daemon to re-read the dhcptab.
505  */
506 /*ARGSUSED*/
507 JNIEXPORT void JNICALL
Java_com_sun_dhcpmgr_bridge_Bridge_reload(JNIEnv * env,jobject obj)508 Java_com_sun_dhcpmgr_bridge_Bridge_reload(
509     JNIEnv *env,
510     jobject obj)
511 {
512 	int err;
513 
514 	if ((err = dd_signal(DHCPD_FNAME, SIGHUP)) != 0) {
515 		if (err == -1) {
516 			/* dd_signal couldn't find in.dhcpd running */
517 			throw_not_running_exception(env);
518 		} else {
519 			throw_bridge_exception(env, strerror(err));
520 		}
521 	}
522 }
523 
524 /*
525  * Make the resource location.
526  */
527 /*ARGSUSED*/
528 JNIEXPORT void JNICALL
Java_com_sun_dhcpmgr_bridge_Bridge_makeLocation(JNIEnv * env,jobject obj,jobject jdatastore)529 Java_com_sun_dhcpmgr_bridge_Bridge_makeLocation(
530     JNIEnv *env,
531     jobject obj,
532     jobject jdatastore)
533 {
534 	dsvc_datastore_t datastore;
535 	int rcode;
536 
537 	/* Create a dsvc_datastore_t using args and DHCP config file */
538 	if (!dd_make_datastore_t(env, &datastore, jdatastore)) {
539 		/* exception thrown */
540 		return;
541 	}
542 
543 	/* If the location does not already exist, go create it. */
544 	if (status_dd(&datastore) != DSVC_SUCCESS) {
545 		rcode = mklocation_dd(&datastore);
546 		if (rcode != DSVC_SUCCESS) {
547 			throw_libdhcpsvc_exception(env, rcode);
548 		}
549 	}
550 
551 	dd_free_datastore_t(&datastore);
552 }
553 
554 /*
555  * Check if the server is running; returns true if so, false if not.
556  */
557 /*ARGSUSED*/
558 JNIEXPORT jboolean JNICALL
Java_com_sun_dhcpmgr_bridge_Bridge_isServerRunning(JNIEnv * env,jobject obj)559 Java_com_sun_dhcpmgr_bridge_Bridge_isServerRunning(
560     JNIEnv *env,
561     jobject obj)
562 {
563 	if (dd_getpid(DAEMON_FNAME) != (pid_t)-1) {
564 		return (JNI_TRUE);
565 	} else {
566 		return (JNI_FALSE);
567 	}
568 }
569 
570 /*
571  * Retrieve the list of interfaces on the system which are candidates for
572  * use by the DHCP daemon.  Returns an array of IPInterface objects.
573  */
574 /*ARGSUSED*/
575 JNIEXPORT jobjectArray JNICALL
Java_com_sun_dhcpmgr_bridge_Bridge_getInterfaces(JNIEnv * env,jobject obj)576 Java_com_sun_dhcpmgr_bridge_Bridge_getInterfaces(
577     JNIEnv *env,
578     jobject obj)
579 {
580 	jclass ipif_class;
581 	jmethodID ipif_cons;
582 	jobjectArray jlist = NULL;
583 	jobject jobj;
584 	jsize len;
585 	struct ip_interface **list;
586 	int i;
587 
588 	/* Locate the class and constructor we need */
589 	ipif_class = find_class(env, IPIF_CLASS);
590 	if (ipif_class == NULL) {
591 		/* exception thrown */
592 		return (NULL);
593 	}
594 	ipif_cons = get_methodID(env, ipif_class, IPIF_CONS);
595 	if (ipif_cons == NULL) {
596 		return (NULL);
597 	}
598 
599 	/* Retrieve interface list */
600 	list = dd_get_interfaces();
601 	if (list == NULL) {
602 		throw_bridge_exception(env,
603 		    gettext("Error in dd_get_interfaces"));
604 		return (NULL);
605 	}
606 	/* Compute length of list */
607 	ARRAY_LENGTH(list, len);
608 
609 	/* Construct the array */
610 	jlist = (*env)->NewObjectArray(env, len, ipif_class, NULL);
611 	if (jlist == NULL) {
612 		/* exception thrown */
613 		for (i = 0; i < len; i++) {
614 			free(list[i]);
615 		}
616 		free(list);
617 		return (NULL);
618 	}
619 
620 	/* For each interface, construct an object and add to the array */
621 	for (i = 0; i < len; ++i) {
622 		jobj = (*env)->NewObject(env, ipif_class, ipif_cons,
623 		    (*env)->NewStringUTF(env, list[i]->name),
624 		    (*env)->NewStringUTF(env, inet_ntoa(list[i]->addr)),
625 		    (*env)->NewStringUTF(env, inet_ntoa(list[i]->mask)));
626 
627 		if (jobj == NULL) {
628 			/* exception thrown */
629 			break;
630 		}
631 
632 		(*env)->SetObjectArrayElement(env, jlist, i, jobj);
633 		if ((*env)->ExceptionOccurred(env) != NULL) {
634 			break;
635 		}
636 	}
637 
638 	for (i = 0; i < len; i++) {
639 		free(list[i]);
640 	}
641 	free(list);
642 
643 	return (jlist);
644 }
645 
646 /*
647  * Parse a line into arguments.
648  */
649 /*ARGSUSED*/
650 JNIEXPORT jobjectArray JNICALL
Java_com_sun_dhcpmgr_bridge_Bridge_getArguments(JNIEnv * env,jobject obj,jstring jline)651 Java_com_sun_dhcpmgr_bridge_Bridge_getArguments(
652     JNIEnv *env,
653     jobject obj,
654     jstring jline)
655 {
656 	wordexp_t exp;
657 	int flags = WRDE_NOCMD;
658 	char *line;
659 	jclass str_class;
660 	jobjectArray jlist = NULL;
661 	jstring jarg;
662 	int i, ret;
663 
664 	/* Go ahead and get the class for a String class */
665 	str_class = (*env)->GetObjectClass(env, jline);
666 	if (str_class == NULL) {
667 		/* exception thrown */
668 		return (NULL);
669 	}
670 
671 	/* Retrieve the line argument */
672 	if (jline != NULL &&
673 	    (line = dd_jstring_to_native(env, jline)) == NULL) {
674 		/* exception thrown */
675 		return (NULL);
676 	}
677 
678 	/* Retrieve argument list */
679 	ret = wordexp(line, &exp, flags);
680 	free(line);
681 	if (ret != 0) {
682 		throw_wordexp_exception(env, ret);
683 		/* Free memory for the one error case where it's allocated */
684 		if (ret == WRDE_NOSPACE)
685 			wordfree(&exp);
686 		return (NULL);
687 	}
688 
689 	/* Construct the array */
690 	jlist = (*env)->NewObjectArray(env, exp.we_wordc, str_class, NULL);
691 	if (jlist == NULL) {
692 		/* exception thrown */
693 		wordfree(&exp);
694 		return (NULL);
695 	}
696 
697 	/* For each argument, create an object and add it to the array */
698 	for (i = 0; i < exp.we_wordc; i++) {
699 		jarg = dd_native_to_jstring(env, exp.we_wordv[i]);
700 		if (jarg == NULL) {
701 			/* exception thrown */
702 			break;
703 		}
704 
705 		(*env)->SetObjectArrayElement(env, jlist, i, jarg);
706 		if ((*env)->ExceptionOccurred(env) != NULL) {
707 			break;
708 		}
709 	}
710 
711 	wordfree(&exp);
712 	return (jlist);
713 }
714