1/*
2 * Copyright (c) 2003, 2019, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26/*
27 * Copyright 2003 Wily Technology, Inc.
28 */
29
30#include <string.h>
31#include <stdlib.h>
32
33#include "jni.h"
34
35#include "Utilities.h"
36#include "JPLISAssert.h"
37#include "JPLISAgent.h"
38#include "JavaExceptions.h"
39
40#include "EncodingSupport.h"
41#include "FileSystemSupport.h"
42#include "JarFacade.h"
43#include "PathCharsValidator.h"
44
45/**
46 * This module contains the direct interface points with the JVMTI.
47 * The OnLoad handler is here, along with the various event handlers.
48 */
49
50static int
51appendClassPath(JPLISAgent* agent,
52 const char* jarfile);
53
54static void
55appendBootClassPath(JPLISAgent* agent,
56 const char* jarfile,
57 const char* pathList);
58
59
60/*
61 * Parse -javaagent tail, of the form name[=options], into name
62 * and options. Returned values are heap allocated and options maybe
63 * NULL. Returns 0 if parse succeeds, -1 if allocation fails.
64 */
65static int
66parseArgumentTail(char* tail, char** name, char** options) {
67 int len;
68 char* pos;
69
70 pos = strchr(tail, '=');
71 len = (pos == NULL) ? (int)strlen(tail) : (int)(pos - tail);
72
73 *name = (char*)malloc(len+1);
74 if (*name == NULL) {
75 return -1;
76 }
77 memcpy(*name, tail, len);
78 (*name)[len] = '\0';
79
80 if (pos == NULL) {
81 *options = NULL;
82 } else {
83 char * str = (char*)malloc( (int)strlen(pos + 1) + 1 );
84 if (str == NULL) {
85 free(*name);
86 return -1;
87 }
88 strcpy(str, pos +1);
89 *options = str;
90 }
91 return 0;
92}
93
94/*
95 * Get the value of an attribute in an attribute list. Returns NULL
96 * if attribute not found.
97 */
98jboolean
99getBooleanAttribute(const jarAttribute* attributes, const char* name) {
100 char* attributeValue = getAttribute(attributes, name);
101 return attributeValue != NULL && strcasecmp(attributeValue, "true") == 0;
102}
103
104/*
105 * Parse any capability settings in the JAR manifest and
106 * convert them to JVM TI capabilities.
107 */
108void
109convertCapabilityAttributes(const jarAttribute* attributes, JPLISAgent* agent) {
110 /* set redefineClasses capability */
111 if (getBooleanAttribute(attributes, "Can-Redefine-Classes")) {
112 addRedefineClassesCapability(agent);
113 }
114
115 /* create an environment which has the retransformClasses capability */
116 if (getBooleanAttribute(attributes, "Can-Retransform-Classes")) {
117 retransformableEnvironment(agent);
118 }
119
120 /* set setNativeMethodPrefix capability */
121 if (getBooleanAttribute(attributes, "Can-Set-Native-Method-Prefix")) {
122 addNativeMethodPrefixCapability(agent);
123 }
124
125 /* for retransformClasses testing, set capability to use original method order */
126 if (getBooleanAttribute(attributes, "Can-Maintain-Original-Method-Order")) {
127 addOriginalMethodOrderCapability(agent);
128 }
129}
130
131/*
132 * This will be called once for every -javaagent on the command line.
133 * Each call to Agent_OnLoad will create its own agent and agent data.
134 *
135 * The argument tail string provided to Agent_OnLoad will be of form
136 * <jarfile>[=<options>]. The tail string is split into the jarfile and
137 * options components. The jarfile manifest is parsed and the value of the
138 * Premain-Class attribute will become the agent's premain class. The jar
139 * file is then added to the system class path, and if the Boot-Class-Path
140 * attribute is present then all relative URLs in the value are processed
141 * to create boot class path segments to append to the boot class path.
142 */
143JNIEXPORT jint JNICALL
144DEF_Agent_OnLoad(JavaVM *vm, char *tail, void * reserved) {
145 JPLISInitializationError initerror = JPLIS_INIT_ERROR_NONE;
146 jint result = JNI_OK;
147 JPLISAgent * agent = NULL;
148
149 initerror = createNewJPLISAgent(vm, &agent);
150 if ( initerror == JPLIS_INIT_ERROR_NONE ) {
151 int oldLen, newLen;
152 char * jarfile;
153 char * options;
154 jarAttribute* attributes;
155 char * premainClass;
156 char * bootClassPath;
157
158 /*
159 * Parse <jarfile>[=options] into jarfile and options
160 */
161 if (parseArgumentTail(tail, &jarfile, &options) != 0) {
162 fprintf(stderr, "-javaagent: memory allocation failure.\n");
163 return JNI_ERR;
164 }
165
166 /*
167 * Agent_OnLoad is specified to provide the agent options
168 * argument tail in modified UTF8. However for 1.5.0 this is
169 * actually in the platform encoding - see 5049313.
170 *
171 * Open zip/jar file and parse archive. If can't be opened or
172 * not a zip file return error. Also if Premain-Class attribute
173 * isn't present we return an error.
174 */
175 attributes = readAttributes(jarfile);
176 if (attributes == NULL) {
177 fprintf(stderr, "Error opening zip file or JAR manifest missing : %s\n", jarfile);
178 free(jarfile);
179 if (options != NULL) free(options);
180 return JNI_ERR;
181 }
182
183 premainClass = getAttribute(attributes, "Premain-Class");
184 if (premainClass == NULL) {
185 fprintf(stderr, "Failed to find Premain-Class manifest attribute in %s\n",
186 jarfile);
187 free(jarfile);
188 if (options != NULL) free(options);
189 freeAttributes(attributes);
190 return JNI_ERR;
191 }
192
193 /* Save the jarfile name */
194 agent->mJarfile = jarfile;
195
196 /*
197 * The value of the Premain-Class attribute becomes the agent
198 * class name. The manifest is in UTF8 so need to convert to
199 * modified UTF8 (see JNI spec).
200 */
201 oldLen = (int)strlen(premainClass);
202 newLen = modifiedUtf8LengthOfUtf8(premainClass, oldLen);
203 if (newLen == oldLen) {
204 premainClass = strdup(premainClass);
205 } else {
206 char* str = (char*)malloc( newLen+1 );
207 if (str != NULL) {
208 convertUtf8ToModifiedUtf8(premainClass, oldLen, str, newLen);
209 }
210 premainClass = str;
211 }
212 if (premainClass == NULL) {
213 fprintf(stderr, "-javaagent: memory allocation failed\n");
214 free(jarfile);
215 if (options != NULL) free(options);
216 freeAttributes(attributes);
217 return JNI_ERR;
218 }
219
220 /*
221 * If the Boot-Class-Path attribute is specified then we process
222 * each relative URL and add it to the bootclasspath.
223 */
224 bootClassPath = getAttribute(attributes, "Boot-Class-Path");
225 if (bootClassPath != NULL) {
226 appendBootClassPath(agent, jarfile, bootClassPath);
227 }
228
229 /*
230 * Convert JAR attributes into agent capabilities
231 */
232 convertCapabilityAttributes(attributes, agent);
233
234 /*
235 * Track (record) the agent class name and options data
236 */
237 initerror = recordCommandLineData(agent, premainClass, options);
238
239 /*
240 * Clean-up
241 */
242 if (options != NULL) free(options);
243 freeAttributes(attributes);
244 free(premainClass);
245 }
246
247 switch (initerror) {
248 case JPLIS_INIT_ERROR_NONE:
249 result = JNI_OK;
250 break;
251 case JPLIS_INIT_ERROR_CANNOT_CREATE_NATIVE_AGENT:
252 result = JNI_ERR;
253 fprintf(stderr, "java.lang.instrument/-javaagent: cannot create native agent.\n");
254 break;
255 case JPLIS_INIT_ERROR_FAILURE:
256 result = JNI_ERR;
257 fprintf(stderr, "java.lang.instrument/-javaagent: initialization of native agent failed.\n");
258 break;
259 case JPLIS_INIT_ERROR_ALLOCATION_FAILURE:
260 result = JNI_ERR;
261 fprintf(stderr, "java.lang.instrument/-javaagent: allocation failure.\n");
262 break;
263 case JPLIS_INIT_ERROR_AGENT_CLASS_NOT_SPECIFIED:
264 result = JNI_ERR;
265 fprintf(stderr, "-javaagent: agent class not specified.\n");
266 break;
267 default:
268 result = JNI_ERR;
269 fprintf(stderr, "java.lang.instrument/-javaagent: unknown error\n");
270 break;
271 }
272 return result;
273}
274
275/*
276 * Agent_OnAttach returns a jint. 0/JNI_OK indicates success and non-0
277 * indicates an error. To allow the attach mechanism throw an
278 * AgentInitializationException with a reasonable exception message we define
279 * a few specific errors here.
280 */
281#define AGENT_ERROR_BADJAR ((jint)100) /* Agent JAR not found or no Agent-Class attribute */
282#define AGENT_ERROR_NOTONCP ((jint)101) /* Unable to add JAR file to system class path */
283#define AGENT_ERROR_STARTFAIL ((jint)102) /* No agentmain method or agentmain failed */
284
285/*
286 * This will be called once each time a tool attaches to the VM and loads
287 * the JPLIS library.
288 */
289JNIEXPORT jint JNICALL
290DEF_Agent_OnAttach(JavaVM* vm, char *args, void * reserved) {
291 JPLISInitializationError initerror = JPLIS_INIT_ERROR_NONE;
292 jint result = JNI_OK;
293 JPLISAgent * agent = NULL;
294 JNIEnv * jni_env = NULL;
295
296 /*
297 * Need JNIEnv - guaranteed to be called from thread that is already
298 * attached to VM
299 */
300 result = (*vm)->GetEnv(vm, (void**)&jni_env, JNI_VERSION_1_2);
301 jplis_assert(result==JNI_OK);
302
303 initerror = createNewJPLISAgent(vm, &agent);
304 if ( initerror == JPLIS_INIT_ERROR_NONE ) {
305 int oldLen, newLen;
306 char * jarfile;
307 char * options;
308 jarAttribute* attributes;
309 char * agentClass;
310 char * bootClassPath;
311 jboolean success;
312
313 /*
314 * Parse <jarfile>[=options] into jarfile and options
315 */
316 if (parseArgumentTail(args, &jarfile, &options) != 0) {
317 return JNI_ENOMEM;
318 }
319
320 /*
321 * Open the JAR file and parse the manifest
322 */
323 attributes = readAttributes( jarfile );
324 if (attributes == NULL) {
325 fprintf(stderr, "Error opening zip file or JAR manifest missing: %s\n", jarfile);
326 free(jarfile);
327 if (options != NULL) free(options);
328 return AGENT_ERROR_BADJAR;
329 }
330
331 agentClass = getAttribute(attributes, "Agent-Class");
332 if (agentClass == NULL) {
333 fprintf(stderr, "Failed to find Agent-Class manifest attribute from %s\n",
334 jarfile);
335 free(jarfile);
336 if (options != NULL) free(options);
337 freeAttributes(attributes);
338 return AGENT_ERROR_BADJAR;
339 }
340
341 /*
342 * Add the jarfile to the system class path
343 */
344 if (appendClassPath(agent, jarfile)) {
345 fprintf(stderr, "Unable to add %s to system class path "
346 "- not supported by system class loader or configuration error!\n",
347 jarfile);
348 free(jarfile);
349 if (options != NULL) free(options);
350 freeAttributes(attributes);
351 return AGENT_ERROR_NOTONCP;
352 }
353
354 /*
355 * The value of the Agent-Class attribute becomes the agent
356 * class name. The manifest is in UTF8 so need to convert to
357 * modified UTF8 (see JNI spec).
358 */
359 oldLen = (int)strlen(agentClass);
360 newLen = modifiedUtf8LengthOfUtf8(agentClass, oldLen);
361 if (newLen == oldLen) {
362 agentClass = strdup(agentClass);
363 } else {
364 char* str = (char*)malloc( newLen+1 );
365 if (str != NULL) {
366 convertUtf8ToModifiedUtf8(agentClass, oldLen, str, newLen);
367 }
368 agentClass = str;
369 }
370 if (agentClass == NULL) {
371 free(jarfile);
372 if (options != NULL) free(options);
373 freeAttributes(attributes);
374 return JNI_ENOMEM;
375 }
376
377 /*
378 * If the Boot-Class-Path attribute is specified then we process
379 * each URL - in the live phase only JAR files will be added.
380 */
381 bootClassPath = getAttribute(attributes, "Boot-Class-Path");
382 if (bootClassPath != NULL) {
383 appendBootClassPath(agent, jarfile, bootClassPath);
384 }
385
386 /*
387 * Convert JAR attributes into agent capabilities
388 */
389 convertCapabilityAttributes(attributes, agent);
390
391 /*
392 * Create the java.lang.instrument.Instrumentation instance
393 */
394 success = createInstrumentationImpl(jni_env, agent);
395 jplis_assert(success);
396
397 /*
398 * Setup ClassFileLoadHook handler.
399 */
400 if (success) {
401 success = setLivePhaseEventHandlers(agent);
402 jplis_assert(success);
403 }
404
405 /*
406 * Start the agent
407 */
408 if (success) {
409 success = startJavaAgent(agent,
410 jni_env,
411 agentClass,
412 options,
413 agent->mAgentmainCaller);
414 }
415
416 if (!success) {
417 fprintf(stderr, "Agent failed to start!\n");
418 result = AGENT_ERROR_STARTFAIL;
419 }
420
421 /*
422 * Clean-up
423 */
424 free(jarfile);
425 if (options != NULL) free(options);
426 free(agentClass);
427 freeAttributes(attributes);
428 }
429
430 return result;
431}
432
433
434JNIEXPORT void JNICALL
435DEF_Agent_OnUnload(JavaVM *vm) {
436}
437
438/**
439 * Invoked by the java launcher to load an agent in the main executable JAR.
440 * The Launcher-Agent-Class attribute in the main manifest of the JAR file
441 * is the agent class.
442 *
443 * Returns JNI_OK if the agent is loaded and initialized; JNI_ERR if this
444 * function fails, possibly with a pending exception.
445 */
446jint loadAgent(JNIEnv* env, jstring path) {
447 JavaVM* vm;
448 JPLISAgent* agent;
449 const char* jarfile = NULL;
450 jarAttribute* attributes = NULL;
451 char* agentClass = NULL;
452 char* bootClassPath;
453 int oldLen, newLen;
454 jint result = JNI_ERR;
455
456 if ((*env)->GetJavaVM(env, &vm) < 0) {
457 return JNI_ERR;
458 }
459
460 // create JPLISAgent with JVMTI environment
461 if (createNewJPLISAgent(vm, &agent) != JPLIS_INIT_ERROR_NONE) {
462 return JNI_ERR;
463 }
464
465 // get path to JAR file as UTF-8 string
466 jarfile = (*env)->GetStringUTFChars(env, path, NULL);
467 if (jarfile == NULL) {
468 return JNI_ERR;
469 }
470
471 // read the attributes in the main section of JAR manifest
472 attributes = readAttributes(jarfile);
473 if (attributes == NULL) {
474 goto releaseAndReturn;
475 }
476
477 // Launcher-Agent-Class is required
478 agentClass = getAttribute(attributes, "Launcher-Agent-Class");
479 if (agentClass == NULL) {
480 goto releaseAndReturn;
481 }
482
483 // The value of Launcher-Agent-Class is in UTF-8, convert it to modified UTF-8
484 oldLen = (int) strlen(agentClass);
485 newLen = modifiedUtf8LengthOfUtf8(agentClass, oldLen);
486 if (newLen == oldLen) {
487 agentClass = strdup(agentClass);
488 } else {
489 char* str = (char*) malloc(newLen + 1);
490 if (str != NULL) {
491 convertUtf8ToModifiedUtf8(agentClass, oldLen, str, newLen);
492 }
493 agentClass = str;
494 }
495 if (agentClass == NULL) {
496 jthrowable oome = createThrowable(env, "java/lang/OutOfMemoryError", NULL);
497 if (oome != NULL) (*env)->Throw(env, oome);
498 goto releaseAndReturn;
499 }
500
501 // Boot-Class-Path
502 bootClassPath = getAttribute(attributes, "Boot-Class-Path");
503 if (bootClassPath != NULL) {
504 appendBootClassPath(agent, jarfile, bootClassPath);
505 }
506
507 // Can-XXXX capabilities
508 convertCapabilityAttributes(attributes, agent);
509
510 // Create the java.lang.instrument.Instrumentation object
511 if (!createInstrumentationImpl(env, agent)) {
512 goto releaseAndReturn;
513 }
514
515 // Enable the ClassFileLoadHook
516 if (!setLivePhaseEventHandlers(agent)) {
517 goto releaseAndReturn;
518 }
519
520 // invoke the agentmain method
521 if (!startJavaAgent(agent, env, agentClass, "", agent->mAgentmainCaller)) {
522 goto releaseAndReturn;
523 }
524
525 // initialization complete
526 result = JNI_OK;
527
528 releaseAndReturn:
529 if (agentClass != NULL) {
530 free(agentClass);
531 }
532 if (attributes != NULL) {
533 freeAttributes(attributes);
534 }
535 if (jarfile != NULL) {
536 (*env)->ReleaseStringUTFChars(env, path, jarfile);
537 }
538
539 return result;
540}
541
542/*
543 * JVMTI callback support
544 *
545 * We have two "stages" of callback support.
546 * At OnLoad time, we install a VMInit handler.
547 * When the VMInit handler runs, we remove the VMInit handler and install a
548 * ClassFileLoadHook handler.
549 */
550
551void JNICALL
552eventHandlerVMInit( jvmtiEnv * jvmtienv,
553 JNIEnv * jnienv,
554 jthread thread) {
555 JPLISEnvironment * environment = NULL;
556 jboolean success = JNI_FALSE;
557
558 environment = getJPLISEnvironment(jvmtienv);
559
560 /* process the premain calls on the all the JPL agents */
561 if (environment == NULL) {
562 abortJVM(jnienv, JPLIS_ERRORMESSAGE_CANNOTSTART ", getting JPLIS environment failed");
563 }
564 jthrowable outstandingException = NULL;
565 /*
566 * Add the jarfile to the system class path
567 */
568 JPLISAgent * agent = environment->mAgent;
569 if (appendClassPath(agent, agent->mJarfile)) {
570 fprintf(stderr, "Unable to add %s to system class path - "
571 "the system class loader does not define the "
572 "appendToClassPathForInstrumentation method or the method failed\n",
573 agent->mJarfile);
574 free((void *)agent->mJarfile);
575 abortJVM(jnienv, JPLIS_ERRORMESSAGE_CANNOTSTART ", appending to system class path failed");
576 }
577 free((void *)agent->mJarfile);
578 agent->mJarfile = NULL;
579
580 outstandingException = preserveThrowable(jnienv);
581 success = processJavaStart( environment->mAgent, jnienv);
582 restoreThrowable(jnienv, outstandingException);
583
584 /* if we fail to start cleanly, bring down the JVM */
585 if ( !success ) {
586 abortJVM(jnienv, JPLIS_ERRORMESSAGE_CANNOTSTART ", processJavaStart failed");
587 }
588}
589
590void JNICALL
591eventHandlerClassFileLoadHook( jvmtiEnv * jvmtienv,
592 JNIEnv * jnienv,
593 jclass class_being_redefined,
594 jobject loader,
595 const char* name,
596 jobject protectionDomain,
597 jint class_data_len,
598 const unsigned char* class_data,
599 jint* new_class_data_len,
600 unsigned char** new_class_data) {
601 JPLISEnvironment * environment = NULL;
602
603 environment = getJPLISEnvironment(jvmtienv);
604
605 /* if something is internally inconsistent (no agent), just silently return without touching the buffer */
606 if ( environment != NULL ) {
607 jthrowable outstandingException = preserveThrowable(jnienv);
608 transformClassFile( environment->mAgent,
609 jnienv,
610 loader,
611 name,
612 class_being_redefined,
613 protectionDomain,
614 class_data_len,
615 class_data,
616 new_class_data_len,
617 new_class_data,
618 environment->mIsRetransformer);
619 restoreThrowable(jnienv, outstandingException);
620 }
621}
622
623
624
625
626/*
627 * URLs in Boot-Class-Path attributes are separated by one or more spaces.
628 * This function splits the attribute value into a list of path segments.
629 * The attribute value is in UTF8 but cannot contain NUL. Also non US-ASCII
630 * characters must be escaped (URI syntax) so safe to iterate through the
631 * value as a C string.
632 */
633static void
634splitPathList(const char* str, int* pathCount, char*** paths) {
635 int count = 0;
636 char** segments = NULL;
637 char** new_segments;
638 char* c = (char*) str;
639 while (*c != '\0') {
640 while (*c == ' ') c++; /* skip leading spaces */
641 if (*c == '\0') {
642 break;
643 }
644 new_segments = (char**)realloc(segments, (count+1)*sizeof(char*));
645 if (new_segments == NULL) {
646 jplis_assert(0);
647 free(segments);
648 count = 0;
649 segments = NULL;
650 break;
651 }
652 segments = new_segments;
653 segments[count++] = c;
654 c = strchr(c, ' ');
655 if (c == NULL) {
656 break;
657 }
658 *c = '\0';
659 c++;
660 }
661 *pathCount = count;
662 *paths = segments;
663}
664
665
666/* URI path decoding - ported from src/share/classes/java/net/URI.java */
667
668static int
669decodeNibble(char c) {
670 if ((c >= '0') && (c <= '9'))
671 return c - '0';
672 if ((c >= 'a') && (c <= 'f'))
673 return c - 'a' + 10;
674 if ((c >= 'A') && (c <= 'F'))
675 return c - 'A' + 10;
676 return -1;
677}
678
679static int
680decodeByte(char c1, char c2) {
681 return (((decodeNibble(c1) & 0xf) << 4) | ((decodeNibble(c2) & 0xf) << 0));
682}
683
684/*
685 * Evaluates all escapes in s. Assumes that escapes are well-formed
686 * syntactically, i.e., of the form %XX.
687 * If the path does not require decoding the original path is
688 * returned. Otherwise the decoded path (heap allocated) is returned,
689 * along with the length of the decoded path. Note that the return
690 * string will not be null terminated after decoding.
691 */
692static
693char *decodePath(const char *s, int* decodedLen) {
694 int n;
695 char *result;
696 char *resultp;
697 int c;
698 int i;
699
700 n = (int)strlen(s);
701 if (n == 0) {
702 *decodedLen = 0;
703 return (char*)s;
704 }
705 if (strchr(s, '%') == NULL) {
706 *decodedLen = n;
707 return (char*)s; /* no escapes, we are done */
708 }
709
710 resultp = result = calloc(n+1, 1);
711 c = s[0];
712 for (i = 0; i < n;) {
713 if (c != '%') {
714 *resultp++ = c;
715 if (++i >= n)
716 break;
717 c = s[i];
718 continue;
719 }
720 for (;;) {
721 char b1 = s[++i];
722 char b2 = s[++i];
723 int decoded = decodeByte(b1, b2);
724 *resultp++ = decoded;
725 if (++i >= n)
726 break;
727 c = s[i];
728 if (c != '%')
729 break;
730 }
731 }
732 *decodedLen = (int)(resultp - result);
733 return result; // not null terminated.
734}
735
736/*
737 * Append the given jar file to the system class path. This should succeed in the
738 * onload phase but may fail in the live phase if the system class loader doesn't
739 * support appending to the class path.
740 */
741static int
742appendClassPath( JPLISAgent* agent,
743 const char* jarfile ) {
744 jvmtiEnv* jvmtienv = jvmti(agent);
745 jvmtiError jvmtierr;
746
747 jvmtierr = (*jvmtienv)->AddToSystemClassLoaderSearch(jvmtienv, jarfile);
748 check_phase_ret_1(jvmtierr);
749
750 switch (jvmtierr) {
751 case JVMTI_ERROR_NONE :
752 return 0;
753 case JVMTI_ERROR_CLASS_LOADER_UNSUPPORTED :
754 fprintf(stderr, "System class loader does not define "
755 "the appendToClassPathForInstrumentation method\n");
756 break;
757 default:
758 fprintf(stderr, "Unexpected error (%d) returned by "
759 "AddToSystemClassLoaderSearch\n", jvmtierr);
760 break;
761 }
762 return -1;
763}
764
765
766/*
767 * res = func, free'ing the previous value of 'res' if function
768 * returns a new result.
769 */
770#define TRANSFORM(res,func) { \
771 char* tmp = func; \
772 if (tmp != res) { \
773 free(res); \
774 res = tmp; \
775 } \
776 jplis_assert((void*)res != (void*)NULL); \
777}
778
779/**
780 * Convert a pathname to canonical form.
781 * This method is exported from libjava.
782 */
783extern int
784Canonicalize(JNIEnv *unused, char *orig, char *out, int len);
785
786
787/*
788 * This function takes the value of the Boot-Class-Path attribute,
789 * splits it into the individual path segments, and then combines it
790 * with the path to the jar file to create the path to be added
791 * to the bootclasspath.
792 *
793 * Each individual path segment starts out as a UTF8 string. Additionally
794 * as the path is specified to use URI path syntax all non US-ASCII
795 * characters are escaped. Once the URI path is decoded we get a UTF8
796 * string which must then be converted to the platform encoding (as it
797 * will be combined with the platform path of the jar file). Once
798 * converted it is then normalized (remove duplicate slashes, etc.).
799 * If the resulting path is an absolute path (starts with a slash for
800 * example) then the path will be added to the bootclasspath. Otherwise
801 * if it's not absolute then we get the canoncial path of the agent jar
802 * file and then resolve the path in the context of the base path of
803 * the agent jar.
804 */
805static void
806appendBootClassPath( JPLISAgent* agent,
807 const char* jarfile,
808 const char* pathList ) {
809 char canonicalPath[MAXPATHLEN];
810 char *parent = NULL;
811 int haveBasePath = 0;
812
813 int count, i;
814 char **paths;
815 jvmtiEnv* jvmtienv = jvmti(agent);
816 jvmtiError jvmtierr;
817
818 /*
819 * Split the attribute value into the individual path segments
820 * and process each in sequence
821 */
822 splitPathList(pathList, &count, &paths);
823
824 for (i=0; i<count; i++) {
825 int len;
826 char* path;
827 char* pos;
828
829 /*
830 * The path segment at this point is a pointer into the attribute
831 * value. As it will go through a number of transformation (tossing away
832 * the previous results as we go along) it make it easier if the path
833 * starts out as a heap allocated string.
834 */
835 path = strdup(paths[i]);
836 jplis_assert(path != (char*)NULL);
837
838 /*
839 * The attribute is specified to be a list of relative URIs so in theory
840 * there could be a query component - if so, get rid of it.
841 */
842 pos = strchr(path, '?');
843 if (pos != NULL) {
844 *pos = '\0';
845 }
846
847 /*
848 * Check for characters that are not allowed in the path component of
849 * a URI.
850 */
851 if (validatePathChars(path)) {
852 fprintf(stderr, "WARNING: illegal character in Boot-Class-Path value: %s\n",
853 path);
854 free(path);
855 continue;
856 }
857
858
859 /*
860 * Next decode any escaped characters. The result is a UTF8 string.
861 */
862 TRANSFORM(path, decodePath(path,&len));
863
864 /*
865 * Convert to the platform encoding
866 */
867 {
868 char platform[MAXPATHLEN];
869 int new_len = convertUft8ToPlatformString(path, len, platform, MAXPATHLEN);
870 free(path);
871 if (new_len < 0) {
872 /* bogus value - exceeds maximum path size or unable to convert */
873 continue;
874 }
875 path = strdup(platform);
876 jplis_assert(path != (char*)NULL);
877 }
878
879 /*
880 * Post-process the URI path - needed on Windows to transform
881 * /c:/foo to c:/foo.
882 */
883 TRANSFORM(path, fromURIPath(path));
884
885 /*
886 * Normalize the path - no duplicate slashes (except UNCs on Windows), trailing
887 * slash removed.
888 */
889 TRANSFORM(path, normalize(path));
890
891 /*
892 * If the path is an absolute path then add to the bootclassloader
893 * search path. Otherwise we get the canonical path of the agent jar
894 * and then use its base path (directory) to resolve the given path
895 * segment.
896 *
897 * NOTE: JVMTI is specified to use modified UTF8 strings (like JNI).
898 * In 1.5.0 the AddToBootstrapClassLoaderSearch takes a platform string
899 * - see 5049313.
900 */
901 if (isAbsolute(path)) {
902 jvmtierr = (*jvmtienv)->AddToBootstrapClassLoaderSearch(jvmtienv, path);
903 } else {
904 char* resolved;
905
906 if (!haveBasePath) {
907 /* Use NULL as the JNIEnv since we know that Canonicalize does not use it. */
908 if (Canonicalize(NULL, (char*)jarfile, canonicalPath, sizeof(canonicalPath)) != 0) {
909 fprintf(stderr, "WARNING: unable to canonicalize %s\n", jarfile);
910 free(path);
911 continue;
912 }
913 parent = basePath(canonicalPath);
914 jplis_assert(parent != (char*)NULL);
915 haveBasePath = 1;
916 }
917
918 resolved = resolve(parent, path);
919 jvmtierr = (*jvmtienv)->AddToBootstrapClassLoaderSearch(jvmtienv, resolved);
920 }
921
922 /* print warning if boot class path not updated */
923 if (jvmtierr != JVMTI_ERROR_NONE) {
924 check_phase_blob_ret(jvmtierr, free(path));
925
926 fprintf(stderr, "WARNING: %s not added to bootstrap class loader search: ", path);
927 switch (jvmtierr) {
928 case JVMTI_ERROR_ILLEGAL_ARGUMENT :
929 fprintf(stderr, "Illegal argument or not JAR file\n");
930 break;
931 default:
932 fprintf(stderr, "Unexpected error: %d\n", jvmtierr);
933 }
934 }
935
936 /* finished with the path */
937 free(path);
938 }
939
940
941 /* clean-up */
942 if (haveBasePath && parent != canonicalPath) {
943 free(parent);
944 }
945}
946