1 /*
2  * Copyright (c) 2003, 2008, 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 
50 static int
51 appendClassPath(JPLISAgent* agent,
52                 const char* jarfile);
53 
54 static void
55 appendBootClassPath(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  */
65 static int
parseArgumentTail(char * tail,char ** name,char ** options)66 parseArgumentTail(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  */
98 jboolean
getBooleanAttribute(const jarAttribute * attributes,const char * name)99 getBooleanAttribute(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  */
108 void
convertCapabilityAtrributes(const jarAttribute * attributes,JPLISAgent * agent)109 convertCapabilityAtrributes(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  */
143 JNIEXPORT jint JNICALL
Agent_OnLoad(JavaVM * vm,char * tail,void * reserved)144 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 *          agentClass;
157         char *          bootClassPath;
158 
159         /*
160          * Parse <jarfile>[=options] into jarfile and options
161          */
162         if (parseArgumentTail(tail, &jarfile, &options) != 0) {
163             fprintf(stderr, "-javaagent: memory allocation failure.\n");
164             return JNI_ERR;
165         }
166 
167         /*
168          * Agent_OnLoad is specified to provide the agent options
169          * argument tail in modified UTF8. However for 1.5.0 this is
170          * actually in the platform encoding - see 5049313.
171          *
172          * Open zip/jar file and parse archive. If can't be opened or
173          * not a zip file return error. Also if Premain-Class attribute
174          * isn't present we return an error.
175          */
176         attributes = readAttributes(jarfile);
177         if (attributes == NULL) {
178             fprintf(stderr, "Error opening zip file or JAR manifest missing : %s\n", jarfile);
179             free(jarfile);
180             if (options != NULL) free(options);
181             return JNI_ERR;
182         }
183 
184         premainClass = getAttribute(attributes, "Premain-Class");
185         if (premainClass == NULL) {
186             fprintf(stderr, "Failed to find Premain-Class manifest attribute in %s\n",
187                 jarfile);
188             free(jarfile);
189             if (options != NULL) free(options);
190             freeAttributes(attributes);
191             return JNI_ERR;
192         }
193 
194         /*
195          * Add to the jarfile
196          */
197         appendClassPath(agent, jarfile);
198 
199         /*
200          * The value of the Premain-Class attribute becomes the agent
201          * class name. The manifest is in UTF8 so need to convert to
202          * modified UTF8 (see JNI spec).
203          */
204         oldLen = (int)strlen(premainClass);
205         newLen = modifiedUtf8LengthOfUtf8(premainClass, oldLen);
206         /*
207          * According to JVMS class name is represented as CONSTANT_Utf8_info,
208          * so its length is u2 (i.e. must be <= 0xFFFF).
209          * Negative oldLen or newLen means we got signed integer overflow
210          * (modifiedUtf8LengthOfUtf8 returns negative value if oldLen is negative).
211          */
212         if (oldLen < 0 || newLen < 0 || newLen > 0xFFFF) {
213             fprintf(stderr, "-javaagent: Premain-Class value is too big\n");
214             free(jarfile);
215             if (options != NULL) free(options);
216             freeAttributes(attributes);
217             return JNI_ERR;
218         }
219         if (newLen == oldLen) {
220             premainClass = strdup(premainClass);
221         } else {
222             char* str = (char*)malloc( newLen+1 );
223             if (str != NULL) {
224                 convertUtf8ToModifiedUtf8(premainClass, oldLen, str, newLen);
225             }
226             premainClass = str;
227         }
228         if (premainClass == NULL) {
229             fprintf(stderr, "-javaagent: memory allocation failed\n");
230             free(jarfile);
231             if (options != NULL) free(options);
232             freeAttributes(attributes);
233             return JNI_ERR;
234         }
235 
236         /*
237          * If the Boot-Class-Path attribute is specified then we process
238          * each relative URL and add it to the bootclasspath.
239          */
240         bootClassPath = getAttribute(attributes, "Boot-Class-Path");
241         if (bootClassPath != NULL) {
242             appendBootClassPath(agent, jarfile, bootClassPath);
243         }
244 
245         /*
246          * Convert JAR attributes into agent capabilities
247          */
248         convertCapabilityAtrributes(attributes, agent);
249 
250         /*
251          * Track (record) the agent class name and options data
252          */
253         initerror = recordCommandLineData(agent, premainClass, options);
254 
255         /*
256          * Clean-up
257          */
258         free(jarfile);
259         if (options != NULL) free(options);
260         freeAttributes(attributes);
261         free(premainClass);
262     }
263 
264     switch (initerror) {
265     case JPLIS_INIT_ERROR_NONE:
266       result = JNI_OK;
267       break;
268     case JPLIS_INIT_ERROR_CANNOT_CREATE_NATIVE_AGENT:
269       result = JNI_ERR;
270       fprintf(stderr, "java.lang.instrument/-javaagent: cannot create native agent.\n");
271       break;
272     case JPLIS_INIT_ERROR_FAILURE:
273       result = JNI_ERR;
274       fprintf(stderr, "java.lang.instrument/-javaagent: initialization of native agent failed.\n");
275       break;
276     case JPLIS_INIT_ERROR_ALLOCATION_FAILURE:
277       result = JNI_ERR;
278       fprintf(stderr, "java.lang.instrument/-javaagent: allocation failure.\n");
279       break;
280     case JPLIS_INIT_ERROR_AGENT_CLASS_NOT_SPECIFIED:
281       result = JNI_ERR;
282       fprintf(stderr, "-javaagent: agent class not specified.\n");
283       break;
284     default:
285       result = JNI_ERR;
286       fprintf(stderr, "java.lang.instrument/-javaagent: unknown error\n");
287       break;
288     }
289     return result;
290 }
291 
292 /*
293  * Agent_OnAttach returns a jint. 0/JNI_OK indicates success and non-0
294  * indicates an error. To allow the attach mechanism throw an
295  * AgentInitializationException with a reasonable exception message we define
296  * a few specific errors here.
297  */
298 #define AGENT_ERROR_BADJAR    ((jint)100)  /* Agent JAR not found or no Agent-Class attribute */
299 #define AGENT_ERROR_NOTONCP   ((jint)101)  /* Unable to add JAR file to system class path */
300 #define AGENT_ERROR_STARTFAIL ((jint)102)  /* No agentmain method or agentmain failed */
301 
302 /*
303  *  This will be called once each time a tool attaches to the VM and loads
304  *  the JPLIS library.
305  */
306 JNIEXPORT jint JNICALL
Agent_OnAttach(JavaVM * vm,char * args,void * reserved)307 Agent_OnAttach(JavaVM* vm, char *args, void * reserved) {
308     JPLISInitializationError initerror  = JPLIS_INIT_ERROR_NONE;
309     jint                     result     = JNI_OK;
310     JPLISAgent *             agent      = NULL;
311     JNIEnv *                 jni_env    = NULL;
312 
313     /*
314      * Need JNIEnv - guaranteed to be called from thread that is already
315      * attached to VM
316      */
317     result = (*vm)->GetEnv(vm, (void**)&jni_env, JNI_VERSION_1_2);
318     jplis_assert(result==JNI_OK);
319 
320     initerror = createNewJPLISAgent(vm, &agent);
321     if ( initerror == JPLIS_INIT_ERROR_NONE ) {
322         int             oldLen, newLen;
323         char *          jarfile;
324         char *          options;
325         jarAttribute*   attributes;
326         char *          agentClass;
327         char *          bootClassPath;
328         jboolean        success;
329 
330         /*
331          * Parse <jarfile>[=options] into jarfile and options
332          */
333         if (parseArgumentTail(args, &jarfile, &options) != 0) {
334             return JNI_ENOMEM;
335         }
336 
337         /*
338          * Open the JAR file and parse the manifest
339          */
340         attributes = readAttributes( jarfile );
341         if (attributes == NULL) {
342             fprintf(stderr, "Error opening zip file or JAR manifest missing: %s\n", jarfile);
343             free(jarfile);
344             if (options != NULL) free(options);
345             return AGENT_ERROR_BADJAR;
346         }
347 
348         agentClass = getAttribute(attributes, "Agent-Class");
349         if (agentClass == NULL) {
350             fprintf(stderr, "Failed to find Agent-Class manifest attribute from %s\n",
351                 jarfile);
352             free(jarfile);
353             if (options != NULL) free(options);
354             freeAttributes(attributes);
355             return AGENT_ERROR_BADJAR;
356         }
357 
358         /*
359          * Add the jarfile to the system class path
360          */
361         if (appendClassPath(agent, jarfile)) {
362             fprintf(stderr, "Unable to add %s to system class path "
363                 "- not supported by system class loader or configuration error!\n",
364                 jarfile);
365             free(jarfile);
366             if (options != NULL) free(options);
367             freeAttributes(attributes);
368             return AGENT_ERROR_NOTONCP;
369         }
370 
371         /*
372          * The value of the Agent-Class attribute becomes the agent
373          * class name. The manifest is in UTF8 so need to convert to
374          * modified UTF8 (see JNI spec).
375          */
376         oldLen = (int)strlen(agentClass);
377         newLen = modifiedUtf8LengthOfUtf8(agentClass, oldLen);
378         /*
379          * According to JVMS class name is represented as CONSTANT_Utf8_info,
380          * so its length is u2 (i.e. must be <= 0xFFFF).
381          * Negative oldLen or newLen means we got signed integer overflow
382          * (modifiedUtf8LengthOfUtf8 returns negative value if oldLen is negative).
383          */
384         if (oldLen < 0 || newLen < 0 || newLen > 0xFFFF) {
385             fprintf(stderr, "Agent-Class value is too big\n");
386             free(jarfile);
387             if (options != NULL) free(options);
388             freeAttributes(attributes);
389             return AGENT_ERROR_BADJAR;
390         }
391         if (newLen == oldLen) {
392             agentClass = strdup(agentClass);
393         } else {
394             char* str = (char*)malloc( newLen+1 );
395             if (str != NULL) {
396                 convertUtf8ToModifiedUtf8(agentClass, oldLen, str, newLen);
397             }
398             agentClass = str;
399         }
400         if (agentClass == NULL) {
401             free(jarfile);
402             if (options != NULL) free(options);
403             freeAttributes(attributes);
404             return JNI_ENOMEM;
405         }
406 
407         /*
408          * If the Boot-Class-Path attribute is specified then we process
409          * each URL - in the live phase only JAR files will be added.
410          */
411         bootClassPath = getAttribute(attributes, "Boot-Class-Path");
412         if (bootClassPath != NULL) {
413             appendBootClassPath(agent, jarfile, bootClassPath);
414         }
415 
416         /*
417          * Convert JAR attributes into agent capabilities
418          */
419         convertCapabilityAtrributes(attributes, agent);
420 
421         /*
422          * Create the java.lang.instrument.Instrumentation instance
423          */
424         success = createInstrumentationImpl(jni_env, agent);
425         jplis_assert(success);
426 
427         /*
428          *  Turn on the ClassFileLoadHook.
429          */
430         if (success) {
431             success = setLivePhaseEventHandlers(agent);
432             jplis_assert(success);
433         }
434 
435         /*
436          * Start the agent
437          */
438         if (success) {
439             success = startJavaAgent(agent,
440                                      jni_env,
441                                      agentClass,
442                                      options,
443                                      agent->mAgentmainCaller);
444         }
445 
446         if (!success) {
447             fprintf(stderr, "Agent failed to start!\n");
448             result = AGENT_ERROR_STARTFAIL;
449         }
450 
451         /*
452          * Clean-up
453          */
454         free(jarfile);
455         if (options != NULL) free(options);
456         free(agentClass);
457         freeAttributes(attributes);
458     }
459 
460     return result;
461 }
462 
463 
464 JNIEXPORT void JNICALL
Agent_OnUnload(JavaVM * vm)465 Agent_OnUnload(JavaVM *vm) {
466 }
467 
468 
469 /*
470  *  JVMTI callback support
471  *
472  *  We have two "stages" of callback support.
473  *  At OnLoad time, we install a VMInit handler.
474  *  When the VMInit handler runs, we remove the VMInit handler and install a
475  *  ClassFileLoadHook handler.
476  */
477 
478 void JNICALL
eventHandlerVMInit(jvmtiEnv * jvmtienv,JNIEnv * jnienv,jthread thread)479 eventHandlerVMInit( jvmtiEnv *      jvmtienv,
480                     JNIEnv *        jnienv,
481                     jthread         thread) {
482     JPLISEnvironment * environment  = NULL;
483     jboolean           success      = JNI_FALSE;
484 
485     environment = getJPLISEnvironment(jvmtienv);
486 
487     /* process the premain calls on the all the JPL agents */
488     if ( environment != NULL ) {
489         jthrowable outstandingException = preserveThrowable(jnienv);
490         success = processJavaStart( environment->mAgent,
491                                     jnienv);
492         restoreThrowable(jnienv, outstandingException);
493     }
494 
495     /* if we fail to start cleanly, bring down the JVM */
496     if ( !success ) {
497         abortJVM(jnienv, JPLIS_ERRORMESSAGE_CANNOTSTART);
498     }
499 }
500 
501 void JNICALL
eventHandlerClassFileLoadHook(jvmtiEnv * jvmtienv,JNIEnv * jnienv,jclass class_being_redefined,jobject loader,const char * name,jobject protectionDomain,jint class_data_len,const unsigned char * class_data,jint * new_class_data_len,unsigned char ** new_class_data)502 eventHandlerClassFileLoadHook(  jvmtiEnv *              jvmtienv,
503                                 JNIEnv *                jnienv,
504                                 jclass                  class_being_redefined,
505                                 jobject                 loader,
506                                 const char*             name,
507                                 jobject                 protectionDomain,
508                                 jint                    class_data_len,
509                                 const unsigned char*    class_data,
510                                 jint*                   new_class_data_len,
511                                 unsigned char**         new_class_data) {
512     JPLISEnvironment * environment  = NULL;
513 
514     environment = getJPLISEnvironment(jvmtienv);
515 
516     /* if something is internally inconsistent (no agent), just silently return without touching the buffer */
517     if ( environment != NULL ) {
518         jthrowable outstandingException = preserveThrowable(jnienv);
519         transformClassFile( environment->mAgent,
520                             jnienv,
521                             loader,
522                             name,
523                             class_being_redefined,
524                             protectionDomain,
525                             class_data_len,
526                             class_data,
527                             new_class_data_len,
528                             new_class_data,
529                             environment->mIsRetransformer);
530         restoreThrowable(jnienv, outstandingException);
531     }
532 }
533 
534 
535 
536 
537 /*
538  * URLs in Boot-Class-Path attributes are separated by one or more spaces.
539  * This function splits the attribute value into a list of path segments.
540  * The attribute value is in UTF8 but cannot contain NUL. Also non US-ASCII
541  * characters must be escaped (URI syntax) so safe to iterate through the
542  * value as a C string.
543  */
544 static void
splitPathList(const char * str,int * pathCount,char *** paths)545 splitPathList(const char* str, int* pathCount, char*** paths) {
546     int count = 0;
547     char** segments = NULL;
548     char* c = (char*) str;
549     while (*c != '\0') {
550         while (*c == ' ') c++;          /* skip leading spaces */
551         if (*c == '\0') {
552             break;
553         }
554         if (segments == NULL) {
555             segments = (char**)malloc( sizeof(char**) );
556         } else {
557             segments = (char**)realloc( segments, (count+1)*sizeof(char**) );
558         }
559         jplis_assert(segments != (char**)NULL);
560         segments[count++] = c;
561         c = strchr(c, ' ');
562         if (c == NULL) {
563             break;
564         }
565         *c = '\0';
566         c++;
567     }
568     *pathCount = count;
569     *paths = segments;
570 }
571 
572 
573 /* URI path decoding - ported from src/share/classes/java/net/URI.java */
574 
575 static int
decodeNibble(char c)576 decodeNibble(char c) {
577     if ((c >= '0') && (c <= '9'))
578         return c - '0';
579     if ((c >= 'a') && (c <= 'f'))
580         return c - 'a' + 10;
581     if ((c >= 'A') && (c <= 'F'))
582         return c - 'A' + 10;
583     return -1;
584 }
585 
586 static int
decodeByte(char c1,char c2)587 decodeByte(char c1, char c2) {
588     return (((decodeNibble(c1) & 0xf) << 4) | ((decodeNibble(c2) & 0xf) << 0));
589 }
590 
591 /*
592  * Evaluates all escapes in s.  Assumes that escapes are well-formed
593  * syntactically, i.e., of the form %XX.
594  * If the path does not require decoding the the original path is
595  * returned. Otherwise the decoded path (heap allocated) is returned,
596  * along with the length of the decoded path. Note that the return
597  * string will not be null terminated after decoding.
598  */
599 static
decodePath(const char * s,int * decodedLen)600 char *decodePath(const char *s, int* decodedLen) {
601     int n;
602     char *result;
603     char *resultp;
604     int c;
605     int i;
606 
607     n = (int)strlen(s);
608     if (n == 0) {
609         *decodedLen = 0;
610         return (char*)s;
611     }
612     if (strchr(s, '%') == NULL) {
613         *decodedLen = n;
614         return (char*)s; /* no escapes, we are done */
615     }
616 
617     resultp = result = calloc(n+1, 1);
618     c = s[0];
619     for (i = 0; i < n;) {
620         if (c != '%') {
621             *resultp++ = c;
622             if (++i >= n)
623                 break;
624             c = s[i];
625             continue;
626         }
627         for (;;) {
628             char b1 = s[++i];
629             char b2 = s[++i];
630             int decoded = decodeByte(b1, b2);
631             *resultp++ = decoded;
632             if (++i >= n)
633                 break;
634             c = s[i];
635             if (c != '%')
636                 break;
637         }
638     }
639     *decodedLen = (int)(resultp - result);
640     return result; // not null terminated.
641 }
642 
643 /*
644  * Append the given jar file to the system class path. This should succeed in the
645  * onload phase but may fail in the live phase if the system class loader doesn't
646  * support appending to the class path.
647  */
648 static int
appendClassPath(JPLISAgent * agent,const char * jarfile)649 appendClassPath( JPLISAgent* agent,
650                  const char* jarfile ) {
651     jvmtiEnv* jvmtienv = jvmti(agent);
652     jvmtiError jvmtierr;
653 
654     jvmtierr = (*jvmtienv)->AddToSystemClassLoaderSearch(jvmtienv, jarfile);
655     check_phase_ret_1(jvmtierr);
656 
657     if (jvmtierr == JVMTI_ERROR_NONE) {
658         return 0;
659     } else {
660         jvmtiPhase phase;
661         jvmtiError err;
662 
663         err = (*jvmtienv)->GetPhase(jvmtienv, &phase);
664         /* can be called from any phase */
665         jplis_assert(err == JVMTI_ERROR_NONE);
666 
667         if (phase == JVMTI_PHASE_LIVE) {
668             switch (jvmtierr) {
669                 case JVMTI_ERROR_CLASS_LOADER_UNSUPPORTED :
670                     fprintf(stderr, "System class loader does not support adding "
671                         "JAR file to system class path during the live phase!\n");
672                         break;
673                 default:
674                     fprintf(stderr, "Unexpected error (%d) returned by "
675                         "AddToSystemClassLoaderSearch\n", jvmtierr);
676                     break;
677             }
678             return -1;
679         }
680         jplis_assert(0);
681     }
682     return -2;
683 }
684 
685 
686 /*
687  * res = func, free'ing the previous value of 'res' if function
688  * returns a new result.
689  */
690 #define TRANSFORM(res,func) {    \
691     char* tmp = func;            \
692     if (tmp != res) {            \
693         free(res);               \
694         res = tmp;               \
695     }                            \
696     jplis_assert((void*)res != (void*)NULL);     \
697 }
698 
699 
700 /*
701  * This function takes the value of the Boot-Class-Path attribute,
702  * splits it into the individual path segments, and then combines it
703  * with the path to the jar file to create the path to be added
704  * to the bootclasspath.
705  *
706  * Each individual path segment starts out as a UTF8 string. Additionally
707  * as the path is specified to use URI path syntax all non US-ASCII
708  * characters are escaped. Once the URI path is decoded we get a UTF8
709  * string which must then be converted to the platform encoding (as it
710  * will be combined with the platform path of the jar file). Once
711  * converted it is then normalized (remove duplicate slashes, etc.).
712  * If the resulting path is an absolute path (starts with a slash for
713  * example) then the path will be added to the bootclasspath. Otherwise
714  * if it's not absolute then we get the canoncial path of the agent jar
715  * file and then resolve the path in the context of the base path of
716  * the agent jar.
717  */
718 static void
appendBootClassPath(JPLISAgent * agent,const char * jarfile,const char * pathList)719 appendBootClassPath( JPLISAgent* agent,
720                      const char* jarfile,
721                      const char* pathList ) {
722     char canonicalPath[MAXPATHLEN];
723     char *parent = NULL;
724     int haveBasePath = 0;
725 
726     int count, i;
727     char **paths;
728     jvmtiEnv* jvmtienv = jvmti(agent);
729     jvmtiError jvmtierr;
730 
731     /*
732      * Split the attribute value into the individual path segments
733      * and process each in sequence
734      */
735     splitPathList(pathList, &count, &paths);
736 
737     for (i=0; i<count; i++) {
738         int len;
739         char* path;
740         char* pos;
741 
742         /*
743          * The path segment at this point is a pointer into the attribute
744          * value. As it will go through a number of transformation (tossing away
745          * the previous results as we go along) it make it easier if the path
746          * starts out as a heap allocated string.
747          */
748         path = strdup(paths[i]);
749         jplis_assert(path != (char*)NULL);
750 
751         /*
752          * The attribute is specified to be a list of relative URIs so in theory
753          * there could be a query component - if so, get rid of it.
754          */
755         pos = strchr(path, '?');
756         if (pos != NULL) {
757             *pos = '\0';
758         }
759 
760         /*
761          * Check for characters that are not allowed in the path component of
762          * a URI.
763          */
764         if (validatePathChars(path)) {
765             fprintf(stderr, "WARNING: illegal character in Boot-Class-Path value: %s\n",
766                path);
767             free(path);
768             continue;
769         }
770 
771 
772         /*
773          * Next decode any escaped characters. The result is a UTF8 string.
774          */
775         TRANSFORM(path, decodePath(path,&len));
776 
777         /*
778          * Convert to the platform encoding
779          */
780         {
781             char platform[MAXPATHLEN];
782             int new_len = convertUft8ToPlatformString(path, len, platform, MAXPATHLEN);
783             free(path);
784             if (new_len  < 0) {
785                 /* bogus value - exceeds maximum path size or unable to convert */
786                 continue;
787             }
788             path = strdup(platform);
789             jplis_assert(path != (char*)NULL);
790         }
791 
792         /*
793          * Post-process the URI path - needed on Windows to transform
794          * /c:/foo to c:/foo.
795          */
796         TRANSFORM(path, fromURIPath(path));
797 
798         /*
799          * Normalize the path - no duplicate slashes (except UNCs on Windows), trailing
800          * slash removed.
801          */
802         TRANSFORM(path, normalize(path));
803 
804         /*
805          * If the path is an absolute path then add to the bootclassloader
806          * search path. Otherwise we get the canonical path of the agent jar
807          * and then use its base path (directory) to resolve the given path
808          * segment.
809          *
810          * NOTE: JVMTI is specified to use modified UTF8 strings (like JNI).
811          * In 1.5.0 the AddToBootstrapClassLoaderSearch takes a platform string
812          * - see 5049313.
813          */
814         if (isAbsolute(path)) {
815             jvmtierr = (*jvmtienv)->AddToBootstrapClassLoaderSearch(jvmtienv, path);
816         } else {
817             char* resolved;
818 
819             if (!haveBasePath) {
820                 if (canonicalize((char*)jarfile, canonicalPath, sizeof(canonicalPath)) != 0) {
821                     fprintf(stderr, "WARNING: unable to canonicalize %s\n", jarfile);
822                     free(path);
823                     continue;
824                 }
825                 parent = basePath(canonicalPath);
826                 jplis_assert(parent != (char*)NULL);
827                 haveBasePath = 1;
828             }
829 
830             resolved = resolve(parent, path);
831             jvmtierr = (*jvmtienv)->AddToBootstrapClassLoaderSearch(jvmtienv, resolved);
832         }
833 
834         /* print warning if boot class path not updated */
835         if (jvmtierr != JVMTI_ERROR_NONE) {
836             check_phase_blob_ret(jvmtierr, free(path));
837 
838             fprintf(stderr, "WARNING: %s not added to bootstrap class loader search: ", path);
839             switch (jvmtierr) {
840                 case JVMTI_ERROR_ILLEGAL_ARGUMENT :
841                     fprintf(stderr, "Illegal argument or not JAR file\n");
842                     break;
843                 default:
844                     fprintf(stderr, "Unexpected error: %d\n", jvmtierr);
845             }
846         }
847 
848         /* finished with the path */
849         free(path);
850     }
851 
852 
853     /* clean-up */
854     if (haveBasePath && parent != canonicalPath) {
855         free(parent);
856     }
857 }
858