1 /************ Javaconn C++ Functions Source Code File (.CPP) ***********/
2 /*  Name: JAVAConn.CPP  Version 1.1                                    */
3 /*                                                                     */
4 /*  (C) Copyright to the author Olivier BERTRAND          2017 - 2021  */
5 /*                                                                     */
6 /*  This file contains the JAVA connection classes functions.          */
7 /***********************************************************************/
8 
9 #if defined(_WIN32)
10 // This is needed for RegGetValue
11 #define _WINVER 0x0601
12 #undef  _WIN32_WINNT
13 #define _WIN32_WINNT 0x0601
14 #endif   // _WIN32
15 
16 /***********************************************************************/
17 /*  Include relevant MariaDB header file.                              */
18 /***********************************************************************/
19 #include <my_global.h>
20 //#include <m_string.h>
21 #if defined(_WIN32)
22 #include <direct.h>                      // for getcwd
23 #if defined(__BORLANDC__)
24 #define __MFC_COMPAT__                   // To define min/max as macro
25 #endif   // __BORLANDC__
26 #else   // !_WIN32
27 #if defined(UNIX)
28 #include <errno.h>
29 #else   // !UNIX
30 #endif  // !UNIX
31 #include <stdio.h>
32 #include <stdlib.h>                      // for getenv
33 #define NODW
34 #endif  // !_WIN32
35 
36 /***********************************************************************/
37 /*  Required objects includes.                                         */
38 /***********************************************************************/
39 #include "global.h"
40 #include "plgdbsem.h"
41 #include "colblk.h"
42 #include "xobject.h"
43 #include "xtable.h"
44 #include "tabext.h"
45 #include "javaconn.h"
46 #include "resource.h"
47 #include "valblk.h"
48 #include "osutil.h"
49 
50 #if defined(_WIN32)
51 extern "C" HINSTANCE s_hModule;           // Saved module handle
52 #endif   // _WIN32
53 #define nullptr 0
54 
55 //TYPCONV GetTypeConv();
56 //int GetConvSize();
57 extern char *JvmPath;   // The connect_jvm_path global variable value
58 extern char *ClassPath; // The connect_class_path global variable value
59 
60 char *GetPluginDir(void);
61 char *GetMessageDir(void);
62 char *GetJavaWrapper(void);		// The connect_java_wrapper variable value
63 extern MYSQL_PLUGIN_IMPORT char lc_messages_dir[FN_REFLEN];
64 
65 /***********************************************************************/
66 /*  Static JAVAConn objects.                                           */
67 /***********************************************************************/
68 void  *JAVAConn::LibJvm = NULL;
69 CRTJVM JAVAConn::CreateJavaVM = NULL;
70 GETJVM JAVAConn::GetCreatedJavaVMs = NULL;
71 #if defined(_DEBUG)
72 GETDEF JAVAConn::GetDefaultJavaVMInitArgs = NULL;
73 #endif   // _DEBUG
74 
75 /***********************************************************************/
76 /*  Some macro's (should be defined elsewhere to be more accessible)   */
77 /***********************************************************************/
78 #if defined(_DEBUG)
79 #define ASSERT(f)          assert(f)
80 #define DEBUG_ONLY(f)      (f)
81 #else   // !_DEBUG
82 #define ASSERT(f)          ((void)0)
83 #define DEBUG_ONLY(f)      ((void)0)
84 #endif  // !_DEBUG
85 
86 /***********************************************************************/
87 /*  JAVAConn construction/destruction.                                 */
88 /***********************************************************************/
JAVAConn(PGLOBAL g,PCSZ wrapper)89 JAVAConn::JAVAConn(PGLOBAL g, PCSZ wrapper)
90 {
91 	m_G = g;
92 	jvm = nullptr;            // Pointer to the JVM (Java Virtual Machine)
93 	env = nullptr;            // Pointer to native interface
94 	jdi = nullptr;						// Pointer to the java wrapper class
95 	job = nullptr;						// The java wrapper class object
96 	errid = nullptr;
97 	DiscFunc = "Disconnect";
98 	Msg = NULL;
99 	m_Wrap = (wrapper) ? wrapper : GetJavaWrapper();
100 
101 	if (!strchr(m_Wrap, '/')) {
102 		// Add the wrapper package name
103 		char *wn = (char*)PlugSubAlloc(g, NULL, strlen(m_Wrap) + 10);
104 		m_Wrap = strcat(strcpy(wn, "wrappers/"), m_Wrap);
105 	} // endif m_Wrap
106 
107 	fp = NULL;
108 	m_Opened = false;
109 	m_Connected = false;
110 	m_Rows = 0;
111 //*m_ErrMsg = '\0';
112 } // end of JAVAConn
113 
114 //JAVAConn::~JAVAConn()
115 //  {
116 //if (Connected())
117 //  EndCom();
118 
119 //  } // end of ~JAVAConn
GetUTFString(jstring s)120 char *JAVAConn::GetUTFString(jstring s)
121 {
122 	char *str;
123 	const char *utf = env->GetStringUTFChars(s, nullptr);
124 
125 	str = PlugDup(m_G, utf);
126 	env->ReleaseStringUTFChars(s, utf);
127 	env->DeleteLocalRef(s);
128 	return str;
129 }	// end of GetUTFString
130 
131 /***********************************************************************/
132 /*  Screen for errors.                                                 */
133 /***********************************************************************/
Check(jint rc)134 bool JAVAConn::Check(jint rc)
135 {
136 	jstring s;
137 
138 	if (env->ExceptionCheck()) {
139 		jthrowable exc = env->ExceptionOccurred();
140 		jmethodID tid = env->GetMethodID(env->FindClass("java/lang/Object"),
141 			"toString", "()Ljava/lang/String;");
142 
143 		if (exc != nullptr && tid != nullptr) {
144 			s = (jstring)env->CallObjectMethod(exc, tid);
145 			Msg = GetUTFString(s);
146 		} else
147 			Msg = "Exception occurred";
148 
149 		env->ExceptionClear();
150 	} else if (rc < 0) {
151 		s = (jstring)env->CallObjectMethod(job, errid);
152 		Msg = GetUTFString(s);
153 	} else
154 		Msg = NULL;
155 
156 	return (Msg != NULL);
157 } // end of Check
158 
159 /***********************************************************************/
160 /*  Get MethodID if not exists yet.                                    */
161 /***********************************************************************/
gmID(PGLOBAL g,jmethodID & mid,const char * name,const char * sig)162 bool JAVAConn::gmID(PGLOBAL g, jmethodID& mid, const char *name, const char *sig)
163 {
164 	if (mid == nullptr) {
165 		mid = env->GetMethodID(jdi, name, sig);
166 
167 		if (Check()) {
168 			strcpy(g->Message, Msg);
169 			return true;
170 		} else
171 			return false;
172 
173 	} else
174 		return false;
175 
176 } // end of gmID
177 
178 #if 0
179 /***********************************************************************/
180 /*  Utility routine.                                                   */
181 /***********************************************************************/
182 int JAVAConn::GetMaxValue(int n)
183 {
184 	jint      m;
185 	jmethodID maxid = nullptr;
186 
187 	if (gmID(m_G, maxid, "GetMaxValue", "(I)I"))
188 		return -1;
189 
190 	// call method
191 	if (Check(m = env->CallIntMethod(job, maxid, n)))
192 		htrc("GetMaxValue: %s", Msg);
193 
194 	return (int)m;
195 } // end of GetMaxValue
196 #endif // 0
197 
198 /***********************************************************************/
199 /*  Reset the JVM library.                                             */
200 /***********************************************************************/
ResetJVM(void)201 void JAVAConn::ResetJVM(void)
202 {
203 	if (LibJvm) {
204 #if defined(_WIN32)
205 		FreeLibrary((HMODULE)LibJvm);
206 #else   // !_WIN32
207 		dlclose(LibJvm);
208 #endif  // !_WIN32
209 		LibJvm = NULL;
210 		CreateJavaVM = NULL;
211 		GetCreatedJavaVMs = NULL;
212 #if defined(_DEBUG)
213 		GetDefaultJavaVMInitArgs = NULL;
214 #endif   // _DEBUG
215 	} // endif LibJvm
216 
217 } // end of ResetJVM
218 
219 /***********************************************************************/
220 /*  Dynamically link the JVM library.                                  */
221 /*  The purpose of this function is to allow using the CONNECT plugin  */
222 /*  for other table types when the Java JDK is not installed.          */
223 /***********************************************************************/
GetJVM(PGLOBAL g)224 bool JAVAConn::GetJVM(PGLOBAL g)
225 {
226 	int ntry;
227 
228 	if (!LibJvm) {
229 		char soname[512];
230 
231 #if defined(_WIN32)
232 		for (ntry = 0; !LibJvm && ntry < 3; ntry++) {
233 			if (!ntry && JvmPath) {
234 				strcat(strcpy(soname, JvmPath), "\\jvm.dll");
235 				ntry = 3;		 // No other try
236 			} else if (ntry < 2 && getenv("JAVA_HOME")) {
237 				strcpy(soname, getenv("JAVA_HOME"));
238 
239 				if (ntry == 1)
240 					strcat(soname, "\\jre");
241 
242 				strcat(soname, "\\bin\\client\\jvm.dll");
243 			} else {
244 				// Try to find it through the registry
245 				char version[16];
246 				char javaKey[64] = "SOFTWARE\\JavaSoft\\Java Runtime Environment";
247 				LONG  rc;
248 				DWORD BufferSize = 16;
249 
250 				strcpy(soname, "jvm.dll");		// In case it fails
251 
252 				if ((rc = RegGetValue(HKEY_LOCAL_MACHINE, javaKey, "CurrentVersion",
253 					RRF_RT_ANY, NULL, (PVOID)&version, &BufferSize)) == ERROR_SUCCESS) {
254 					strcat(strcat(javaKey, "\\"), version);
255 					BufferSize = sizeof(soname);
256 
257 					if ((rc = RegGetValue(HKEY_LOCAL_MACHINE, javaKey, "RuntimeLib",
258 						RRF_RT_ANY, NULL, (PVOID)&soname, &BufferSize)) != ERROR_SUCCESS)
259 						printf("RegGetValue: rc=%ld\n", rc);
260 
261 				} // endif rc
262 
263 				ntry = 3;		 // Try this only once
264 			} // endelse
265 
266 			// Load the desired shared library
267 			LibJvm = LoadLibrary(soname);
268 		}	// endfor ntry
269 
270 		// Get the needed entries
271 		if (!LibJvm) {
272 			char  buf[256];
273 			DWORD rc = GetLastError();
274 
275 			sprintf(g->Message, MSG(DLL_LOAD_ERROR), rc, soname);
276 			FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
277 										FORMAT_MESSAGE_IGNORE_INSERTS, NULL, rc, 0,
278 				            (LPTSTR)buf, sizeof(buf), NULL);
279 			strcat(strcat(g->Message, ": "), buf);
280 		} else if (!(CreateJavaVM = (CRTJVM)GetProcAddress((HINSTANCE)LibJvm,
281 			                                       "JNI_CreateJavaVM"))) {
282 			sprintf(g->Message, MSG(PROCADD_ERROR), GetLastError(), "JNI_CreateJavaVM");
283 			FreeLibrary((HMODULE)LibJvm);
284 			LibJvm = NULL;
285 		} else if (!(GetCreatedJavaVMs = (GETJVM)GetProcAddress((HINSTANCE)LibJvm,
286 			                                       "JNI_GetCreatedJavaVMs"))) {
287 			sprintf(g->Message, MSG(PROCADD_ERROR), GetLastError(), "JNI_GetCreatedJavaVMs");
288 			FreeLibrary((HMODULE)LibJvm);
289 			LibJvm = NULL;
290 #if defined(_DEBUG)
291 		} else if (!(GetDefaultJavaVMInitArgs = (GETDEF)GetProcAddress((HINSTANCE)LibJvm,
292 			                                       "JNI_GetDefaultJavaVMInitArgs"))) {
293 			sprintf(g->Message, MSG(PROCADD_ERROR), GetLastError(),
294 				"JNI_GetDefaultJavaVMInitArgs");
295 			FreeLibrary((HMODULE)LibJvm);
296 			LibJvm = NULL;
297 #endif   // _DEBUG
298 		} // endif LibJvm
299 #else   // !_WIN32
300 		const char *error = NULL;
301 
302 		for (ntry = 0; !LibJvm && ntry < 2; ntry++) {
303 			if (!ntry && JvmPath) {
304 				strcat(strcpy(soname, JvmPath), "/libjvm.so");
305 				ntry = 2;
306 			} else if (!ntry && getenv("JAVA_HOME")) {
307 				// TODO: Replace i386 by a better guess
308 				strcat(strcpy(soname, getenv("JAVA_HOME")), "/jre/lib/i386/client/libjvm.so");
309 			} else {	 // Will need LD_LIBRARY_PATH to be set
310 				strcpy(soname, "libjvm.so");
311 				ntry = 2;
312 			} // endelse
313 
314 			LibJvm = dlopen(soname, RTLD_LAZY);
315 		} // endfor ntry
316 
317 			// Load the desired shared library
318 		if (!LibJvm) {
319 			error = dlerror();
320 			sprintf(g->Message, MSG(SHARED_LIB_ERR), soname, SVP(error));
321 		} else if (!(CreateJavaVM = (CRTJVM)dlsym(LibJvm, "JNI_CreateJavaVM"))) {
322 			error = dlerror();
323 			sprintf(g->Message, MSG(GET_FUNC_ERR), "JNI_CreateJavaVM", SVP(error));
324 			dlclose(LibJvm);
325 			LibJvm = NULL;
326 		} else if (!(GetCreatedJavaVMs = (GETJVM)dlsym(LibJvm, "JNI_GetCreatedJavaVMs"))) {
327 			error = dlerror();
328 			sprintf(g->Message, MSG(GET_FUNC_ERR), "JNI_GetCreatedJavaVMs", SVP(error));
329 			dlclose(LibJvm);
330 			LibJvm = NULL;
331 #if defined(_DEBUG)
332 		} else if (!(GetDefaultJavaVMInitArgs = (GETDEF)dlsym(LibJvm,
333 			"JNI_GetDefaultJavaVMInitArgs"))) {
334 			error = dlerror();
335 			sprintf(g->Message, MSG(GET_FUNC_ERR), "JNI_GetDefaultJavaVMInitArgs", SVP(error));
336 			dlclose(LibJvm);
337 			LibJvm = NULL;
338 #endif   // _DEBUG
339 		} // endif LibJvm
340 #endif  // !_WIN32
341 
342 	} // endif LibJvm
343 
344 	return LibJvm == NULL;
345 } // end of GetJVM
346 
347 /***********************************************************************/
348 /*  Open: connect to a data source.                                    */
349 /***********************************************************************/
Open(PGLOBAL g)350 bool JAVAConn::Open(PGLOBAL g)
351 {
352 	bool		 brc = true, err = false;
353 	jboolean jt = (trace(1));
354 
355 	// Link or check whether jvm library was linked
356 	if (GetJVM(g))
357 		return true;
358 
359 	// Firstly check whether the jvm was already created
360 	JavaVM* jvms[1];
361 	jsize   jsz;
362 	jint    rc = GetCreatedJavaVMs(jvms, 1, &jsz);
363 
364 	if (rc == JNI_OK && jsz == 1) {
365 		// jvm already existing
366 		jvm = jvms[0];
367 		rc = jvm->AttachCurrentThread((void**)&env, nullptr);
368 
369 		if (rc != JNI_OK) {
370 			strcpy(g->Message, "Cannot attach jvm to the current thread");
371 			return true;
372 		} // endif rc
373 
374 	} else {
375 		/*******************************************************************/
376 		/*  Create a new jvm																							 */
377 		/*******************************************************************/
378 		PSTRG    jpop = new(g)STRING(g, 512, "-Djava.class.path=.");
379 		char    *cp = NULL;
380 		char     sep;
381 
382 #if defined(_WIN32)
383 		sep = ';';
384 #define N 1
385 		//#define N 2
386 		//#define N 3
387 #else
388 		sep = ':';
389 #define N 1
390 #endif
391 
392 		// Add wrappers jar files
393 		AddJars(jpop, sep);
394 
395 		//================== prepare loading of Java VM ============================
396 		JavaVMInitArgs vm_args;                        // Initialization arguments
397 		JavaVMOption* options = new JavaVMOption[N];   // JVM invocation options
398 
399 		// where to find java .class
400 		if (ClassPath && *ClassPath) {
401 			jpop->Append(sep);
402 			jpop->Append(ClassPath);
403 		}	// endif ClassPath
404 
405 		// All wrappers are pre-compiled in JavaWrappers.jar in the share dir
406 		jpop->Append(sep);
407 		jpop->Append(GetMessageDir());
408 		jpop->Append("JavaWrappers.jar");
409 
410 #if defined(MONGO_SUPPORT)
411 		jpop->Append(sep);
412 		jpop->Append(GetMessageDir());
413 		jpop->Append("Mongo3.jar");
414 		jpop->Append(sep);
415 		jpop->Append(GetMessageDir());
416 		jpop->Append("Mongo2.jar");
417 #endif   // MONGO_SUPPORT
418 
419 		if ((cp = getenv("CLASSPATH"))) {
420 			jpop->Append(sep);
421 			jpop->Append(cp);
422 		} // endif cp
423 
424 		if (trace(1)) {
425 			htrc("ClassPath=%s\n", ClassPath ? ClassPath : "null");
426 			htrc("CLASSPATH=%s\n", cp ? cp : "null");
427 			htrc("%s\n", jpop->GetStr());
428 		} // endif trace
429 
430 		options[0].optionString = jpop->GetStr();
431 #if N == 2
432 		options[1].optionString = "-Xcheck:jni";
433 #endif
434 #if N == 3
435 		options[1].optionString = "-Xms256M";
436 		options[2].optionString = "-Xmx512M";
437 #endif
438 #if defined(_DEBUG)
439 		vm_args.version = JNI_VERSION_1_2;             // minimum Java version
440 		rc = GetDefaultJavaVMInitArgs(&vm_args);
441 #else
442 		vm_args.version = JNI_VERSION_1_6;             // minimum Java version
443 #endif   // _DEBUG
444 		vm_args.nOptions = N;                          // number of options
445 		vm_args.options = options;
446 		vm_args.ignoreUnrecognized = false; // invalid options make the JVM init fail
447 
448 		//=============== load and initialize Java VM and JNI interface =============
449 		rc = CreateJavaVM(&jvm, (void**)&env, &vm_args);  // YES !!
450 		delete[] options;    // we then no longer need the initialisation options.
451 
452 		switch (rc) {
453 			case JNI_OK:
454 				strcpy(g->Message, "VM successfully created");
455 				brc = false;
456 				break;
457 			case JNI_ERR:
458 				strcpy(g->Message, "Initialising JVM failed: unknown error");
459 				break;
460 			case JNI_EDETACHED:
461 				strcpy(g->Message, "Thread detached from the VM");
462 				break;
463 			case JNI_EVERSION:
464 				strcpy(g->Message, "JNI version error");
465 				break;
466 			case JNI_ENOMEM:
467 				strcpy(g->Message, "Not enough memory");
468 				break;
469 			case JNI_EEXIST:
470 				strcpy(g->Message, "VM already created");
471 				break;
472 			case JNI_EINVAL:
473 				strcpy(g->Message, "Invalid arguments");
474 				break;
475 			default:
476 				sprintf(g->Message, "Unknown return code %d", (int)rc);
477 				break;
478 		} // endswitch rc
479 
480 		if (trace(1))
481 			htrc("%s\n", g->Message);
482 
483 		if (brc)
484 			return true;
485 
486 		//=============== Display JVM version ===============
487 		jint ver = env->GetVersion();
488 		printf("JVM Version %d.%d\n", ((ver >> 16) & 0x0f), (ver & 0x0f));
489 	} // endif rc
490 
491 	// try to find the java wrapper class
492 	jdi = env->FindClass(m_Wrap);
493 
494 	if (jdi == nullptr) {
495 		sprintf(g->Message, "ERROR: class %s not found!", m_Wrap);
496 		return true;
497 	} // endif jdi
498 
499 #if 0		// Suppressed because it does not make any usable change
500 	if (b && jpath && *jpath) {
501 		// Try to add that path the the jvm class path
502 		jmethodID alp = env->GetStaticMethodID(jdi, "addLibraryPath",
503 			"(Ljava/lang/String;)I");
504 
505 		if (alp == nullptr) {
506 			env->ExceptionDescribe();
507 			env->ExceptionClear();
508 		} else {
509 			char *msg;
510 			jstring path = env->NewStringUTF(jpath);
511 			rc = env->CallStaticIntMethod(jdi, alp, path);
512 
513 			if ((msg = Check(rc))) {
514 				strcpy(g->Message, msg);
515 				env->DeleteLocalRef(path);
516 				return RC_FX;
517 			} else switch (rc) {
518 				case JNI_OK:
519 					printf("jpath added\n");
520 					break;
521 				case JNI_EEXIST:
522 					printf("jpath already exist\n");
523 					break;
524 				case JNI_ERR:
525 				default:
526 					strcpy(g->Message, "Error adding jpath");
527 					env->DeleteLocalRef(path);
528 					return RC_FX;
529 			}	// endswitch rc
530 
531 			env->DeleteLocalRef(path);
532 		}	// endif alp
533 
534 	}	// endif jpath
535 #endif // 0
536 
537 	// if class found, continue
538 	jmethodID ctor = env->GetMethodID(jdi, "<init>", "(Z)V");
539 
540 	if (ctor == nullptr) {
541 		sprintf(g->Message, "ERROR: %s constructor not found!", m_Wrap);
542 		return true;
543 	} else
544 		job = env->NewObject(jdi, ctor, jt);
545 
546 	if (job == nullptr) {
547 		sprintf(g->Message, "%s class object not constructed!", m_Wrap);
548 		return true;
549 	} // endif job
550 
551 	// If the object is successfully constructed,
552 	// we can then search for the method we want to call,
553 	// and invoke it for the object:
554 	errid = env->GetMethodID(jdi, "GetErrmsg", "()Ljava/lang/String;");
555 
556 	if (env->ExceptionCheck()) {
557 		strcpy(g->Message, "ERROR: method GetErrmsg() not found!");
558 		env->ExceptionDescribe();
559 		env->ExceptionClear();
560 		return true;
561 	} // endif Check
562 
563 	/*********************************************************************/
564 	/*  Link a Fblock. This make possible to automatically close it      */
565 	/*  in case of error (throw).                                        */
566 	/*********************************************************************/
567 	PDBUSER dbuserp = (PDBUSER)g->Activityp->Aptr;
568 
569 	fp = (PFBLOCK)PlugSubAlloc(g, NULL, sizeof(FBLOCK));
570 	fp->Type = TYPE_FB_JAVA;
571 	fp->Fname = NULL;
572 	fp->Next = dbuserp->Openlist;
573 	dbuserp->Openlist = fp;
574 	fp->Count = 1;
575 	fp->Length = 0;
576 	fp->Memory = NULL;
577 	fp->Mode = MODE_ANY;
578 	fp->File = this;
579 	fp->Handle = 0;
580 
581 	m_Opened = true;
582 	return false;
583 } // end of Open
584 
585 /***********************************************************************/
586 /*  Disconnect connection                                              */
587 /***********************************************************************/
Close()588 void JAVAConn::Close()
589 {
590 	jint rc;
591 
592 	if (m_Connected) {
593 		jmethodID did = nullptr;
594 
595 		// Could have been detached in case of join
596 		rc = jvm->AttachCurrentThread((void**)&env, nullptr);
597 
598 		if (gmID(m_G, did, DiscFunc, "()I"))
599 			printf("%s\n", Msg);
600 		else if (Check(env->CallIntMethod(job, did)))
601 			printf("%s: %s\n", DiscFunc, Msg);
602 
603 		m_Connected = false;
604 	}	// endif m_Connected
605 
606 	if ((rc = jvm->DetachCurrentThread()) != JNI_OK)
607 		printf("DetachCurrentThread: rc=%d\n", (int)rc);
608 
609 	if (fp)
610 		fp->Count = 0;
611 
612 	m_Opened = false;
613 } // end of Close
614