1 /*
2  * Copyright (C) 2008 - 2011 Vivien Malerba <malerba@gnome-db.org>
3  * Copyright (C) 2010 David King <davidk@openismus.com>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA  02110-1301, USA.
19  */
20 
21 #define __GDA_JDBC_LIBMAIN__
22 
23 #include <glib/gi18n-lib.h>
24 #include <gmodule.h>
25 #include <libgda/gda-log.h>
26 #include <libgda/gda-server-provider-extra.h>
27 #include <libgda/binreloc/gda-binreloc.h>
28 #include "gda-jdbc.h"
29 #include "gda-jdbc-provider.h"
30 #include "jni-wrapper.h"
31 #include "jni-globals.h"
32 #include <unistd.h>
33 #include <sys/wait.h>
34 
35 #ifdef G_OS_WIN32
36 #include <windows.h>
37 #include <winreg.h>
38 #define EXPORT __declspec(dllexport)
39 #else
40 #define EXPORT
41 #endif
42 
43 static GModule *jvm_handle = NULL;
44 
45 /* JVM's symbols */
46 static GMutex vm_create;
47 static jint (*__CreateJavaVM) (JavaVM **pvm, void **penv, void *args) = NULL;
48 JavaVM *_jdbc_provider_java_vm = NULL;
49 
50 
51 static gchar      *module_path = NULL;
52 
53 EXPORT const gchar      **plugin_get_sub_names (void);
54 EXPORT const gchar       *plugin_get_sub_description (const gchar *name);
55 EXPORT gchar             *plugin_get_sub_dsn_spec (const gchar *name);
56 EXPORT GdaServerProvider *plugin_create_sub_provider (const gchar *name);
57 
58 /* locate and load JAVA virtual machine */
59 static gboolean    load_jvm ();
60 
61 /* get the name of the database server type from the JDBC driver name */
62 static const gchar *get_database_name_from_driver_name (const gchar *driver_name);
63 
64 
65 /* JDBC drivers installed */
66 typedef struct {
67 	const gchar *name;
68 	const gchar *native_db;
69 	gchar *descr;
70 } JdbcDriver;
71 static GHashTable *jdbc_drivers_hash = NULL; /* key = name, value = JdbcDriver pointer */
72 static gchar **sub_names = NULL;
73 static gint    sub_nb; /* size of sub_names */
74 
75 /* Functions executed when calling g_module_open() and g_module_close()
76  *
77  * The GModule is made resident here because I haven't found a way to completely unload the JVM, maybe
78  *    http://forums.sun.com/thread.jspa?threadID=5321076
79  * would be the solution...
80  */
81 EXPORT const gchar *
g_module_check_init(G_GNUC_UNUSED GModule * module)82 g_module_check_init (G_GNUC_UNUSED GModule *module)
83 {
84 	//g_module_make_resident (module);
85 	return NULL;
86 }
87 
88 EXPORT void
g_module_unload(G_GNUC_UNUSED GModule * module)89 g_module_unload (G_GNUC_UNUSED GModule *module)
90 {
91 	if (! __CreateJavaVM) {
92 		g_free (module_path);
93 		module_path = NULL;
94 	}
95 
96 	/*
97 	__CreateJavaVM = NULL;
98 	jni_wrapper_destroy_vm (_jdbc_provider_java_vm);
99 	_jdbc_provider_java_vm = NULL;
100 	if (jvm_handle) {
101 		g_module_close (jvm_handle);
102 		jvm_handle = NULL;
103 	}
104 	*/
105 }
106 
107 /*
108  * Normal plugin functions
109  */
110 EXPORT void
plugin_init(const gchar * real_path)111 plugin_init (const gchar *real_path)
112 {
113         if (real_path)
114                 module_path = g_strdup (real_path);
115 }
116 
117 static gboolean in_forked = FALSE;
118 static gchar **try_getting_drivers_list_forked (gboolean *out_forked_ok);
119 static void describe_driver_names (void);
120 
121 EXPORT const gchar **
plugin_get_sub_names(void)122 plugin_get_sub_names (void)
123 {
124 	if (sub_names)
125 		return (const gchar**) sub_names;
126 
127 	if (! in_forked) {
128 		gboolean forked_ok;
129 		sub_names = try_getting_drivers_list_forked (&forked_ok);
130 		if (forked_ok) {
131 			if (sub_names)
132 				describe_driver_names ();
133 			return (const gchar**) sub_names;
134 		}
135 	}
136 
137 	if (! __CreateJavaVM && !load_jvm ())
138 		return NULL;
139 
140 	GError *error = NULL;
141 	GValue *lvalue;
142 	JNIEnv *env;
143 	jclass cls;
144 
145 	if ((*_jdbc_provider_java_vm)->AttachCurrentThread (_jdbc_provider_java_vm,
146 							    (void**) &env, NULL) < 0) {
147 		g_warning ("Could not attach JAVA virtual machine's current thread");
148 		return NULL;
149 	}
150 
151 	cls = jni_wrapper_class_get (env, "GdaJProvider", &error);
152 	if (!cls) {
153 		g_warning (_("Can't get list of installed JDBC drivers: %s"),
154 			   error && error->message ? error->message : _("No detail"));
155 		if (error)
156 			g_error_free (error);
157 
158 		(*_jdbc_provider_java_vm)->DetachCurrentThread (_jdbc_provider_java_vm);
159 		return NULL;
160 	}
161 	lvalue = jni_wrapper_method_call (env, GdaJProvider__getDrivers, NULL, NULL, NULL, &error);
162 	if (!lvalue) {
163 		g_warning (_("Can't get list of installed JDBC drivers: %s"),
164 			   error && error->message ? error->message : _("No detail"));
165 		if (error)
166 			g_error_free (error);
167 
168 		(*_jdbc_provider_java_vm)->DetachCurrentThread (_jdbc_provider_java_vm);
169 		return NULL;
170 	}
171 	if (!gda_value_is_null (lvalue)) {
172 		sub_names = g_strsplit (g_value_get_string (lvalue), ":", 0);
173 		gda_value_free (lvalue);
174 
175 		describe_driver_names ();
176 
177 		(*_jdbc_provider_java_vm)->DetachCurrentThread (_jdbc_provider_java_vm);
178 		return (const gchar **) sub_names;
179 	}
180 	else {
181 		g_free (lvalue);
182 		(*_jdbc_provider_java_vm)->DetachCurrentThread (_jdbc_provider_java_vm);
183 		return NULL;
184 	}
185 }
186 
187 /*
188  * fills the jdbc_drivers_hash hash table
189  */
190 static void
describe_driver_names(void)191 describe_driver_names (void)
192 {
193 	gint i;
194 
195 	if (jdbc_drivers_hash)
196 		g_hash_table_destroy (jdbc_drivers_hash);
197 	sub_nb = g_strv_length (sub_names);
198 	jdbc_drivers_hash = g_hash_table_new (g_str_hash, g_str_equal);
199 	for (i = 0; i < sub_nb; i++) {
200 		JdbcDriver *dr = g_new0 (JdbcDriver, 1);
201 		dr->name = sub_names [i];
202 		dr->native_db = get_database_name_from_driver_name (sub_names [i]);
203 		if (dr->native_db)
204 			dr->descr = g_strdup_printf ("Provider to access %s databases using JDBC",
205 						     dr->native_db);
206 		else
207 			dr->descr = g_strdup_printf ("Provider to access databases using JDBC's %s driver",
208 						     dr->name);
209 		g_hash_table_insert (jdbc_drivers_hash, (gchar*) dr->name, dr);
210 	}
211 }
212 
213 /*
214  * Tries to load the JVM in a forked child to avoid
215  * keeping the JVM loaded all the time
216  *
217  * if it succedded running a forked child, then @out_forked_ok is set to %TRUE
218  */
219 static gchar **
try_getting_drivers_list_forked(gboolean * out_forked_ok)220 try_getting_drivers_list_forked (gboolean *out_forked_ok)
221 {
222 	g_assert (out_forked_ok);
223 	*out_forked_ok = FALSE;
224 #ifdef G_OS_UNIX
225 	int pipes[2] = { -1, -1 };
226 	pid_t pid;
227 
228 	if (pipe (pipes) < 0)
229 		return NULL;
230 
231 	pid = fork ();
232 	if (pid < 0) {
233 		close (pipes [0]);
234 		close (pipes [1]);
235 		return NULL;
236 	}
237 
238 	if (pid == 0) {
239 		/* in child */
240 		const gchar **retval;
241 		const gchar **ptr;
242 		GString *string = NULL;
243 
244 		close (pipes [0]);
245 		in_forked = TRUE;
246 		retval = plugin_get_sub_names ();
247 		for (ptr = retval; ptr && *ptr; ptr++) {
248 			if (!string)
249 				string = g_string_new ("");
250 			else
251 				g_string_append_c (string, ':');
252 			g_string_append (string, *ptr);
253 		}
254 		if (string) {
255 			write (pipes [1], string->str, strlen (string->str));
256 			g_string_free (string, TRUE);
257 		}
258 		close (pipes [1]);
259 		exit (0);
260 	}
261 	else {
262 		/* in parent */
263 		gchar **retval;
264 		GString *string;
265 		char buf;
266 		close (pipes [1]);
267 		string = g_string_new ("");
268 		while (read(pipes[0], &buf, 1) > 0)
269 			g_string_append_c (string, buf);
270 		close (pipes [0]);
271 		wait (NULL);
272 		/*g_print ("Read from child [%s]\n", string->str);*/
273 		retval = g_strsplit (string->str, ":", -1);
274 		g_string_free (string, TRUE);
275 		*out_forked_ok = TRUE;
276 
277 		return retval;
278 	}
279 #else
280 	/* not supported */
281 	return NULL;
282 #endif
283 }
284 
285 
286 EXPORT const gchar *
plugin_get_sub_description(const gchar * name)287 plugin_get_sub_description (const gchar *name)
288 {
289 	JdbcDriver *dr;
290 	dr = g_hash_table_lookup (jdbc_drivers_hash, name);
291 	if (dr)
292 		return dr->descr;
293 	else
294 		return NULL;
295 }
296 
297 EXPORT gchar *
plugin_get_sub_dsn_spec(const gchar * name)298 plugin_get_sub_dsn_spec (const gchar *name)
299 {
300 	gchar *ret, *dir, *tmp;
301 
302 	dir = gda_gbr_get_file_path (GDA_DATA_DIR, LIBGDA_ABI_NAME, NULL);
303 	tmp = g_strdup_printf ("jdbc_specs_%s_dsn.xml", name);
304 	ret = gda_server_provider_load_file_contents (module_path, dir, tmp);
305 	g_free (tmp);
306 	if (!ret)
307 		ret = gda_server_provider_load_file_contents (module_path, dir, "jdbc_specs_dsn.xml");
308 	g_free (dir);
309 	return ret;
310 }
311 
312 EXPORT GdaServerProvider *
plugin_create_sub_provider(const gchar * name)313 plugin_create_sub_provider (const gchar *name)
314 {
315 	/* server creation */
316 	GdaServerProvider *prov;
317 
318 	if (! __CreateJavaVM && !load_jvm ())
319 		return NULL;
320 	else {
321 		JNIEnv *env;
322 		jclass cls;
323 
324 		if ((*_jdbc_provider_java_vm)->AttachCurrentThread (_jdbc_provider_java_vm,
325 								    (void**) &env, NULL) < 0) {
326 			(*_jdbc_provider_java_vm)->DetachCurrentThread (_jdbc_provider_java_vm);
327 			if (g_getenv ("GDA_SHOW_PROVIDER_LOADING_ERROR"))
328 				g_warning ("Could not attach JAVA virtual machine's current thread");
329 			return NULL;
330 		}
331 
332 		cls = jni_wrapper_class_get (env, "GdaJProvider", NULL);
333 		(*_jdbc_provider_java_vm)->DetachCurrentThread (_jdbc_provider_java_vm);
334 		if (!cls) {
335 			if (g_getenv ("GDA_SHOW_PROVIDER_LOADING_ERROR"))
336 				g_warning ("Could not find the GdaJProvider class");
337 			return NULL;
338 		}
339 	}
340 
341 	prov = gda_jdbc_provider_new (name, NULL);
342 	g_object_set_data ((GObject *) prov, "GDA_PROVIDER_DIR", module_path);
343 	return prov;
344 }
345 
346 #ifdef G_OS_WIN32
347 /* Look up a key's value in the registry. */
348 static gchar *
win32_lookup_registry_key(const gchar * keyname,const gchar * name)349 win32_lookup_registry_key (const gchar *keyname, const gchar *name)
350 {
351         gchar *retval;
352         DWORD size;
353         DWORD type;
354         LONG res;
355         HKEY reg_key = (HKEY) INVALID_HANDLE_VALUE;
356 
357         res = RegOpenKeyExA (HKEY_LOCAL_MACHINE, keyname, 0, KEY_READ, &reg_key);
358         if (res != ERROR_SUCCESS) {
359                 reg_key = (HKEY) INVALID_HANDLE_VALUE;
360                 return NULL;
361         }
362 
363         size = 128;
364         retval = g_malloc (sizeof (gchar) * size);
365 
366         res = RegQueryValueExA (reg_key, name, 0, &type, retval, &size);
367         if (res == ERROR_MORE_DATA && type == REG_SZ) {
368                 retval = (gchar *) g_realloc (retval, sizeof (gchar) * size);
369                 res = RegQueryValueExA (reg_key, name, 0, &type, retval, &size);
370         }
371 
372         if (type != REG_SZ || res != ERROR_SUCCESS) {
373                 g_free (retval);
374                 retval = NULL;
375         }
376 
377         RegCloseKey (reg_key);
378 
379         return retval;
380 }
381 
382 #endif
383 
384 static gboolean find_jvm_in_dir (const gchar *dir_name);
385 static gboolean
load_jvm()386 load_jvm ()
387 {
388 	gboolean jvm_found = FALSE;
389 	const gchar *env;
390 
391 	g_mutex_lock (&vm_create);
392 	if (_jdbc_provider_java_vm) {
393 		g_mutex_unlock (&vm_create);
394 		return TRUE;
395 	}
396 
397 #ifdef G_OS_WIN32
398 	gchar *tmp1;
399 	const gchar *loc = "SOFTWARE\\\JavaSoft\\Java Runtime Environment";
400 	tmp1 = win32_lookup_registry_key (loc, "CurrentVersion");
401 	if (tmp1) {
402 		gchar *kn, *tmp2;
403 		kn = g_strdup_printf ("%s\\%s", loc, tmp1);
404 		g_free (tmp1);
405 		tmp2 = win32_lookup_registry_key (kn, "JavaHome");
406 		if (tmp2) {
407 			gchar *tmp3;
408 			tmp3 = g_build_filename (tmp2, "bin", "client", NULL);
409 			g_free (tmp2);
410 			if (find_jvm_in_dir (tmp3))
411 				jvm_found = TRUE;
412 		}
413 	}
414 	if (!jvm_found) {
415 		loc = "SOFTWARE\\\JavaSoft\\Java Development Kit";
416 		tmp1 = win32_lookup_registry_key (loc, "CurrentVersion");
417 		if (tmp1) {
418 			gchar *kn, *tmp2;
419 			kn = g_strdup_printf ("%s\\%s", loc, tmp1);
420 			g_free (tmp1);
421 			tmp2 = win32_lookup_registry_key (kn, "JavaHome");
422 			if (tmp2) {
423 				gchar *tmp3;
424 				tmp3 = g_build_filename (tmp2, "jre", "bin", "client", NULL);
425 				g_free (tmp2);
426 				if (find_jvm_in_dir (tmp3))
427 					jvm_found = TRUE;
428 			}
429 		}
430 	}
431 
432 #else
433 	/* first, use LD_LIBRARY_PATH */
434 	env = g_getenv ("LD_LIBRARY_PATH");
435 	if (env) {
436 		gchar **array;
437 		gint i;
438 		array = g_strsplit (env, ":", 0);
439 		for (i = 0; array[i]; i++) {
440 			if (find_jvm_in_dir (array [i])) {
441 				jvm_found = TRUE;
442 				break;
443 			}
444 		}
445 		g_strfreev (array);
446 	}
447 	if (jvm_found)
448 		goto out;
449 
450 	/* then use the compile time JVM_PATH */
451 	if (JVM_PATH) {
452 		gchar **array;
453 		gint i;
454 		array = g_strsplit (JVM_PATH, ":", 0);
455 		for (i = 0; array[i]; i++) {
456 			if (find_jvm_in_dir (array [i])) {
457 				jvm_found = TRUE;
458 				break;
459 			}
460 		}
461 		g_strfreev (array);
462 	}
463 #endif
464 	if (jvm_found)
465 		goto out;
466 
467 	/* at last use module_path */
468 	if (find_jvm_in_dir (module_path))
469 		jvm_found = TRUE;
470 
471  out:
472 	if (jvm_found) {
473 		gchar *path;
474 		GError *error = NULL;
475 		path = g_build_filename (module_path, "gdaprovider-5.0.jar", NULL);
476 		jni_wrapper_create_vm (&_jdbc_provider_java_vm, __CreateJavaVM, module_path, path, &error);
477 		if (!_jdbc_provider_java_vm) {
478 			if (g_getenv ("GDA_SHOW_PROVIDER_LOADING_ERROR"))
479 				g_warning (_("Can't create JAVA virtual machine: %s"),
480 					   error && error->message ? error->message : _("No detail"));
481 			jvm_found = FALSE;
482 		}
483 	}
484 	else {
485 		__CreateJavaVM = NULL;
486 		if (g_getenv ("GDA_SHOW_PROVIDER_LOADING_ERROR"))
487 			g_warning (_("Could not find the JVM runtime (libjvm.so), JDBC provider is unavailable."));
488 	}
489 
490 	g_mutex_unlock (&vm_create);
491 	return jvm_found;
492 }
493 
494 static gboolean
find_jvm_in_dir(const gchar * dir_name)495 find_jvm_in_dir (const gchar *dir_name)
496 {
497 	GDir *dir;
498 	GError *err = NULL;
499 	const gchar *name;
500 
501 	if (jvm_handle) {
502 		g_module_close (jvm_handle);
503 		jvm_handle = NULL;
504 	}
505 
506 	/* read the plugin directory */
507 #ifdef GDA_DEBUG_NO
508 	g_print ("Looking for JVM in %s\n", dir_name);
509 #endif
510 	dir = g_dir_open (dir_name, 0, &err);
511 	if (err) {
512 		gda_log_error (err->message);
513 		g_error_free (err);
514 		return FALSE;
515 	}
516 
517 	while ((name = g_dir_read_name (dir))) {
518 		if (!g_str_has_suffix (name, "." G_MODULE_SUFFIX))
519 			continue;
520 		if (!g_strrstr (name, "jvm"))
521 			continue;
522 
523 		gchar *path;
524 
525 		path = g_build_path (G_DIR_SEPARATOR_S, dir_name, name, NULL);
526 		jvm_handle = g_module_open (path, G_MODULE_BIND_LAZY);
527 		g_free (path);
528 		if (!jvm_handle) {
529 			/*g_warning (_("Error: %s"), g_module_error ());*/
530 			continue;
531 		}
532 
533 		if (g_module_symbol (jvm_handle, "JNI_CreateJavaVM", (gpointer *) &__CreateJavaVM)) {
534 			/* JVM found */
535 #ifdef GDA_DEBUG_NO
536 			path = g_build_path (G_DIR_SEPARATOR_S, dir_name, name, NULL);
537 			g_print ("JVM found as: '%s'\n", path);
538 			g_free (path);
539 #endif
540 			break;
541 		}
542 		else {
543 			g_module_close (jvm_handle);
544 			jvm_handle = NULL;
545 		}
546 
547 	}
548 	/* free memory */
549 	g_dir_close (dir);
550 	return jvm_handle ? TRUE : FALSE;
551 }
552 
553 static const gchar *
get_database_name_from_driver_name(const gchar * driver_name)554 get_database_name_from_driver_name (const gchar *driver_name)
555 {
556 	typedef struct {
557 		gchar *jdbc_name;
558 		gchar *db_name;
559 	} Corresp;
560 
561 	static Corresp carray[] = {
562 		{"COM.cloudscape.core.JDBCDriver", "Cloudscape"},
563 		{"RmiJdbc.RJDriver", "Cloudscape"},
564 		{"COM.ibm.db2.jdbc.app.DB2Driver", "DB2"},
565 		{"org.firebirdsql.jdbc.FBDriver", "Firebird"},
566 		{"hSql.hDriver", "Hypersonic SQL"},
567 		{"org.hsql.jdbcDriver", "Hypersonic SQL"},
568 		{"com.informix.jdbc.IfxDriver", "Informix"},
569 		{"jdbc.idbDriver", "InstantDB"},
570 		{"org.enhydra.instantdb.jdbc.idbDriver", "InstantDB"},
571 		{"interbase.interclient.Driver", "Interbase"},
572 		{"ids.sql.IDSDriver", "IDS Server"},
573 		{"com.mysql.jdbc.Driver", "MySQL"},
574 		{"org.gjt.mm.mysql.Driver", "MySQL"},
575 		{"oracle.jdbc.driver.OracleDriver", "Oracle"},
576 		{"oracle.jdbc.OracleDriver", "Oracle"},
577 		{"com.pointbase.jdbc.jdbcUniversalDriver", "PointBase"},
578 		{"org.postgresql.Driver", "PostgreSQL"},
579 		{"postgresql.Driver", "v6.5 and earlier"},
580 		{"com.microsoft.sqlserver.jdbc.SQLServerDriver", "SqlServer"},
581 		{"weblogic.jdbc.mssqlserver4.Driver", "SqlServer"},
582 		{"com.ashna.jturbo.driver.Driver", "SqlServer"},
583 		{"com.inet.tds.TdsDriver", "SqlServer"},
584 		{"com.sybase.jdbc.SybDriver", "Sybase"},
585 		{"com.sybase.jdbc2.jdbc.SybDriver", "Sybase"}
586 	};
587 
588 	gsize i;
589 	for (i = 0; i < sizeof (carray) / sizeof (Corresp); i++) {
590 		if (!strcmp (carray[i].jdbc_name, driver_name))
591 			return carray[i].db_name;
592 	}
593 
594 	return NULL;
595 }
596