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