From 883954c3e5fe20e3834cfb3118cc2fcd2a104357 Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Mon, 17 Sep 2018 13:08:25 -0700 Subject: [PATCH] Consolidate & enhance "environment config" handling. The new code is more careful about how it obtains config info, and issues warnings when sources of info conflict. It does not abort in such circumstances as Getdown's prime directive is to do the best it can to get the app updated and launched. But the warnings will hopefully allow accidental conflicts to be detected and avoided before being deployed to customers. We also now allow bootstrap.properties to provide an appid; that matches what was possible via system properties and command line arguments. We also avoid "communicating" environment configuration through the Java system properties mechanism. We now read from sysprops but do not write back to them. Lastly, this includes some name tidying (like appdir -> appDir and getAppdir -> getAppDir). --- .../main/java/com/threerings/getdown/Log.java | 8 + .../getdown/classpath/ClassPaths.java | 2 +- .../threerings/getdown/data/Application.java | 79 ++----- .../threerings/getdown/data/EnvConfig.java | 207 ++++++++++++++++++ .../com/threerings/getdown/data/SysProps.java | 4 +- .../com/threerings/getdown/tools/Differ.java | 5 +- .../threerings/getdown/tools/Digester.java | 3 +- .../com/threerings/getdown/tools/JarDiff.java | 4 +- .../getdown/classpath/ClassPathsTest.java | 2 +- .../threerings/getdown/launcher/Getdown.java | 27 +-- .../getdown/launcher/GetdownApp.java | 104 ++------- 11 files changed, 277 insertions(+), 168 deletions(-) create mode 100644 core/src/main/java/com/threerings/getdown/data/EnvConfig.java diff --git a/core/src/main/java/com/threerings/getdown/Log.java b/core/src/main/java/com/threerings/getdown/Log.java index 7538ea3..a953f5d 100644 --- a/core/src/main/java/com/threerings/getdown/Log.java +++ b/core/src/main/java/com/threerings/getdown/Log.java @@ -42,6 +42,14 @@ public class Log */ public void warning (Object message, Object... args) { doLog(2, message, args); } + /** + * Logs an error message. + * + * @param message the message to be logged. + * @param args a list of key/value pairs and an optional final Throwable. + */ + public void error (Object message, Object... args) { doLog(3, message, args); } + protected void doLog (int levIdx, Object message, Object[] args) { if (_impl.isLoggable(LEVELS[levIdx])) { Throwable err = null; diff --git a/core/src/main/java/com/threerings/getdown/classpath/ClassPaths.java b/core/src/main/java/com/threerings/getdown/classpath/ClassPaths.java index f7ce8e7..a0dcf7b 100644 --- a/core/src/main/java/com/threerings/getdown/classpath/ClassPaths.java +++ b/core/src/main/java/com/threerings/getdown/classpath/ClassPaths.java @@ -48,7 +48,7 @@ public class ClassPaths */ public static ClassPath buildCachedClassPath (Application app) throws IOException { - File cacheDir = new File(app.getAppdir(), CACHE_DIR); + File cacheDir = new File(app.getAppDir(), CACHE_DIR); // a negative value of code_cache_retention_days allows to clean up the cache forcefully if (app.getCodeCacheRetentionDays() <= 0) { runGarbageCollection(app, cacheDir); diff --git a/core/src/main/java/com/threerings/getdown/data/Application.java b/core/src/main/java/com/threerings/getdown/data/Application.java index 54f4d54..02a29d4 100644 --- a/core/src/main/java/com/threerings/getdown/data/Application.java +++ b/core/src/main/java/com/threerings/getdown/data/Application.java @@ -233,45 +233,21 @@ public class Application } } - /** - * Creates an application instance with no signers. - * - * @see #Application(File, String, List, String[], String[]) - */ - public Application (File appdir, String appid) - { - this(appdir, appid, null, null, null); - } - /** * Creates an application instance which records the location of the getdown.txt * configuration file from the supplied application directory. * - * @param appid usually null but a string identifier if a secondary application is desired to - * be launched. That application will use {@code appid.class} and {@code appid.apparg} to - * configure itself but all other parameters will be the same as the primary application. - * @param signers a list of possible signers of this application. Used to verify the digest. - * @param jvmargs additional arguments to pass on to launched jvms. - * @param appargs additional arguments to pass on to launched application; these will be added - * after the args in the getdown.txt file. */ - public Application (File appdir, String appid, List signers, - String[] jvmargs, String[] appargs) - { - _appdir = appdir; - _appid = appid; - _signers = (signers == null) ? Collections.emptyList() : signers; - _config = getLocalPath(appdir, CONFIG_FILE); - _extraJvmArgs = (jvmargs == null) ? EMPTY_STRING_ARRAY : jvmargs; - _extraAppArgs = (appargs == null) ? EMPTY_STRING_ARRAY : appargs; + public Application (EnvConfig envc) { + _envc = envc; + _config = getLocalPath(envc.appDir, CONFIG_FILE); } /** * Returns the configured application directory. */ - public File getAppdir() - { - return _appdir; + public File getAppDir () { + return _envc.appDir; } /** @@ -550,7 +526,7 @@ public class Application } // otherwise, issue a warning that we found no getdown file else { - log.info("Found no getdown.txt file", "appdir", _appdir); + log.info("Found no getdown.txt file", "appdir", getAppDir()); } } catch (Exception e) { log.warning("Failure reading config file", "file", config, e); @@ -559,8 +535,8 @@ public class Application // if we failed to read our config file, check for an appbase specified via a system // property; we can use that to bootstrap ourselves back into operation if (config == null) { - String appbase = SysProps.appBase(); - log.info("Attempting to obtain 'appbase' from system property", "appbase", appbase); + String appbase = _envc.appBase; + log.info("Using 'appbase' from bootstrap config", "appbase", appbase); Map cdata = new HashMap<>(); cdata.put("appbase", appbase); config = new Config(cdata); @@ -607,7 +583,7 @@ public class Application } } - String appPrefix = StringUtil.isBlank(_appid) ? "" : (_appid + "."); + String appPrefix = _envc.appId == null ? "" : (_envc.appId + "."); // determine our application class name (use app-specific class _if_ one is provided) _class = config.getString("class"); @@ -708,9 +684,6 @@ public class Application addAll(jvmargs, _jvmargs); } - // Add the launch specific JVM arguments - addAll(_extraJvmArgs, _jvmargs); - // get the set of optimum JVM arguments _optimumJvmArgs = config.getMultiValue("optimum_jvmarg"); @@ -719,7 +692,7 @@ public class Application addAll(appargs, _appargs); // add the launch specific application arguments - addAll(_extraAppArgs, _appargs); + _appargs.addAll(_envc.appArgs); // look for custom arguments fillAssignmentListFromPairs("extra.txt", _txtJvmArgs); @@ -783,7 +756,7 @@ public class Application */ public File getLocalPath (String path) { - return getLocalPath(_appdir, path); + return getLocalPath(getAppDir(), path); } /** @@ -806,7 +779,7 @@ public class Application // if we have an unpacked VM, check the 'release' file for its version Resource vmjar = getJavaVMResource(); if (vmjar != null && vmjar.isMarkedValid()) { - File vmdir = new File(_appdir, LaunchUtil.LOCAL_JAVA_DIR); + File vmdir = new File(getAppDir(), LaunchUtil.LOCAL_JAVA_DIR); File relfile = new File(vmdir, "release"); if (!relfile.exists()) { log.warning("Unpacked JVM missing 'release' file. Assuming valid version."); @@ -925,7 +898,7 @@ public class Application ArrayList args = new ArrayList<>(); // reconstruct the path to the JVM - args.add(LaunchUtil.getJVMPath(_appdir, _windebug || optimum)); + args.add(LaunchUtil.getJVMPath(getAppDir(), _windebug || optimum)); // check whether we're using -jar mode or -classpath mode boolean dashJarMode = MANIFEST_CLASS.equals(_class); @@ -999,7 +972,7 @@ public class Application String[] sargs = args.toArray(new String[args.size()]); log.info("Running " + StringUtil.join(sargs, "\n ")); - return Runtime.getRuntime().exec(sargs, envp, _appdir); + return Runtime.getRuntime().exec(sargs, envp, getAppDir()); } /** @@ -1095,7 +1068,7 @@ public class Application /** Replaces the application directory and version in any argument. */ protected String processArg (String arg) { - arg = arg.replace("%APPDIR%", _appdir.getAbsolutePath()); + arg = arg.replace("%APPDIR%", getAppDir().getAbsolutePath()); arg = arg.replace("%VERSION%", String.valueOf(_version)); // if this argument contains %ENV.FOO% replace those with the associated values looked up @@ -1135,7 +1108,7 @@ public class Application // this will read in the contents of the digest file and validate itself try { - _digest = new Digest(_appdir, _strictComments); + _digest = new Digest(getAppDir(), _strictComments); } catch (IOException ioe) { log.info("Failed to load digest: " + ioe.getMessage() + ". Attempting recovery..."); } @@ -1149,7 +1122,7 @@ public class Application try { status.updateStatus("m.checking"); downloadDigestFiles(); - _digest = new Digest(_appdir, _strictComments); + _digest = new Digest(getAppDir(), _strictComments); if (!olddig.equals(_digest.getMetaDigest())) { log.info("Unversioned digest changed. Revalidating..."); status.updateStatus("m.validating"); @@ -1167,7 +1140,7 @@ public class Application if (_digest == null) { status.updateStatus("m.updating_metadata"); downloadDigestFiles(); - _digest = new Digest(_appdir, _strictComments); + _digest = new Digest(getAppDir(), _strictComments); } // now verify the contents of our main config file @@ -1178,7 +1151,7 @@ public class Application // caller because there's nothing we can do to automatically recover downloadConfigFile(); downloadDigestFiles(); - _digest = new Digest(_appdir, _strictComments); + _digest = new Digest(getAppDir(), _strictComments); // revalidate everything if we end up downloading new metadata clearValidationMarkers(); // if the new copy validates, reinitialize ourselves; otherwise report baffling hoseage @@ -1509,8 +1482,8 @@ public class Application File target = downloadFile(path); if (sigVersion > 0) { - if (_signers.isEmpty()) { - log.info("No signers, not verifying file", "path", path); + if (_envc.certs.isEmpty()) { + log.info("No signing certs, not verifying digest.txt", "path", path); } else { File signatureFile = downloadFile(path + SIGNATURE_SUFFIX); @@ -1523,7 +1496,7 @@ public class Application byte[] buffer = new byte[8192]; int length, validated = 0; - for (Certificate cert : _signers) { + for (Certificate cert : _envc.certs) { try (FileInputStream dataInput = new FileInputStream(target)) { Signature sig = Signature.getInstance(Digest.sigAlgorithm(sigVersion)); sig.initVerify(cert); @@ -1705,8 +1678,7 @@ public class Application return new File(appdir, path); } - protected File _appdir; - protected String _appid; + protected final EnvConfig _envc; protected File _config; protected Digest _digest; @@ -1749,15 +1721,10 @@ public class Application protected List _jvmargs = new ArrayList<>(); protected List _appargs = new ArrayList<>(); - protected String[] _extraJvmArgs; - protected String[] _extraAppArgs; - protected String[] _optimumJvmArgs; protected List _txtJvmArgs = new ArrayList<>(); - protected List _signers; - /** If a warning has been issued about not being able to set modtimes. */ protected boolean _warnedAboutSetLastModified; diff --git a/core/src/main/java/com/threerings/getdown/data/EnvConfig.java b/core/src/main/java/com/threerings/getdown/data/EnvConfig.java new file mode 100644 index 0000000..96ec657 --- /dev/null +++ b/core/src/main/java/com/threerings/getdown/data/EnvConfig.java @@ -0,0 +1,207 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2016 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.data; + +import java.io.File; +import java.io.FileInputStream; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.*; + +import com.threerings.getdown.util.StringUtil; + +/** Configuration that comes from our "environment" (command line args, sys props, etc.). */ +public final class EnvConfig { + + /** Used to report problems or feedback by {@link #create}. */ + public static final class Note { + public static enum Level { INFO, WARN, ERROR }; + public static Note info (String msg) { return new Note(Level.INFO, msg); } + public static Note warn (String msg) { return new Note(Level.WARN, msg); } + public static Note error (String msg) { return new Note(Level.ERROR, msg); } + public final Level level; + public final String message; + public Note (Level level, String message) { + this.level = level; + this.message = message; + } + } + + /** + * Creates an environment config, obtaining information (in order) from the following sources: + * + *
    + *
  • A {@code bootstrap.properties} file bundled with the jar.
  • + *
  • System properties supplied to the JVM.
  • + *
  • The supplied command line arguments ({@code argv}).
  • + *
+ * + * If a later source supplies a configuration already provided by a prior source, a warning + * message will be logged to indicate the conflict, and the prior source will be used. + * + * @param notes a list into which notes are added, to be logged after the logging system has + * been initialized (which cannot happen until the appdir is known). If any {@code ERROR} notes + * are included, the app should terminate after reporting them. + * @return an env config instance, or {@code null} if no appdir could be located via any + * configuration source. + */ + public static EnvConfig create (String[] argv, List notes) { + String appDir = null, appDirProv = null; + String appId = null, appIdProv = null; + String appBase = null, appBaseProv = null; + + // start with bootstrap.properties config, if avaialble + try { + ResourceBundle bundle = ResourceBundle.getBundle("bootstrap"); + if (bundle.containsKey("appdir")) { + appDir = bundle.getString("appdir"); + appDir = appDir.replace(USER_HOME_KEY, System.getProperty("user.home")); + File appDirFile = new File(appDir); + if (!appDirFile.exists()) { + appDirFile.mkdirs(); + } + appDirProv = "bootstrap.properties"; + } + if (bundle.containsKey("appid")) { + appId = bundle.getString("appid"); + appIdProv = "bootstrap.properties"; + } + if (bundle.containsKey("appbase")) { + appBase = bundle.getString("appbase"); + appBaseProv = "bootstrap.properties"; + } + } catch (MissingResourceException e) { + // bootstrap.properties is optional; no need for a warning + } + + // next seek config from system properties + String spropsAppDir = SysProps.appDir(); + if (!StringUtil.isBlank(spropsAppDir)) { + if (appDir == null) { + appDir = spropsAppDir; + appDirProv = "system property"; + } else { + notes.add(Note.warn("Ignoring 'appdir' system property, have appdir via '" + + appDirProv + "'")); + } + } + String spropsAppId = SysProps.appId(); + if (!StringUtil.isBlank(spropsAppId)) { + if (appId == null) { + appId = spropsAppId; + appIdProv = "system property"; + } else { + notes.add(Note.warn("Ignoring 'appid' system property, have appid via '" + + appIdProv + "'")); + } + } + String spropsAppBase = SysProps.appBase(); + if (!StringUtil.isBlank(spropsAppBase)) { + if (appBase == null) { + appBase = spropsAppBase; + appBaseProv = "system property"; + } else { + notes.add(Note.warn("Ignoring 'appbase' system property, have appbase via '" + + appBaseProv + "'")); + } + } + + // finally obtain config from command line arguments + String argvAppDir = argv.length > 0 ? argv[0] : null; + if (!StringUtil.isBlank(argvAppDir)) { + if (appDir == null) { + appDir = argvAppDir; + appDirProv = "command line"; + } else { + notes.add(Note.warn("Ignoring 'appdir' command line arg, have appdir via '" + + appDirProv + "'")); + } + } + String argvAppId = argv.length > 1 ? argv[1] : null; + if (!StringUtil.isBlank(spropsAppId)) { + if (appId == null) { + appId = argvAppId; + appIdProv = "command line"; + } else { + notes.add(Note.warn("Ignoring 'appid' command line arg, have appid via '" + + appIdProv + "'")); + } + } + + // ensure that we were able to fine an app dir + if (appDir == null) { + return null; // caller will report problem to user + } + + // ensure that the appdir refers to a directory that exists + File appDirFile = new File(appDir); + if (!appDirFile.exists() || !appDirFile.isDirectory()) { + notes.add(Note.error("Invalid appdir '" + appDir + "'.")); + return null; + } + + notes.add(Note.info("Using appdir from " + appDirProv + ": " + appDir)); + if (appId != null) notes.add(Note.info("Using appid from " + appIdProv + ": " + appId)); + if (appBase != null) notes.add(Note.info("Using appbase from " + appBaseProv + ": " + + appBase)); + + // pass along anything after the first tow args as extra app args + List appArgs = argv.length > 2 ? + Arrays.asList(argv).subList(2, argv.length-2) : + Collections.emptyList(); + + // load X.509 certificate if it exists + File crtFile = new File(appDirFile, Digest.digestFile(Digest.VERSION) + ".crt"); + List certs = new ArrayList<>(); + if (crtFile.exists()) { + try (FileInputStream fis = new FileInputStream(crtFile)) { + X509Certificate certificate = (X509Certificate) + CertificateFactory.getInstance("X.509").generateCertificate(fis); + certs.add(certificate); + } catch (Exception e) { + notes.add(Note.error("Certificate error: " + e.getMessage())); + } + } + + return new EnvConfig(appDirFile, appId, appBase, certs, appArgs); + } + + /** The directory in which the application and metadata is stored. */ + public final File appDir; + + /** Either {@code null} or an identifier for a secondary application that should be + * launched. That app will use {@code appid.class} and {@code appid.apparg} to configure + * itself but all other parameters will be the same as the primary app. */ + public final String appId; + + /** Either {@code null} or fallback {@code appbase} to use if one cannot be read from a + * {@code getdown.txt} file during startup. */ + public final String appBase; + + /** Zero or more signing certificates used to verify the digest file. */ + public final List certs; + + /** Additional arguments to pass on to launched application. These will be added after the + * args in the getdown.txt file. */ + public final List appArgs; + + public EnvConfig (File appDir) { + this(appDir, null, null, Collections.emptyList(), + Collections.emptyList()); + } + + private EnvConfig (File appDir, String appId, String appBase, List certs, + List appArgs) { + this.appDir = appDir; + this.appId = appId; + this.appBase = appBase; + this.certs = certs; + this.appArgs = appArgs; + } + + private static final String USER_HOME_KEY = "${user.home}"; +} diff --git a/core/src/main/java/com/threerings/getdown/data/SysProps.java b/core/src/main/java/com/threerings/getdown/data/SysProps.java index 0d164b3..6da630e 100644 --- a/core/src/main/java/com/threerings/getdown/data/SysProps.java +++ b/core/src/main/java/com/threerings/getdown/data/SysProps.java @@ -28,8 +28,8 @@ public class SysProps return System.getProperty("appid"); } - /** Configures the appbase (in lieu of providing a skeleton getdown.txt, and as a last resort - * fallback). Usage: {@code -Dappbase=URL}. */ + /** Configures the bootstrap appbase (used in lieu of providing a skeleton getdown.txt, and as + * a last resort fallback). Usage: {@code -Dappbase=URL}. */ public static String appBase () { return System.getProperty("appbase"); } diff --git a/core/src/main/java/com/threerings/getdown/tools/Differ.java b/core/src/main/java/com/threerings/getdown/tools/Differ.java index 189f0e6..87784f6 100644 --- a/core/src/main/java/com/threerings/getdown/tools/Differ.java +++ b/core/src/main/java/com/threerings/getdown/tools/Differ.java @@ -23,6 +23,7 @@ import java.security.MessageDigest; import com.threerings.getdown.data.Application; import com.threerings.getdown.data.Digest; +import com.threerings.getdown.data.EnvConfig; import com.threerings.getdown.data.Resource; import com.threerings.getdown.util.FileUtil; import com.threerings.getdown.util.StreamUtil; @@ -58,13 +59,13 @@ public class Differ ", overs=" + overs + "]."); } - Application oapp = new Application(ovdir, null); + Application oapp = new Application(new EnvConfig(ovdir)); oapp.init(false); ArrayList orsrcs = new ArrayList<>(); orsrcs.addAll(oapp.getCodeResources()); orsrcs.addAll(oapp.getResources()); - Application napp = new Application(nvdir, null); + Application napp = new Application(new EnvConfig(nvdir)); napp.init(false); ArrayList nrsrcs = new ArrayList<>(); nrsrcs.addAll(napp.getCodeResources()); diff --git a/core/src/main/java/com/threerings/getdown/tools/Digester.java b/core/src/main/java/com/threerings/getdown/tools/Digester.java index e1fa2ab..9aad0cb 100644 --- a/core/src/main/java/com/threerings/getdown/tools/Digester.java +++ b/core/src/main/java/com/threerings/getdown/tools/Digester.java @@ -20,6 +20,7 @@ import java.util.List; import com.threerings.getdown.data.Application; import com.threerings.getdown.data.Digest; +import com.threerings.getdown.data.EnvConfig; import com.threerings.getdown.data.Resource; import com.threerings.getdown.util.Base64; @@ -73,7 +74,7 @@ public class Digester System.out.println("Generating digest file '" + target + "'..."); // create our application and instruct it to parse its business - Application app = new Application(appdir, null); + Application app = new Application(new EnvConfig(appdir)); app.init(false); List rsrcs = new ArrayList<>(); diff --git a/core/src/main/java/com/threerings/getdown/tools/JarDiff.java b/core/src/main/java/com/threerings/getdown/tools/JarDiff.java index da74d1f..2ac66d4 100644 --- a/core/src/main/java/com/threerings/getdown/tools/JarDiff.java +++ b/core/src/main/java/com/threerings/getdown/tools/JarDiff.java @@ -381,7 +381,7 @@ public class JarDiff implements JarDiffCodes public String hasSameContent (JarFile2 file, JarEntry entry) throws IOException { String thisName = null; - Long crcL = new Long(entry.getCrc()); + Long crcL = Long.valueOf(entry.getCrc()); // check if this jar contains files with the passed in entry's crc if (_crcToEntryMap.containsKey(crcL)) { // get the Linked List with files with the crc @@ -416,7 +416,7 @@ public class JarDiff implements JarDiffCodes while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); long crc = entry.getCrc(); - Long crcL = new Long(crc); + Long crcL = Long.valueOf(crc); if (_debug) { System.out.println("\t" + entry.getName() + " CRC " + crc); } diff --git a/core/src/test/java/com/threerings/getdown/classpath/ClassPathsTest.java b/core/src/test/java/com/threerings/getdown/classpath/ClassPathsTest.java index fcca643..be6b32b 100644 --- a/core/src/test/java/com/threerings/getdown/classpath/ClassPathsTest.java +++ b/core/src/test/java/com/threerings/getdown/classpath/ClassPathsTest.java @@ -28,7 +28,7 @@ public class ClassPathsTest when(_firstJar.getFinalTarget()).thenReturn(_firstJarFile); when(_secondJar.getFinalTarget()).thenReturn(_secondJarFile); when(_application.getActiveCodeResources()).thenReturn(Arrays.asList(_firstJar, _secondJar)); - when(_application.getAppdir()).thenReturn(_appdir.getRoot()); + when(_application.getAppDir()).thenReturn(_appdir.getRoot()); } @Test public void shouldBuildDefaultClassPath () throws IOException diff --git a/launcher/src/main/java/com/threerings/getdown/launcher/Getdown.java b/launcher/src/main/java/com/threerings/getdown/launcher/Getdown.java index 1718107..89a1094 100644 --- a/launcher/src/main/java/com/threerings/getdown/launcher/Getdown.java +++ b/launcher/src/main/java/com/threerings/getdown/launcher/Getdown.java @@ -43,22 +43,11 @@ import ca.beq.util.win32.registry.RootKey; import com.samskivert.swing.util.SwingUtil; import com.threerings.getdown.data.Application.UpdateInterface.Step; -import com.threerings.getdown.data.Application; -import com.threerings.getdown.data.Build; -import com.threerings.getdown.data.Resource; -import com.threerings.getdown.data.SysProps; +import com.threerings.getdown.data.*; import com.threerings.getdown.net.Downloader; import com.threerings.getdown.net.HTTPDownloader; import com.threerings.getdown.tools.Patcher; -import com.threerings.getdown.util.Config; -import com.threerings.getdown.util.ConnectionUtil; -import com.threerings.getdown.util.FileUtil; -import com.threerings.getdown.util.LaunchUtil; -import com.threerings.getdown.util.MessageUtil; -import com.threerings.getdown.util.ProgressAggregator; -import com.threerings.getdown.util.ProgressObserver; -import com.threerings.getdown.util.StringUtil; -import com.threerings.getdown.util.VersionUtil; +import com.threerings.getdown.util.*; import static com.threerings.getdown.Log.log; @@ -68,13 +57,7 @@ import static com.threerings.getdown.Log.log; public abstract class Getdown extends Thread implements Application.StatusDisplay, RotatingBackgrounds.ImageLoader { - public Getdown (File appDir, String appId) - { - this(appDir, appId, null, null, null); - } - - public Getdown (File appDir, String appId, List signers, - String[] jvmargs, String[] appargs) + public Getdown (EnvConfig envc) { super("Getdown"); try { @@ -94,7 +77,7 @@ public abstract class Getdown extends Thread } catch (Exception e) { // welcome to hell, where java can't cope with a classpath that contains jars that live // in a directory that contains a !, at least the same bug happens on all platforms - String dir = appDir.toString(); + String dir = envc.appDir.toString(); if (dir.equals(".")) { dir = System.getProperty("user.dir"); } @@ -103,7 +86,7 @@ public abstract class Getdown extends Thread "contains the '!' character, this will trigger this error."; fail(errmsg); } - _app = new Application(appDir, appId, signers, jvmargs, appargs); + _app = new Application(envc); _startup = System.currentTimeMillis(); } diff --git a/launcher/src/main/java/com/threerings/getdown/launcher/GetdownApp.java b/launcher/src/main/java/com/threerings/getdown/launcher/GetdownApp.java index 356fac2..9c6a9fb 100644 --- a/launcher/src/main/java/com/threerings/getdown/launcher/GetdownApp.java +++ b/launcher/src/main/java/com/threerings/getdown/launcher/GetdownApp.java @@ -14,24 +14,18 @@ import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.BufferedOutputStream; import java.io.File; -import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintStream; -import java.security.cert.Certificate; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; -import java.util.MissingResourceException; -import java.util.ResourceBundle; import javax.swing.JFrame; import javax.swing.WindowConstants; import com.samskivert.swing.util.SwingUtil; import com.threerings.getdown.data.Digest; +import com.threerings.getdown.data.EnvConfig; import com.threerings.getdown.data.SysProps; import com.threerings.getdown.util.LaunchUtil; import com.threerings.getdown.util.StringUtil; @@ -42,11 +36,10 @@ import static com.threerings.getdown.Log.log; */ public class GetdownApp { - - private static final String USER_HOME = "${user.home}"; - - public static void main (String[] argv) - { + /** + * The main entry point of the Getdown launcher application. + */ + public static void main (String[] argv) { try { start(argv); } catch (Exception e) { @@ -61,58 +54,17 @@ public class GetdownApp * @throws Exception if anything goes wrong starting Getdown. */ public static Getdown start (String[] argv) throws Exception { - int aidx = 0; - List args = Arrays.asList(argv); - - // check for app dir in a sysprop and then via argv - String adarg = SysProps.appDir(); - if (StringUtil.isBlank(adarg)) { - loadBootstrapResource(); - if (args.isEmpty() && SysProps.appDir() == null) { - System.err.println("Usage: java -jar getdown.jar app_dir [app_id] [app args]"); - System.exit(-1); - } - if (!args.isEmpty()) { - adarg = args.get(aidx++); - } else { - adarg = SysProps.appDir(); - } - } - - // check for an app identifier in a sysprop and then via argv - String appId = SysProps.appId(); - if (StringUtil.isBlank(appId) && aidx < args.size()) { - appId = args.get(aidx++); - } - - // pass along anything after that as app args - String[] appArgs = (aidx >= args.size()) ? null : - args.subList(aidx, args.size()).toArray(new String[0]); - - // ensure a valid directory was supplied - File appDir = new File(adarg); - if (!appDir.exists() || !appDir.isDirectory()) { - log.warning("Invalid app_dir '" + adarg + "'."); + List notes = new ArrayList<>(); + EnvConfig envc = EnvConfig.create(argv, notes); + if (envc == null) { + if (!notes.isEmpty()) for (EnvConfig.Note n : notes) System.err.println(n.message); + else System.err.println("Usage: java -jar getdown.jar [app_dir] [app_id] [app args]"); System.exit(-1); } - // load X.509 certificate if it exists - File crtFile = new File(appDir, Digest.digestFile(Digest.VERSION) + ".crt"); - List crts = new ArrayList<>(); - if (crtFile.exists()) { - try (FileInputStream fis = new FileInputStream(crtFile)) { - X509Certificate certificate = (X509Certificate) - CertificateFactory.getInstance("X.509").generateCertificate(fis); - crts.add(certificate); - } catch (Exception e) { - log.warning("Certificate error: " + e.getMessage()); - System.exit(-1); - } - } - // pipe our output into a file in the application directory if (!SysProps.noLogRedir()) { - File logFile = new File(appDir, "launcher.log"); + File logFile = new File(envc.appDir, "launcher.log"); try { PrintStream logOut = new PrintStream( new BufferedOutputStream(new FileOutputStream(logFile)), true); @@ -123,6 +75,17 @@ public class GetdownApp } } + // report any notes from reading our env config, and abort if necessary + boolean abort = false; + for (EnvConfig.Note note : notes) { + switch (note.level) { + case INFO: log.info(note.message); break; + case WARN: log.warning(note.message); break; + case ERROR: log.error(note.message); abort = true; break; + } + } + if (abort) System.exit(-1); + // record a few things for posterity log.info("------------------ VM Info ------------------"); log.info("-- OS Name: " + System.getProperty("os.name")); @@ -135,7 +98,7 @@ public class GetdownApp log.info("-- Cur dir: " + System.getProperty("user.dir")); log.info("---------------------------------------------"); - Getdown app = new Getdown(appDir, appId, crts, null, appArgs) { + Getdown app = new Getdown(envc) { @Override protected Container createContainer () { // create our user interface, and display it @@ -270,25 +233,4 @@ public class GetdownApp app.start(); return app; } - - private static void loadBootstrapResource() { - try { - ResourceBundle bundle = ResourceBundle.getBundle("bootstrap"); - if (bundle.containsKey("appbase")) { - System.setProperty("appbase", bundle.getString("appbase")); - } - if (bundle.containsKey("appdir")) { - String appDir = bundle.getString("appdir"); - appDir = appDir.replace(USER_HOME, System.getProperty("user.home")); - File appDirFile = new File(appDir); - if (!appDirFile.exists()) { - appDirFile.mkdirs(); - } - System.setProperty("appdir", appDir); - } - log.info("bootstrap.properties found using:", "_appdir", System.getProperty("appdir"), "appbase", System.getProperty("appbase")); - } catch (MissingResourceException e) { - log.info("bootstrap.properties not found in resource bundle, starting without bootstrapping"); - } - } }