1 /*
   2  * Copyright (c) 2008, 2009, 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 package sun.jkernel;
  26 
  27 import java.io.*;
  28 import java.net.URLStreamHandlerFactory;
  29 import java.net.URL;
  30 import java.net.MalformedURLException;
  31 import java.security.*;
  32 import java.util.*;
  33 import java.util.concurrent.*;
  34 import java.util.jar.*;
  35 import java.util.zip.*;
  36 import sun.misc.BootClassLoaderHook;
  37 import sun.misc.Launcher;
  38 import sun.misc.URLClassPath;
  39 import sun.net.www.ParseUtil;
  40 
  41 /**
  42  * Handles the downloading of additional JRE components.  The bootstrap class
  43  * loader automatically invokes DownloadManager when it comes across a resource
  44  * that can't be located.
  45  *
  46  *@author Ethan Nicholas
  47  */
  48 public class DownloadManager extends BootClassLoaderHook {
  49     public static final String KERNEL_DOWNLOAD_URL_PROPERTY =
  50             "kernel.download.url";
  51     public static final String KERNEL_DOWNLOAD_ENABLED_PROPERTY =
  52             "kernel.download.enabled";
  53 
  54     public static final String KERNEL_DOWNLOAD_DIALOG_PROPERTY =
  55             "kernel.download.dialog";
  56 
  57     public static final String KERNEL_DEBUG_PROPERTY = "kernel.debug";
  58     // disables JRE completion when set to true, used as part of the build
  59     // process
  60     public static final String KERNEL_NOMERGE_PROPERTY = "kernel.nomerge";
  61 
  62     public static final String KERNEL_SIMULTANEOUS_DOWNLOADS_PROPERTY =
  63             "kernel.simultaneous.downloads";
  64 
  65     // used to bypass some problems with JAR entry modtimes not matching.
  66     // originally was set to zero, but apparently the epochs are different
  67     // for zip and pack so the pack/unpack cycle was causing the modtimes
  68     // to change.  With some recent changes to the reconstruction, I'm
  69     // not sure if this is actually necessary anymore.
  70     public static final int KERNEL_STATIC_MODTIME = 10000000;
  71 
  72     // indicates that bundles should be grabbed using getResource(), rather
  73     // than downloaded from a network path -- this is used during the build
  74     // process
  75     public static final String RESOURCE_URL = "internal-resource/";
  76     public static final String REQUESTED_BUNDLES_PATH = "lib" + File.separator +
  77             "bundles" + File.separator + "requested.list";
  78 
  79     private static final boolean disableDownloadDialog = "false".equals(
  80             System.getProperty(KERNEL_DOWNLOAD_DIALOG_PROPERTY));
  81 
  82     static boolean debug = "true".equals(
  83             System.getProperty(KERNEL_DEBUG_PROPERTY));
  84     // points to stderr in case we need to println before System.err is
  85     // initialized
  86     private static OutputStream errorStream;
  87     private static OutputStream logStream;
  88 
  89     static String MUTEX_PREFIX;
  90 
  91     static boolean complete;
  92 
  93     // 1 if jbroker started; 0 otherwise
  94     private static int _isJBrokerStarted = -1;
  95 
  96     // maps bundle names to URL strings
  97     private static Properties bundleURLs;
  98 
  99     public static final String JAVA_HOME = System.getProperty("java.home");
 100     public static final String USER_HOME = System.getProperty("user.home");
 101     public static final String JAVA_VERSION =
 102             System.getProperty("java.version");
 103     static final int BUFFER_SIZE = 2048;
 104 
 105     static volatile boolean jkernelLibLoaded = false;
 106 
 107     public static String DEFAULT_DOWNLOAD_URL =
 108         "http://javadl.sun.com/webapps/download/GetList/"
 109         +  System.getProperty("java.runtime.version") + "-kernel/windows-i586/";
 110 
 111     private static final String CUSTOM_PREFIX = "custom";
 112     private static final String KERNEL_PATH_SUFFIX = "-kernel";
 113 
 114     public static final String JAR_PATH_PROPERTY = "jarpath";
 115     public static final String SIZE_PROPERTY = "size";
 116     public static final String DEPENDENCIES_PROPERTY = "dependencies";
 117     public static final String INSTALL_PROPERTY = "install";
 118 
 119     private static boolean reportErrors = true;
 120 
 121     static final int ERROR_UNSPECIFIED = 0;
 122     static final int ERROR_DISK_FULL   = 1;
 123     static final int ERROR_MALFORMED_BUNDLE_PROPERTIES = 2;
 124     static final int ERROR_DOWNLOADING_BUNDLE_PROPERTIES = 3;
 125     static final int ERROR_MALFORMED_URL = 4;
 126     static final int ERROR_RETRY_CANCELLED = 5;
 127     static final int ERROR_NO_SUCH_BUNDLE = 6;
 128 
 129 
 130     // tracks whether the current thread is downloading.  A count of zero means
 131     // not currently downloading, >0 means the current thread is downloading or
 132     // installing a bundle.
 133     static ThreadLocal<Integer> downloading = new ThreadLocal<Integer>() {
 134         protected Integer initialValue() {
 135             return 0;
 136         }
 137     };
 138 
 139     private static File[] additionalBootStrapPaths = { };
 140 
 141     private static String[] bundleNames;
 142     private static String[] criticalBundleNames;
 143 
 144     private static String downloadURL;
 145 
 146     private static boolean visitorIdDetermined;
 147     private static String visitorId;
 148 
 149     /**
 150      * File and path where the Check value properties are gotten from
 151      */
 152     public static String CHECK_VALUES_FILE = "check_value.properties";
 153     static String CHECK_VALUES_DIR = "sun/jkernel/";
 154     static String CHECK_VALUES_PATH = CHECK_VALUES_DIR + CHECK_VALUES_FILE;
 155 
 156     /**
 157      * The contents of the bundle.properties file, which contains various
 158      * information about individual bundles.
 159      */
 160     private static Map<String, Map<String, String>> bundleProperties;
 161 
 162 
 163     /**
 164      * The contents of the resource_map file, which maps resources
 165      * to their respective bundles.
 166      */
 167     private static Map<String, String> resourceMap;
 168 
 169 
 170     /**
 171      * The contents of the file_map file, which maps files
 172      * to their respective bundles.
 173      */
 174     private static Map<String, String> fileMap;
 175 
 176     private static boolean extDirDetermined;
 177     private static boolean extDirIncluded;
 178 
 179     static {
 180         AccessController.doPrivileged(new PrivilegedAction() {
 181             public Object run() {
 182                 if (debug)
 183                     println("DownloadManager startup");
 184 
 185                  // this mutex is global and will apply to all different
 186                 // version of java kernel installed on the local machine
 187                 MUTEX_PREFIX = "jkernel";
 188                 boolean downloadEnabled = !"false".equals(
 189                         System.getProperty(KERNEL_DOWNLOAD_ENABLED_PROPERTY));
 190                 complete = !getBundlePath().exists() ||
 191                         !downloadEnabled;
 192 
 193                 // only load jkernel.dll if we are not "complete".
 194                 // DownloadManager will be loaded during build time, before
 195                 // jkernel.dll is built.  We only need to load jkernel.dll
 196                 // when DownloadManager needs to download something, which is
 197                 // not necessary during build time
 198                 if (!complete) {
 199                     loadJKernelLibrary();
 200                     log("Log opened");
 201 
 202                     if (isWindowsVista()) {
 203                         getLocalLowTempBundlePath().mkdirs();
 204                     }
 205 
 206                     new Thread() {
 207                         public void run() {
 208                             startBackgroundDownloads();
 209                         }
 210                     }.start();
 211 
 212                     try {
 213                         String dummyPath;
 214                         if (isWindowsVista()) {
 215                             dummyPath = USER_HOME +
 216                                     "\\appdata\\locallow\\dummy.kernel";
 217                         } else {
 218                             dummyPath = USER_HOME + "\\dummy.kernel";
 219                         }
 220 
 221                         File f = new File(dummyPath);
 222                         FileOutputStream out = new FileOutputStream(f, true);
 223                         out.close();
 224                         f.deleteOnExit();
 225 
 226                     } catch (IOException e) {
 227                         log(e);
 228                     }
 229                     // end of warm up code
 230 
 231                     new Thread("BundleDownloader") {
 232                         public void run() {
 233                             downloadRequestedBundles();
 234                         }
 235                     }.start();
 236                 }
 237                 return null;
 238             }
 239         });
 240     }
 241 
 242 
 243     static synchronized void loadJKernelLibrary() {
 244         if (!jkernelLibLoaded) {
 245             try {
 246                 System.loadLibrary("jkernel");
 247                 jkernelLibLoaded = true;
 248                 debug = getDebugProperty();
 249             } catch (Exception e) {
 250                 throw new Error(e);
 251             }
 252         }
 253     }
 254 
 255     static String appendTransactionId(String url) {
 256         StringBuilder result = new StringBuilder(url);
 257         String visitorId = DownloadManager.getVisitorId();
 258         if (visitorId != null) {
 259             if (url.indexOf("?") == -1)
 260                 result.append('?');
 261             else
 262                 result.append('&');
 263             result.append("transactionId=");
 264             result.append(DownloadManager.getVisitorId());
 265         }
 266         return result.toString();
 267     }
 268 
 269 
 270     /**
 271      * Returns the URL for the directory from which bundles should be
 272      * downloaded.
 273      */
 274     static synchronized String getBaseDownloadURL() {
 275         if (downloadURL == null) {
 276             log("Determining download URL...");
 277             loadJKernelLibrary();
 278 
 279             /*
 280              * First check if system property has been set - system
 281              * property should take over registry key setting.
 282              */
 283             downloadURL = System.getProperty(
 284                           DownloadManager.KERNEL_DOWNLOAD_URL_PROPERTY);
 285             log("System property kernel.download.url = " + downloadURL);
 286 
 287             /*
 288              * Now check if registry key has been set
 289              */
 290             if (downloadURL == null){
 291                 downloadURL = getUrlFromRegistry();
 292                 log("getUrlFromRegistry = " + downloadURL);
 293             }
 294 
 295             /*
 296              * Use default download url
 297              */
 298             if (downloadURL == null)
 299                 downloadURL = DEFAULT_DOWNLOAD_URL;
 300             log("Final download URL: " + downloadURL);
 301         }
 302         return downloadURL;
 303     }
 304 
 305 
 306     /**
 307      * Loads a file representing a node tree.  The format is described in
 308      * SplitJRE.writeTreeMap().  The node paths (such as
 309      * core/java/lang/Object.class) are interpreted with the root node as the
 310      * value and the remaining nodes as
 311      * the key, so the mapping for this entry would be java/lang/Object.class =
 312      * core.
 313      */
 314     static Map<String, String> readTreeMap(InputStream rawIn)
 315             throws IOException {
 316         // "token level" refers to the 0-31 byte that occurs prior to every
 317         // token in the stream, and would be e.g. <0> core <1> java <2> lang
 318         // <3> Object.class <3> String.class, which gives us two mappings:
 319         // java/lang/Object.class = core, and java/lang/String.class = core.
 320         // See the format description in SplitJRE.writeTreeMap for more details.
 321         Map<String, String> result = new HashMap<String, String>();
 322         InputStream in = new BufferedInputStream(rawIn);
 323         // holds the current token sequence,
 324         // e.g. {"core", "java", "lang", "Object.class"}
 325         List<String> tokens = new ArrayList<String>();
 326         StringBuilder currentToken = new StringBuilder();
 327         for (;;) {
 328             int c = in.read();
 329             if (c  == -1) // eof
 330                 break;
 331             if (c < 32) { // new token level
 332                 if (tokens.size() > 0) {
 333                     // replace the null at the end of the list with the token
 334                     // we just finished reading
 335                     tokens.set(tokens.size() - 1, currentToken.toString());
 336                 }
 337 
 338                 currentToken.setLength(0);
 339 
 340                 if (c > tokens.size()) {
 341                     // can't increase by more than one token level at a step
 342                     throw new InternalError("current token level is " +
 343                             (tokens.size() - 1) + " but encountered token " +
 344                             "level " + c);
 345                 }
 346                 else if (c == tokens.size()) {
 347                     // token level increased by 1; this means we are still
 348                     // adding tokens for the current mapping -- e.g. we have
 349                     // read "core", "java", "lang" and are just about to read
 350                     // "Object.class"
 351                     // add a placeholder for the new token
 352                     tokens.add(null);
 353                 }
 354                 else {
 355                     // we just stayed at the same level or backed up one or more
 356                     // token levels; this means that the current sequence is
 357                     // complete and needs to be added to the result map
 358                     StringBuilder key = new StringBuilder();
 359                     // combine all tokens except the first into a single string
 360                     for (int i = 1; i < tokens.size(); i++) {
 361                         if (i > 1)
 362                             key.append('/');
 363                         key.append(tokens.get(i));
 364                     }
 365                     // map the combined string to the first token, e.g.
 366                     // java/lang/Object.class = core
 367                     result.put(key.toString(), tokens.get(0));
 368                     // strip off tokens until we get back to the current token
 369                     // level
 370                     while (c < tokens.size())
 371                         tokens.remove(c);
 372                     // placeholder for upcoming token
 373                     tokens.add(null);
 374                 }
 375             }
 376             else if (c < 254) // character
 377                 currentToken.append((char) c);
 378             else if (c == 255)
 379                 currentToken.append(".class");
 380             else { // out-of-band value
 381                 throw new InternalError("internal error processing " +
 382                         "resource_map (can't-happen error)");
 383             }
 384         }
 385         if (tokens.size() > 0) // add token we just finished reading
 386             tokens.set(tokens.size() - 1, currentToken.toString());
 387         StringBuilder key = new StringBuilder();
 388         // add the last entry to the map
 389         for (int i = 1; i < tokens.size(); i++) {
 390             if (i > 1)
 391                 key.append('/');
 392             key.append(tokens.get(i));
 393         }
 394         if (!tokens.isEmpty())
 395             result.put(key.toString(), tokens.get(0));
 396         in.close();
 397         return Collections.unmodifiableMap(result);
 398     }
 399 
 400 
 401     /**
 402      * Returns the contents of the resource_map file, which maps
 403      * resources names to their respective bundles.
 404      */
 405     public static Map<String, String> getResourceMap() throws IOException {
 406         if (resourceMap == null) {
 407             InputStream in = DownloadManager.class.getResourceAsStream("resource_map");
 408             if (in != null) {
 409                 in = new BufferedInputStream(in);
 410                 try {
 411                     resourceMap = readTreeMap(in);
 412                     in.close();
 413                 }
 414                 catch (IOException e) {
 415                     // turns out we can be returned a broken stream instead of
 416                     // just null
 417                     resourceMap = new HashMap<String, String>();
 418                     complete = true;
 419                     log("Can't find resource_map, forcing complete to true");
 420                 }
 421                 in.close();
 422             }
 423             else {
 424                 resourceMap = new HashMap<String, String>();
 425                 complete = true;
 426                 log("Can't find resource_map, forcing complete to true");
 427             }
 428 
 429             for (int i = 1; ; i++) { // run through the numbered custom bundles
 430                 String name = CUSTOM_PREFIX + i;
 431                 File customPath = new File(getBundlePath(), name + ".jar");
 432                 if (customPath.exists()) {
 433                     JarFile custom = new JarFile(customPath);
 434                     Enumeration entries = custom.entries();
 435                     while (entries.hasMoreElements()) {
 436                         JarEntry entry = (JarEntry) entries.nextElement();
 437                         if (!entry.isDirectory())
 438                             resourceMap.put(entry.getName(), name);
 439                     }
 440                 }
 441                 else
 442                     break;
 443             }
 444         }
 445         return resourceMap;
 446     }
 447 
 448 
 449     /**
 450      * Returns the contents of the file_map file, which maps
 451      * file names to their respective bundles.
 452      */
 453     public static Map<String, String> getFileMap() throws IOException {
 454         if (fileMap == null) {
 455             InputStream in = DownloadManager.class.getResourceAsStream("file_map");
 456             if (in != null) {
 457                 in = new BufferedInputStream(in);
 458                 try {
 459                     fileMap = readTreeMap(in);
 460                     in.close();
 461                 }
 462                 catch (IOException e) {
 463                     // turns out we can be returned a broken stream instead of
 464                     // just null
 465                     fileMap = new HashMap<String, String>();
 466                     complete = true;
 467                     log("Can't find file_map, forcing complete to true");
 468                 }
 469                 in.close();
 470             }
 471             else {
 472                 fileMap = new HashMap<String, String>();
 473                 complete = true;
 474                 log("Can't find file_map, forcing complete to true");
 475             }
 476         }
 477         return fileMap;
 478     }
 479 
 480 
 481     /**
 482      * Returns the contents of the bundle.properties file, which maps
 483      * bundle names to a pipe-separated list of their properties.  Properties
 484      * include:
 485      * jarpath - By default, the JAR files (unpacked from classes.pack in the
 486      *           bundle) are stored under lib/bundles.  The jarpath property
 487      *           overrides this default setting, causing the JAR to be unpacked
 488      *           at the specified location.  This is used to preserve the
 489      *           identity of JRE JAR files such as lib/deploy.jar.
 490      * size    - The size of the download in bytes.
 491      */
 492     private static synchronized Map<String, Map<String, String>> getBundleProperties()
 493             throws IOException {
 494         if (bundleProperties == null) {
 495             InputStream in = DownloadManager.class.getResourceAsStream("bundle.properties");
 496             if (in == null) {
 497                 complete = true;
 498                 log("Can't find bundle.properties, forcing complete to true");
 499                 return null;
 500             }
 501             in = new BufferedInputStream(in);
 502             Properties tmp = new Properties();
 503             tmp.load(in);
 504             bundleProperties = new HashMap<String, Map<String, String>>();
 505             for (Map.Entry e : tmp.entrySet()) {
 506                 String key = (String) e.getKey();
 507                 String[] properties = ((String) e.getValue()).split("\\|");
 508                 Map<String, String> map = new HashMap<String, String>();
 509                 for (String entry : properties) {
 510                     int equals = entry.indexOf("=");
 511                     if (equals == -1)
 512                         throw new InternalError("error parsing bundle.properties: " +
 513                             entry);
 514                     map.put(entry.substring(0, equals).trim(),
 515                         entry.substring(equals + 1).trim());
 516                 }
 517                 bundleProperties.put(key, map);
 518             }
 519             in.close();
 520         }
 521         return bundleProperties;
 522     }
 523 
 524 
 525     /**
 526      * Returns a single bundle property value loaded from the bundle.properties
 527      * file.
 528      */
 529     static String getBundleProperty(String bundleName, String property) {
 530         try {
 531             Map<String, Map<String, String>> props = getBundleProperties();
 532             Map/*<String, String>*/ map = props != null ? props.get(bundleName) : null;
 533             return map != null ? (String) map.get(property) : null;
 534         }
 535         catch (IOException e) {
 536             throw new RuntimeException(e);
 537         }
 538     }
 539 
 540 
 541     /** Returns an array of all supported bundle names. */
 542     static String[] getBundleNames() throws IOException {
 543         if (bundleNames == null) {
 544             Set<String> result = new HashSet<String>();
 545             Map<String, String> resourceMap = getResourceMap();
 546             if (resourceMap != null)
 547                 result.addAll(resourceMap.values());
 548             Map<String, String> fileMap = getFileMap();
 549             if (fileMap != null)
 550                 result.addAll(fileMap.values());
 551             bundleNames = result.toArray(new String[result.size()]);
 552         }
 553         return bundleNames;
 554     }
 555 
 556 
 557     /**
 558      * Returns an array of all "critical" (must be downloaded prior to
 559      * completion) bundle names.
 560      */
 561     private static String[] getCriticalBundleNames() throws IOException {
 562         if (criticalBundleNames == null) {
 563             Set<String> result = new HashSet<String>();
 564             Map<String, String> fileMap = getFileMap();
 565             if (fileMap != null)
 566                 result.addAll(fileMap.values());
 567             criticalBundleNames = result.toArray(new String[result.size()]);
 568         }
 569         return criticalBundleNames;
 570     }
 571 
 572 
 573     public static void send(InputStream in, OutputStream out)
 574             throws IOException {
 575         byte[] buffer = new byte[BUFFER_SIZE];
 576         int c;
 577         while ((c = in.read(buffer)) > 0)
 578             out.write(buffer, 0, c);
 579     }
 580 
 581 
 582     /**
 583      * Determine whether all bundles have been downloaded, and if so create
 584      * the merged jars that will eventually replace rt.jar and resoures.jar.
 585      * IMPORTANT: this method should only be called from the background
 586      * download process.
 587      */
 588     static void performCompletionIfNeeded() {
 589         if (debug)
 590             log("DownloadManager.performCompletionIfNeeded: checking (" +
 591                     complete + ", " + System.getProperty(KERNEL_NOMERGE_PROPERTY)
 592                     + ")");
 593         if (complete ||
 594                 "true".equals(System.getProperty(KERNEL_NOMERGE_PROPERTY)))
 595             return;
 596         Bundle.loadReceipts();
 597         try {
 598             if (debug) {
 599                 List critical = new ArrayList(Arrays.asList(getCriticalBundleNames()));
 600                 critical.removeAll(Bundle.receipts);
 601                 log("DownloadManager.performCompletionIfNeeded: still need " +
 602                         critical.size() + " bundles (" + critical + ")");
 603             }
 604             if (Bundle.receipts.containsAll(Arrays.asList(getCriticalBundleNames()))) {
 605                 log("DownloadManager.performCompletionIfNeeded: running");
 606                 // all done!
 607                 new Thread("JarMerger") {
 608                     public void run() {
 609                         createMergedJars();
 610                     }
 611                 }.start();
 612             }
 613         }
 614         catch (IOException e) {
 615             throw new RuntimeException(e);
 616         }
 617     }
 618 
 619 
 620     /**
 621      * Returns the bundle corresponding to a given resource path (e.g.
 622      * "java/lang/Object.class").  If the resource does not appear in a bundle,
 623      * null is returned.
 624      */
 625     public static Bundle getBundleForResource(String resource)
 626             throws IOException {
 627         String bundleName = getResourceMap().get(resource);
 628         return bundleName != null ? Bundle.getBundle(bundleName) : null;
 629     }
 630 
 631 
 632     /**
 633      * Returns the bundle corresponding to a given JRE file path (e.g.
 634      * "bin/awt.dll").  If the file does not appear in a bundle, null is
 635      * returned.
 636      */
 637     private static Bundle getBundleForFile(String file) throws IOException {
 638         String bundleName = getFileMap().get(file);
 639         return bundleName != null ? Bundle.getBundle(bundleName) : null;
 640     }
 641 
 642 
 643     /**
 644      * Returns the path to the lib/bundles directory.
 645      */
 646     static File getBundlePath() {
 647         return new File(JAVA_HOME, "lib" + File.separatorChar + "bundles");
 648     }
 649 
 650     private static String getAppDataLocalLow() {
 651         return USER_HOME + "\\appdata\\locallow\\";
 652     }
 653 
 654     public static String getKernelJREDir() {
 655         return "kerneljre" + JAVA_VERSION;
 656     }
 657 
 658     static File getLocalLowTempBundlePath() {
 659         return new File(getLocalLowKernelJava() + "-bundles");
 660     }
 661 
 662     static String getLocalLowKernelJava() {
 663         return getAppDataLocalLow() + getKernelJREDir();
 664     }
 665 
 666     // To be revisited:
 667     // How DownloadManager maintains its bootstrap class path.
 668     // sun.misc.Launcher.getBootstrapClassPath() returns
 669     // DownloadManager.getBootstrapClassPath() instead.
 670     //
 671     // So should no longer need to lock the Launcher.class.
 672     // In addition, additionalBootStrapPaths is not really needed
 673     // if it obtains the initial bootclasspath during DownloadManager's
 674     // initialization.
 675     private static void addEntryToBootClassPath(File path) {
 676         // Must acquire these locks in this order
 677         synchronized(Launcher.class) {
 678             synchronized(DownloadManager.class) {
 679                 File[] newBootStrapPaths = new File[
 680                     additionalBootStrapPaths.length + 1];
 681                 System.arraycopy(additionalBootStrapPaths, 0, newBootStrapPaths,
 682                         0, additionalBootStrapPaths.length);
 683                 newBootStrapPaths[newBootStrapPaths.length - 1] = path;
 684                 additionalBootStrapPaths = newBootStrapPaths;
 685                 if (bootstrapClassPath != null)
 686                     bootstrapClassPath.addURL(getFileURL(path));
 687            }
 688        }
 689     }
 690 
 691     /**
 692      * Returns the kernel's bootstrap class path which includes the additional
 693      * JARs downloaded
 694      */
 695     private static URLClassPath bootstrapClassPath = null;
 696     private synchronized static
 697            URLClassPath getBootClassPath(URLClassPath bcp,
 698                                          URLStreamHandlerFactory factory)
 699     {
 700         if (bootstrapClassPath == null) {
 701             bootstrapClassPath = new URLClassPath(bcp.getURLs(), factory);
 702             for (File path : additionalBootStrapPaths) {
 703                 bootstrapClassPath.addURL(getFileURL(path));
 704             }
 705         }
 706         return bootstrapClassPath;
 707     }
 708 
 709     private static URL getFileURL(File file) {
 710         try {
 711             file = file.getCanonicalFile();
 712         } catch (IOException e) {}
 713 
 714         try {
 715             return ParseUtil.fileToEncodedURL(file);
 716         } catch (MalformedURLException e) {
 717             // Should never happen since we specify the protocol...
 718             throw new InternalError();
 719         }
 720     }
 721 
 722     /**
 723      * Scan through java.ext.dirs to see if the lib/ext directory is included.
 724      * If not, we shouldn't be "finding" lib/ext jars for download.
 725      */
 726     private static synchronized boolean extDirIsIncluded() {
 727         if (!extDirDetermined) {
 728             extDirDetermined = true;
 729             String raw = System.getProperty("java.ext.dirs");
 730             String ext = JAVA_HOME + File.separator + "lib" + File.separator + "ext";
 731             int index = 0;
 732             while (index < raw.length()) {
 733                 int newIndex = raw.indexOf(File.pathSeparator, index);
 734                 if (newIndex == -1)
 735                     newIndex = raw.length();
 736                 String path = raw.substring(index, newIndex);
 737                 if (path.equals(ext)) {
 738                     extDirIncluded = true;
 739                     break;
 740                 }
 741                 index = newIndex + 1;
 742             }
 743         }
 744         return extDirIncluded;
 745     }
 746 
 747 
 748     private static String doGetBootClassPathEntryForResource(
 749             String resourceName) {
 750         boolean retry = false;
 751         do {
 752             Bundle bundle = null;
 753             try {
 754                 bundle = getBundleForResource(resourceName);
 755                 if (bundle != null) {
 756                     File path = bundle.getJarPath();
 757                     boolean isExt = path.getParentFile().getName().equals("ext");
 758                     if (isExt && !extDirIsIncluded()) // this is a lib/ext jar, but
 759                         return null;                  // lib/ext isn't in the path
 760                     if (getBundleProperty(bundle.getName(), JAR_PATH_PROPERTY) == null) {
 761                         // if the bundle doesn't have its own JAR path, that means it's
 762                         // going to be merged into rt.jar.  If we already have the
 763                         // merged rt.jar, we can simply point to that.
 764                         Bundle merged = Bundle.getBundle("merged");
 765                         if (merged != null && merged.isInstalled()) {
 766                             File jar;
 767                             if (resourceName.endsWith(".class"))
 768                                 jar = merged.getJarPath();
 769                             else
 770                                 jar = new File(merged.getJarPath().getPath().replaceAll("merged-rt.jar",
 771                                         "merged-resources.jar"));
 772                             addEntryToBootClassPath(jar);
 773                             return jar.getPath();
 774                         }
 775                     }
 776                     if (!bundle.isInstalled()) {
 777                         bundle.queueDependencies(true);
 778                         log("On-demand downloading " +
 779                                 bundle.getName() + " for resource " +
 780                                 resourceName + "...");
 781                         bundle.install();
 782                         log(bundle + " install finished.");
 783                     }
 784                     log("Double-checking " + bundle + " state...");
 785                     if (!bundle.isInstalled()) {
 786                         throw new IllegalStateException("Expected state of " +
 787                                 bundle + " to be INSTALLED");
 788                     }
 789                     if (isExt) {
 790                         // don't add lib/ext entries to the boot class path, add
 791                         // them to the extension classloader instead
 792                         Launcher.addURLToExtClassLoader(path.toURL());
 793                         return null;
 794                     }
 795 
 796                     if ("javaws".equals(bundle.getName())) {
 797                         Launcher.addURLToAppClassLoader(path.toURL());
 798                         log("Returning null for javaws");
 799                         return null;
 800                     }
 801 
 802                     if ("core".equals(bundle.getName()))
 803                         return null;
 804 
 805                     // else add to boot class path
 806                     addEntryToBootClassPath(path);
 807 
 808                     return path.getPath();
 809                 }
 810                 return null; // not one of the JRE's classes
 811             }
 812             catch (Throwable e) {
 813                 retry = handleException(e);
 814                 log("Error downloading bundle for " +
 815                         resourceName + ":");
 816                 log(e);
 817                 if (e instanceof IOException) {
 818                     // bundle did not get installed correctly, remove incomplete
 819                     // bundle files
 820                     if (bundle != null) {
 821                         if (bundle.getJarPath() != null) {
 822                             File packTmp = new File(bundle.getJarPath() + ".pack");
 823                             packTmp.delete();
 824                             bundle.getJarPath().delete();
 825                         }
 826                         if (bundle.getLocalPath() != null) {
 827                             bundle.getLocalPath().delete();
 828                         }
 829                         bundle.setState(Bundle.NOT_DOWNLOADED);
 830                     }
 831                 }
 832             }
 833         } while (retry);
 834         sendErrorPing(ERROR_RETRY_CANCELLED); // bundle failed to install, user cancelled
 835 
 836         return null; // failed, user chose not to retry
 837     }
 838 
 839     static synchronized void sendErrorPing(int code) {
 840         try {
 841             File bundlePath;
 842             if (isWindowsVista()) {
 843                 bundlePath = getLocalLowTempBundlePath();
 844             } else {
 845                 bundlePath = getBundlePath();
 846             }
 847             File tmp = new File(bundlePath, "tmp");
 848             File errors = new File(tmp, "errors");
 849             String errorString = String.valueOf(code);
 850             if (errors.exists()) {
 851                 BufferedReader in = new BufferedReader(new FileReader(errors));
 852                 String line = in.readLine();
 853                 while (line != null) {
 854                     if (line.equals(errorString))
 855                         return; // we have already pinged this error
 856                     line = in.readLine();
 857                 }
 858             }
 859             tmp.mkdirs();
 860             Writer out = new FileWriter(errors, true);
 861             out.write(errorString + System.getProperty("line.separator"));
 862             out.close();
 863             postDownloadError(code);
 864         }
 865         catch (IOException e) {
 866             e.printStackTrace();
 867         }
 868     }
 869 
 870 
 871 
 872     /**
 873      * Displays an error dialog and prompts the user to retry or cancel.
 874      * Returns true if the user chose to retry, false if he chose to cancel.
 875      */
 876     static boolean handleException(Throwable e) {
 877         if (e instanceof IOException) {
 878             // I don't know of a better method to determine the root cause of
 879             // the exception, unfortunately...
 880             int code = ERROR_UNSPECIFIED;
 881             if (e.getMessage().indexOf("not enough space") != -1)
 882                 code = ERROR_DISK_FULL;
 883             return askUserToRetryDownloadOrQuit(code);
 884         }
 885         else
 886             return false;
 887     }
 888 
 889 
 890     static synchronized void flushBundleURLs() {
 891         bundleURLs = null;
 892     }
 893 
 894 
 895     static synchronized Properties getBundleURLs(boolean showUI)
 896             throws IOException {
 897         if (bundleURLs == null) {
 898             log("Entering DownloadManager.getBundleURLs");
 899             String base = getBaseDownloadURL();
 900             String url = appendTransactionId(base);
 901             // use PID instead of createTempFile or other random filename so as
 902             // to avoid dependencies on the random number generator libraries
 903             File bundlePath = null;
 904             // write temp file to locallow directory on vista
 905             if (isWindowsVista()) {
 906                 bundlePath = getLocalLowTempBundlePath();
 907             } else {
 908                 bundlePath = getBundlePath();
 909             }
 910             File tmp = new File(bundlePath, "urls." + getCurrentProcessId() +
 911                     ".properties");
 912             try {
 913                 log("Downloading from " + url + " to " + tmp);
 914                 downloadFromURL(url, tmp, "", showUI);
 915                 bundleURLs = new Properties();
 916                 if (tmp.exists()) {
 917                     addToTotalDownloadSize((int) tmp.length()); // better late than never
 918                     InputStream in = new FileInputStream(tmp);
 919                     in = new BufferedInputStream(in);
 920                     bundleURLs.load(in);
 921                     in.close();
 922                     if (bundleURLs.isEmpty()) {
 923                         fatalError(ERROR_MALFORMED_BUNDLE_PROPERTIES);
 924                     }
 925                 } else {
 926                     fatalError(ERROR_DOWNLOADING_BUNDLE_PROPERTIES);
 927                 }
 928             } finally {
 929                 // delete the temp file
 930                 if (!debug)
 931                     tmp.delete();
 932             }
 933             log("Leaving DownloadManager.getBundleURLs");
 934             // else an error occurred and user chose not to retry; leave
 935             // bundleURLs empty so we don't continually try to re-download it
 936         }
 937         return bundleURLs;
 938     }
 939 
 940     /**
 941      * Checks to see if the specified resource is part of a bundle, and if so
 942      * downloads it.  Returns either a string which should be added to the boot
 943      * class path (the newly-downloaded JAR's location), or null to indicate
 944      * that it isn't one of the JRE's resources or could not be downloaded.
 945      */
 946     public static String getBootClassPathEntryForResource(
 947             final String resourceName) {
 948         if (debug)
 949             log("Entering getBootClassPathEntryForResource(" + resourceName + ")");
 950         if (isJREComplete() || downloading == null ||
 951                 resourceName.startsWith("sun/jkernel")) {
 952             if (debug)
 953                 log("Bailing: " + isJREComplete() + ", " + (downloading == null));
 954             return null;
 955         }
 956         incrementDownloadCount();
 957         try {
 958             String result = (String) AccessController.doPrivileged(
 959                 new PrivilegedAction() {
 960                     public Object run() {
 961                         return (String) doGetBootClassPathEntryForResource(
 962                                 resourceName);
 963                     }
 964                 }
 965             );
 966             log("getBootClassPathEntryForResource(" + resourceName + ") == " + result);
 967             return result;
 968         }
 969         finally {
 970             decrementDownloadCount();
 971         }
 972     }
 973 
 974 
 975     /**
 976      * Called by the boot class loader when it encounters a class it can't find.
 977      * This method will check to see if the class is part of a bundle, and if so
 978      * download it.  Returns either a string which should be added to the boot
 979      * class path (the newly-downloaded JAR's location), or null to indicate
 980      * that it isn't one of the JRE's classes or could not be downloaded.
 981      */
 982     public static String getBootClassPathEntryForClass(final String className) {
 983         return getBootClassPathEntryForResource(className.replace('.', '/') +
 984                 ".class");
 985     }
 986 
 987 
 988     private static boolean doDownloadFile(String relativePath)
 989             throws IOException {
 990         Bundle bundle = getBundleForFile(relativePath);
 991         if (bundle != null) {
 992             bundle.queueDependencies(true);
 993             log("On-demand downloading " + bundle.getName() +
 994                     " for file " + relativePath + "...");
 995             bundle.install();
 996             return true;
 997         }
 998         return false;
 999     }
1000 
1001 
1002     /**
1003      * Locates the bundle for the specified JRE file (e.g. "bin/awt.dll") and
1004      * installs it.  Returns true if the file is indeed part of the JRE and has
1005      * now been installed, false if the file is not part of the JRE, and throws
1006      * an IOException if the file is part of the JRE but could not be
1007      * downloaded.
1008      */
1009     public static boolean downloadFile(final String relativePath)
1010             throws IOException {
1011         if (isJREComplete() || downloading == null)
1012             return false;
1013 
1014         incrementDownloadCount();
1015         try {
1016             Object result =
1017                     AccessController.doPrivileged(new PrivilegedAction() {
1018                 public Object run() {
1019                     File path = new File(JAVA_HOME,
1020                             relativePath.replace('/', File.separatorChar));
1021                     if (path.exists())
1022                         return true;
1023                     try {
1024                         return new Boolean(doDownloadFile(relativePath));
1025                     }
1026                     catch (IOException e) {
1027                         return e;
1028                     }
1029                 }
1030             });
1031             if (result instanceof Boolean)
1032                 return ((Boolean) result).booleanValue();
1033             else
1034                 throw (IOException) result;
1035         }
1036         finally {
1037             decrementDownloadCount();
1038         }
1039     }
1040 
1041 
1042     // increments the counter that tracks whether the current thread is involved
1043     // in any download-related activities.  A non-zero count indicates that the
1044     // thread is currently downloading or installing a bundle.
1045     static void incrementDownloadCount() {
1046         downloading.set(downloading.get() + 1);
1047     }
1048 
1049 
1050     // increments the counter that tracks whether the current thread is involved
1051     // in any download-related activities.  A non-zero count indicates that the
1052     // thread is currently downloading or installing a bundle.
1053     static void decrementDownloadCount() {
1054         // will generate an exception if incrementDownloadCount() hasn't been
1055         // called first, this is intentional
1056         downloading.set(downloading.get() - 1);
1057     }
1058 
1059 
1060     /**
1061      * Returns <code>true</code> if the current thread is in the process of
1062      * downloading a bundle.  This is called by DownloadManager.loadLibrary()
1063      * that is called by System.loadLibrary(), so
1064      * that when we run into a library required by the download process itself,
1065      * we don't call back into DownloadManager in an attempt to download it
1066      * (which would lead to infinite recursion).
1067      *
1068      * All classes and libraries required to download classes must by
1069      * definition already be present.  So if this method returns true, we are
1070      * currently in the middle of performing a download, and the class or
1071      * library load must be happening due to the download itself.  We can
1072      * immediately abort such requests -- the class or library should already
1073      * be present.  If it isn't, we're not going to be able to download it,
1074      * since we have just established that it is required to perform a
1075      * download, and we might as well just let the NoClassDefFoundError /
1076      * UnsatisfiedLinkError occur.
1077      */
1078     public static boolean isCurrentThreadDownloading() {
1079         return downloading != null ? downloading.get() > 0 : false;
1080     }
1081 
1082 
1083     /**
1084      * Returns true if everything is downloaded and the JRE has been
1085      * reconstructed.  Also returns true if kernel functionality is disabled
1086      * for any other reason.
1087      */
1088     public static boolean isJREComplete() {
1089         return complete;
1090     }
1091 
1092 
1093     // called by BackgroundDownloader
1094     static void doBackgroundDownloads(boolean showProgress) {
1095         if (!complete) {
1096             if (!showProgress && !debug)
1097                 reportErrors = false;
1098             try {
1099                 // install swing first for ergonomic reasons
1100                 Bundle swing = Bundle.getBundle("javax_swing_core");
1101                 if (!swing.isInstalled())
1102                     swing.install(showProgress, false, false);
1103                 // install remaining bundles
1104                 for (String name : getCriticalBundleNames()) {
1105                     Bundle bundle = Bundle.getBundle(name);
1106                     if (!bundle.isInstalled()) {
1107                         bundle.install(showProgress, false, true);
1108                     }
1109                 }
1110                 shutdown();
1111             }
1112             catch (IOException e) {
1113                 log(e);
1114             }
1115         }
1116     }
1117 
1118     // copy receipt file to destination path specified
1119     static void copyReceiptFile(File from, File to) throws IOException {
1120         DataInputStream in = new DataInputStream(
1121                 new BufferedInputStream(new FileInputStream(from)));
1122         OutputStream out = new FileOutputStream(to);
1123         String line = in.readLine();
1124         while (line != null) {
1125             out.write((line + '\n').getBytes("utf-8"));
1126             line = in.readLine();
1127         }
1128         in.close();
1129         out.close();
1130     }
1131 
1132 
1133     private static void downloadRequestedBundles() {
1134         log("Checking for requested bundles...");
1135         try {
1136             File list = new File(JAVA_HOME, REQUESTED_BUNDLES_PATH);
1137             if (list.exists()) {
1138                 FileInputStream in = new FileInputStream(list);
1139                 ByteArrayOutputStream buffer = new ByteArrayOutputStream();
1140                 send(in, buffer);
1141                 in.close();
1142 
1143                 // split string manually to avoid relying on regexes or
1144                 // StringTokenizer
1145                 String raw = new String(buffer.toByteArray(), "utf-8");
1146                 List/*<String>*/ bundles = new ArrayList/*<String>*/();
1147                 StringBuilder token = new StringBuilder();
1148                 for (int i = 0; i < raw.length(); i++) {
1149                     char c = raw.charAt(i);
1150                     if (c == ',' || Character.isWhitespace(c)) {
1151                         if (token.length() > 0) {
1152                             bundles.add(token.toString());
1153                             token.setLength(0);
1154                         }
1155                     }
1156                     else
1157                         token.append(c);
1158                 }
1159                 if (token.length() > 0)
1160                     bundles.add(token.toString());
1161                 log("Requested bundles: " + bundles);
1162                 for (int i = 0; i < bundles.size(); i++) {
1163                     Bundle bundle = Bundle.getBundle((String) bundles.get(i));
1164                     if (bundle != null && !bundle.isInstalled()) {
1165                         log("Downloading " + bundle + " due to requested.list");
1166                         bundle.install(true, false, false);
1167                     }
1168                 }
1169             }
1170         }
1171         catch (IOException e) {
1172             log(e);
1173         }
1174     }
1175 
1176 
1177     static void fatalError(int code) {
1178         fatalError(code, null);
1179     }
1180 
1181 
1182     /**
1183      * Called to cleanly shut down the VM when a fatal download error has
1184      * occurred.  Calls System.exit() if outside of the Java Plug-In, otherwise
1185      * throws an error.
1186      */
1187     static void fatalError(int code, String arg) {
1188         sendErrorPing(code);
1189 
1190         for (int i = 0; i < Bundle.THREADS; i++)
1191             bundleInstallComplete();
1192         if (reportErrors)
1193             displayError(code, arg);
1194         // inPlugIn check isn't 100% reliable but should be close enough.
1195         // headless is for the browser side of things in the out-of-process
1196         // plug-in
1197         boolean inPlugIn = (Boolean.getBoolean("java.awt.headless") ||
1198            System.getProperty("javaplugin.version") != null);
1199         KernelError error = new KernelError("Java Kernel bundle download failed");
1200         if (inPlugIn)
1201             throw error;
1202         else {
1203             log(error);
1204             System.exit(1);
1205         }
1206     }
1207 
1208 
1209     // start the background download process using the jbroker broker process
1210     // the method will first launch the broker process, if it is not already
1211     // running
1212     // it will then send the command necessary to start the background download
1213     // process to the broker process
1214     private static void startBackgroundDownloadWithBroker() {
1215 
1216         if (!BackgroundDownloader.getBackgroundDownloadProperty()) {
1217             // If getBackgroundDownloadProperty() returns false
1218             // we're doing the downloads from this VM; we don't want to
1219             // spawn another one
1220             return;
1221         }
1222 
1223         // launch broker process if necessary
1224         if (!launchBrokerProcess()) {
1225             return;
1226         }
1227 
1228 
1229         String kernelDownloadURLProperty = getBaseDownloadURL();
1230 
1231         String kernelDownloadURL;
1232 
1233         // only set KERNEL_DOWNLOAD_URL_PROPERTY if we override
1234         // the default download url
1235         if (kernelDownloadURLProperty == null ||
1236                 kernelDownloadURLProperty.equals(DEFAULT_DOWNLOAD_URL)) {
1237             kernelDownloadURL = " ";
1238         } else {
1239             kernelDownloadURL = kernelDownloadURLProperty;
1240         }
1241 
1242         startBackgroundDownloadWithBrokerImpl(kernelDownloadURLProperty);
1243     }
1244 
1245     private static void startBackgroundDownloads() {
1246         if (!complete) {
1247             if (BackgroundDownloader.getBackgroundMutex().acquire(0)) {
1248                 // we don't actually need to hold the mutex -- it was just a
1249                 // quick check to see if there is any point in even attempting
1250                 // to start the background downloader
1251                 BackgroundDownloader.getBackgroundMutex().release();
1252                 if (isWindowsVista()) {
1253                     // use broker process to start background download
1254                     // at high integrity
1255                     startBackgroundDownloadWithBroker();
1256                 } else {
1257                     BackgroundDownloader.startBackgroundDownloads();
1258                 }
1259             }
1260         }
1261     }
1262 
1263 
1264     /**
1265      * Increases the total download size displayed in the download progress
1266      * dialog.
1267      */
1268     static native void addToTotalDownloadSize(int size);
1269 
1270 
1271     /**
1272      * Displays a progress dialog while downloading from the specified URL.
1273      *
1274      *@param url the URL string from which to download
1275      *@param file the destination path
1276      *@param name the user-visible name of the component we are downloading
1277      */
1278     static void downloadFromURL(String url, File file, String name,
1279             boolean showProgress) {
1280         // do not show download dialog if kernel.download.dialog is false
1281         downloadFromURLImpl(url, file, name,
1282                 disableDownloadDialog ? false : showProgress);
1283     }
1284 
1285     private static native void downloadFromURLImpl(String url, File file,
1286             String name, boolean showProgress);
1287 
1288     // This is for testing purposes only - allows to specify URL
1289     // to download kernel bundles from through the registry key.
1290     static native String getUrlFromRegistry();
1291 
1292     static native String getVisitorId0();
1293 
1294     static native void postDownloadComplete();
1295 
1296     static native void postDownloadError(int code);
1297 
1298     // Returns the visitor ID set by the installer, will be sent to the server
1299     // during bundle downloads for logging purposes.
1300     static synchronized String getVisitorId() {
1301         if (!visitorIdDetermined) {
1302             visitorIdDetermined = true;
1303             visitorId = getVisitorId0();
1304         }
1305         return visitorId;
1306     }
1307 
1308     // display an error message using a native dialog
1309     public static native void displayError(int code, String arg);
1310 
1311     // prompt user whether to retry download, or quit
1312     // returns true if the user chose to retry
1313     public static native boolean askUserToRetryDownloadOrQuit(int code);
1314 
1315     // returns true if we are running Windows Vista; false otherwise
1316     static native boolean isWindowsVista();
1317 
1318     private static native void startBackgroundDownloadWithBrokerImpl(
1319             String command);
1320 
1321     private static int isJBrokerStarted() {
1322         if (_isJBrokerStarted == -1) {
1323             // initialize state of jbroker
1324             _isJBrokerStarted = isJBrokerRunning() ? 1 : 0;
1325         }
1326         return _isJBrokerStarted;
1327     }
1328 
1329     // returns true if broker process (jbroker) is running; false otherwise
1330     private static native boolean isJBrokerRunning();
1331 
1332     // returns true if we are running in IE protected mode; false otherwise
1333     private static native boolean isIEProtectedMode();
1334 
1335     private static native boolean launchJBroker(String jbrokerPath);
1336 
1337     static native void bundleInstallStart();
1338 
1339     static native void bundleInstallComplete();
1340 
1341     private static native boolean moveFileWithBrokerImpl(String fromPath,
1342             String userHome);
1343 
1344     private static native boolean moveDirWithBrokerImpl(String fromPath,
1345             String userHome);
1346 
1347     static boolean moveFileWithBroker(String fromPath) {
1348         // launch jbroker if necessary
1349         if (!launchBrokerProcess()) {
1350             return false;
1351         }
1352 
1353         return moveFileWithBrokerImpl(fromPath, USER_HOME);
1354     }
1355 
1356     static boolean moveDirWithBroker(String fromPath) {
1357         // launch jbroker if necessary
1358         if (!launchBrokerProcess()) {
1359             return false;
1360         }
1361 
1362         return moveDirWithBrokerImpl(fromPath, USER_HOME);
1363     }
1364 
1365     private static synchronized boolean launchBrokerProcess() {
1366         // launch jbroker if necessary
1367         if (isJBrokerStarted() == 0) {
1368             // launch jbroker if needed
1369             boolean ret = launchJBroker(JAVA_HOME);
1370             // set state of jbroker
1371             _isJBrokerStarted = ret ? 1 : 0;
1372             return ret;
1373         }
1374         return true;
1375     }
1376 
1377     private static class StreamMonitor implements Runnable {
1378         private InputStream istream;
1379         public StreamMonitor(InputStream stream) {
1380             istream = new BufferedInputStream(stream);
1381             new Thread(this).start();
1382         }
1383         public void run() {
1384             byte[] buffer = new byte[4096];
1385             try {
1386                 int ret = istream.read(buffer);
1387                 while (ret != -1) {
1388                     ret = istream.read(buffer);
1389                 }
1390             } catch (IOException e) {
1391                 try {
1392                     istream.close();
1393                 } catch (IOException e2) {
1394                 } // Should allow clean exit when process shuts down
1395             }
1396         }
1397     }
1398 
1399 
1400     /** Copy a file tree, excluding certain named files. */
1401     private static void copyAll(File src, File dest, Set/*<String>*/ excludes)
1402                             throws IOException {
1403         if (!excludes.contains(src.getName())) {
1404             if (src.isDirectory()) {
1405                 File[] children = src.listFiles();
1406                 if (children != null) {
1407                     for (int i = 0; i < children.length; i++)
1408                         copyAll(children[i],
1409                                 new File(dest, children[i].getName()),
1410                                 excludes);
1411                 }
1412             }
1413             else {
1414                 dest.getParentFile().mkdirs();
1415                 FileInputStream in = new FileInputStream(src);
1416                 FileOutputStream out = new FileOutputStream(dest);
1417                 send(in, out);
1418                 in.close();
1419                 out.close();
1420             }
1421         }
1422     }
1423 
1424 
1425     public static void dumpOutput(final Process p) {
1426         Thread outputReader = new Thread("outputReader") {
1427             public void run() {
1428                 try {
1429                     InputStream in = p.getInputStream();
1430                     DownloadManager.send(in, System.out);
1431                 } catch (IOException e) {
1432                     log(e);
1433                 }
1434             }
1435         };
1436         outputReader.start();
1437         Thread errorReader = new Thread("errorReader") {
1438             public void run() {
1439                 try {
1440                     InputStream in = p.getErrorStream();
1441                     DownloadManager.send(in, System.err);
1442                 } catch (IOException e) {
1443                     log(e);
1444                 }
1445             }
1446         };
1447         errorReader.start();
1448     }
1449 
1450 
1451     /**
1452      * Creates the merged rt.jar and resources.jar files.
1453      */
1454     private static void createMergedJars() {
1455         log("DownloadManager.createMergedJars");
1456         File bundlePath;
1457         if (isWindowsVista()) {
1458             bundlePath = getLocalLowTempBundlePath();
1459         } else {
1460             bundlePath = getBundlePath();
1461         }
1462         File tmp = new File(bundlePath, "tmp");
1463         // explicitly check the final location, not the (potentially) local-low
1464         // location -- a local-low finished isn't good enough to call it done
1465         if (new File(getBundlePath(), "tmp" + File.separator + "finished").exists())
1466             return; // already done
1467         log("DownloadManager.createMergedJars: running");
1468         tmp.mkdirs();
1469         boolean retry = false;
1470         do {
1471             try {
1472                 Bundle.getBundle("merged").install(false, false, true);
1473                 postDownloadComplete();
1474                 // done, write an empty "finished" file to flag completion
1475                 File finished = new File(tmp, "finished");
1476                 new FileOutputStream(finished).close();
1477                 if (isWindowsVista()) {
1478                     if (!moveFileWithBroker(getKernelJREDir() +
1479                             "-bundles\\tmp\\finished")) {
1480                         throw new IOException("unable to create 'finished' file");
1481                     }
1482                 }
1483                 log("DownloadManager.createMergedJars: created " + finished);
1484                 // next JRE startup will move these files into their final
1485                 // locations, as long as no other JREs are running
1486 
1487                 // clean up the local low bundle directory on vista
1488                 if (isWindowsVista()) {
1489                     File tmpDir = getLocalLowTempBundlePath();
1490                     File[] list = tmpDir.listFiles();
1491                     if (list != null) {
1492                         for (int i = 0; i < list.length; i++) {
1493                             list[i].delete();
1494                         }
1495                     }
1496                     tmpDir.delete();
1497                     log("Finished cleanup, " + tmpDir + ".exists(): " + tmpDir.exists());
1498                 }
1499             }
1500             catch (IOException e) {
1501                 log(e);
1502             }
1503         }
1504         while (retry);
1505         log("DownloadManager.createMergedJars: finished");
1506     }
1507 
1508 
1509     private static void shutdown() {
1510         try {
1511             ExecutorService e = Bundle.getThreadPool();
1512             e.shutdown();
1513             e.awaitTermination(60 * 60 * 24, TimeUnit.SECONDS);
1514         }
1515         catch (InterruptedException e) {
1516         }
1517     }
1518 
1519 
1520     // returns the registry key for kernel.debug
1521     static native boolean getDebugKey();
1522 
1523 
1524     // returns the final value for the kernel debug property
1525     public static boolean getDebugProperty(){
1526          /*
1527           * Check registry key value
1528           */
1529          boolean debugEnabled = getDebugKey();
1530 
1531          /*
1532           * Check system property - it should override the registry
1533           * key value.
1534           */
1535          if (System.getProperty(KERNEL_DEBUG_PROPERTY) != null) {
1536              debugEnabled = Boolean.valueOf(
1537                       System.getProperty(KERNEL_DEBUG_PROPERTY));
1538          }
1539          return debugEnabled;
1540 
1541     }
1542 
1543 
1544     /**
1545      * Outputs to the error stream even when System.err has not yet been
1546      * initialized.
1547      */
1548     static void println(String msg) {
1549         if (System.err != null)
1550             System.err.println(msg);
1551         else {
1552             try {
1553                 if (errorStream == null)
1554                     errorStream = new FileOutputStream(FileDescriptor.err);
1555                 errorStream.write((msg +
1556                         System.getProperty("line.separator")).getBytes("utf-8"));
1557             }
1558             catch (IOException e) {
1559                 throw new RuntimeException(e);
1560             }
1561         }
1562     }
1563 
1564 
1565     static void log(String msg) {
1566         if (debug) {
1567             println(msg);
1568             try {
1569                 if (logStream == null) {
1570                     loadJKernelLibrary();
1571                     File path = isWindowsVista() ? getLocalLowTempBundlePath() :
1572                             getBundlePath();
1573                     path = new File(path, "kernel." + getCurrentProcessId() + ".log");
1574                     logStream = new FileOutputStream(path);
1575                 }
1576                 logStream.write((msg +
1577                         System.getProperty("line.separator")).getBytes("utf-8"));
1578                 logStream.flush();
1579             }
1580             catch (IOException e) {
1581                 // ignore
1582             }
1583         }
1584     }
1585 
1586 
1587     static void log(Throwable e) {
1588         ByteArrayOutputStream buffer = new ByteArrayOutputStream();
1589         PrintStream p = new PrintStream(buffer);
1590         e.printStackTrace(p);
1591         p.close();
1592         log(buffer.toString(0));
1593     }
1594 
1595 
1596     /** Dump the contents of a map to System.out. */
1597     private static void printMap(Map/*<String, String>*/ map) {
1598         int size = 0;
1599         Set<Integer> identityHashes = new HashSet<Integer>();
1600         Iterator/*<Map.Entry<String, String>>*/ i = map.entrySet().iterator();
1601         while (i.hasNext()) {
1602             Map.Entry/*<String, String>*/ e = (Map.Entry) i.next();
1603             String key = (String) e.getKey();
1604             String value = (String) e.getValue();
1605             System.out.println(key + ": " + value);
1606             Integer keyHash = Integer.valueOf(System.identityHashCode(key));
1607             if (!identityHashes.contains(keyHash)) {
1608                 identityHashes.add(keyHash);
1609                 size += key.length();
1610             }
1611             Integer valueHash = Integer.valueOf(System.identityHashCode(value));
1612             if (!identityHashes.contains(valueHash)) {
1613                 identityHashes.add(valueHash);
1614                 size += value.length();
1615             }
1616         }
1617         System.out.println(size + " bytes");
1618     }
1619 
1620 
1621     /** Process the "-dumpmaps" command-line argument. */
1622     private static void dumpMaps() throws IOException {
1623         System.out.println("Resources:");
1624         System.out.println("----------");
1625         printMap(getResourceMap());
1626         System.out.println();
1627         System.out.println("Files:");
1628         System.out.println("----------");
1629         printMap(getFileMap());
1630     }
1631 
1632 
1633     /** Process the "-download" command-line argument. */
1634     private static void processDownload(String bundleName) throws IOException {
1635         if (bundleName.equals("all")) {
1636             debug = true;
1637             doBackgroundDownloads(true);
1638             performCompletionIfNeeded();
1639         }
1640         else {
1641             Bundle bundle = Bundle.getBundle(bundleName);
1642             if (bundle == null) {
1643                 println("Unknown bundle: " + bundleName);
1644                 System.exit(1);
1645             }
1646             else
1647                 bundle.install();
1648         }
1649     }
1650 
1651 
1652     static native int getCurrentProcessId();
1653 
1654     private DownloadManager() {
1655     }
1656 
1657     // Invoked by jkernel VM after the VM is initialized
1658     static void setBootClassLoaderHook() {
1659         if (!isJREComplete()) {
1660             sun.misc.BootClassLoaderHook.setHook(new DownloadManager());
1661         }
1662     }
1663 
1664     // Implementation of the BootClassLoaderHook interface
1665     public String loadBootstrapClass(String name) {
1666         // Check for download before we look for it.  If
1667         // DownloadManager ends up downloading it, it will add it to
1668         // our search path before we proceed to the findClass().
1669         return DownloadManager.getBootClassPathEntryForClass(name);
1670     }
1671 
1672     public boolean loadLibrary(String name) {
1673        try {
1674             if (!DownloadManager.isJREComplete() &&
1675                     !DownloadManager.isCurrentThreadDownloading()) {
1676                 return DownloadManager.downloadFile("bin/" +
1677                     System.mapLibraryName(name));
1678                 // it doesn't matter if the downloadFile call returns false --
1679                 // it probably just means that this is a user library, as
1680                 // opposed to a JRE library
1681             }
1682         } catch (IOException e) {
1683             throw new UnsatisfiedLinkError("Error downloading library " +
1684                                                 name + ": " + e);
1685         } catch (NoClassDefFoundError e) {
1686             // This happens while Java itself is being compiled; DownloadManager
1687             // isn't accessible when this code is first invoked.  It isn't an
1688             // issue, as if we can't find DownloadManager, we can safely assume
1689             // that additional code is not available for download.
1690         }
1691         return false;
1692     }
1693 
1694     public boolean prefetchFile(String name) {
1695         try {
1696             return sun.jkernel.DownloadManager.downloadFile(name);
1697         } catch (IOException ioe) {
1698             return false;
1699         }
1700     }
1701 
1702     public String getBootstrapResource(String name) {
1703         try {
1704             // If this is a known JRE resource, ensure that its bundle is
1705             // downloaded.  If it isn't known, we just ignore the download
1706             // failure and check to see if we can find the resource anyway
1707             // (which is possible if the boot class path has been modified).
1708             return DownloadManager.getBootClassPathEntryForResource(name);
1709         } catch (NoClassDefFoundError e) {
1710             // This happens while Java itself is being compiled; DownloadManager
1711             // isn't accessible when this code is first invoked.  It isn't an
1712             // issue, as if we can't find DownloadManager, we can safely assume
1713             // that additional code is not available for download.
1714             return null;
1715         }
1716     }
1717 
1718     public URLClassPath getBootstrapClassPath(URLClassPath bcp,
1719                                               URLStreamHandlerFactory factory)
1720     {
1721         return DownloadManager.getBootClassPath(bcp, factory);
1722     }
1723 
1724     public boolean isCurrentThreadPrefetching() {
1725         return DownloadManager.isCurrentThreadDownloading();
1726     }
1727 
1728     public static void main(String[] arg) throws Exception {
1729         AccessController.checkPermission(new AllPermission());
1730 
1731         boolean valid = false;
1732         if (arg.length == 2 && arg[0].equals("-install")) {
1733             valid = true;
1734             Bundle bundle = new Bundle() {
1735                 protected void updateState() {
1736                     // the bundle path was provided on the command line, so we
1737                     // just claim it has already been "downloaded" to the local
1738                     // filesystem
1739                     state = DOWNLOADED;
1740                 }
1741             };
1742 
1743             File jarPath;
1744             int index = 0;
1745             do {
1746                 index++;
1747                 jarPath = new File(getBundlePath(),
1748                         CUSTOM_PREFIX + index + ".jar");
1749             }
1750             while (jarPath.exists());
1751             bundle.setName(CUSTOM_PREFIX + index);
1752             bundle.setLocalPath(new File(arg[1]));
1753             bundle.setJarPath(jarPath);
1754             bundle.setDeleteOnInstall(false);
1755             bundle.install();
1756         }
1757         else if (arg.length == 2 && arg[0].equals("-download")) {
1758             valid = true;
1759             processDownload(arg[1]);
1760         }
1761         else if (arg.length == 1 && arg[0].equals("-dumpmaps")) {
1762             valid = true;
1763             dumpMaps();
1764         }
1765         else if (arg.length == 2 && arg[0].equals("-sha1")) {
1766             valid = true;
1767             System.out.println(BundleCheck.getInstance(new File(arg[1])));
1768         }
1769         else if (arg.length == 1 && arg[0].equals("-downloadtest")) {
1770             valid = true;
1771             File file = File.createTempFile("download", ".test");
1772             for (;;) {
1773                 file.delete();
1774                 downloadFromURL(getBaseDownloadURL(), file, "URLS", true);
1775                 System.out.println("Downloaded " + file.length() + " bytes");
1776             }
1777         }
1778         if (!valid) {
1779             System.out.println("usage: DownloadManager -install <path>.zip |");
1780             System.out.println("       DownloadManager -download " +
1781                     "<bundle_name> |");
1782             System.out.println("       DownloadManager -dumpmaps");
1783             System.exit(1);
1784         }
1785     }
1786 }