Added support for marking resources as executable.

This commit is contained in:
Michael Bayne
2018-08-08 11:18:26 -07:00
parent 4c7fa5c87b
commit d1992a5b9d
5 changed files with 67 additions and 50 deletions
@@ -269,7 +269,7 @@ public class Application
public Resource getConfigResource () public Resource getConfigResource ()
{ {
try { try {
return createResource(CONFIG_FILE, false); return createResource(CONFIG_FILE, Resource.NORMAL);
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException("Invalid appbase '" + _vappbase + "'.", e); throw new RuntimeException("Invalid appbase '" + _vappbase + "'.", e);
} }
@@ -395,7 +395,7 @@ public class Application
String pfile = "patch" + infix + _version + ".dat"; String pfile = "patch" + infix + _version + ".dat";
try { try {
URL remote = new URL(createVAppBase(_targetVersion), encodePath(pfile)); URL remote = new URL(createVAppBase(_targetVersion), encodePath(pfile));
return new Resource(pfile, remote, getLocalPath(pfile), false); return new Resource(pfile, remote, getLocalPath(pfile), Resource.NORMAL);
} catch (Exception e) { } catch (Exception e) {
log.warning("Failed to create patch resource path", log.warning("Failed to create patch resource path",
"pfile", pfile, "appbase", _appbase, "tvers", _targetVersion, "error", e); "pfile", pfile, "appbase", _appbase, "tvers", _targetVersion, "error", e);
@@ -417,7 +417,8 @@ public class Application
String vmfile = LaunchUtil.LOCAL_JAVA_DIR + ".jar"; String vmfile = LaunchUtil.LOCAL_JAVA_DIR + ".jar";
try { try {
URL remote = new URL(createVAppBase(_targetVersion), encodePath(_javaLocation)); URL remote = new URL(createVAppBase(_targetVersion), encodePath(_javaLocation));
return new Resource(vmfile, remote, getLocalPath(vmfile), true); return new Resource(vmfile, remote, getLocalPath(vmfile),
EnumSet.of(Resource.Attr.UNPACK));
} catch (Exception e) { } catch (Exception e) {
log.warning("Failed to create VM resource", "vmfile", vmfile, "appbase", _appbase, log.warning("Failed to create VM resource", "vmfile", vmfile, "appbase", _appbase,
"tvers", _targetVersion, "javaloc", _javaLocation, "error", e); "tvers", _targetVersion, "javaloc", _javaLocation, "error", e);
@@ -434,7 +435,7 @@ public class Application
String file = "full"; String file = "full";
try { try {
URL remote = new URL(createVAppBase(_targetVersion), encodePath(file)); URL remote = new URL(createVAppBase(_targetVersion), encodePath(file));
return new Resource(file, remote, getLocalPath(file), false); return new Resource(file, remote, getLocalPath(file), Resource.NORMAL);
} catch (Exception e) { } catch (Exception e) {
log.warning("Failed to create full resource path", log.warning("Failed to create full resource path",
"file", file, "appbase", _appbase, "tvers", _targetVersion, "error", e); "file", file, "appbase", _appbase, "tvers", _targetVersion, "error", e);
@@ -656,21 +657,22 @@ public class Application
ConfigUtil.getMultiValue(cdata, "ucode") == null) { ConfigUtil.getMultiValue(cdata, "ucode") == null) {
throw new IOException("m.missing_code"); throw new IOException("m.missing_code");
} }
parseResources(cdata, "code", false, _codes); parseResources(cdata, "code", Resource.NORMAL, _codes);
parseResources(cdata, "ucode", true, _codes); parseResources(cdata, "ucode", Resource.UNPACK, _codes);
// parse our non-code resources // parse our non-code resources
parseResources(cdata, "resource", false, _resources); parseResources(cdata, "resource", Resource.NORMAL, _resources);
parseResources(cdata, "uresource", true, _resources); parseResources(cdata, "uresource", Resource.UNPACK, _resources);
parseResources(cdata, "xresource", Resource.EXEC, _resources);
// parse our auxiliary resource groups // parse our auxiliary resource groups
for (String auxgroup : parseList(cdata, "auxgroups")) { for (String auxgroup : parseList(cdata, "auxgroups")) {
ArrayList<Resource> codes = new ArrayList<>(); ArrayList<Resource> codes = new ArrayList<>();
parseResources(cdata, auxgroup + ".code", false, codes); parseResources(cdata, auxgroup + ".code", Resource.NORMAL, codes);
parseResources(cdata, auxgroup + ".ucode", true, codes); parseResources(cdata, auxgroup + ".ucode", Resource.UNPACK, codes);
ArrayList<Resource> rsrcs = new ArrayList<>(); ArrayList<Resource> rsrcs = new ArrayList<>();
parseResources(cdata, auxgroup + ".resource", false, rsrcs); parseResources(cdata, auxgroup + ".resource", Resource.NORMAL, rsrcs);
parseResources(cdata, auxgroup + ".uresource", true, rsrcs); parseResources(cdata, auxgroup + ".uresource", Resource.UNPACK, rsrcs);
_auxgroups.put(auxgroup, new AuxGroup(auxgroup, codes, rsrcs)); _auxgroups.put(auxgroup, new AuxGroup(auxgroup, codes, rsrcs));
} }
@@ -1375,8 +1377,7 @@ public class Application
toInstall.add(rsrc); toInstall.add(rsrc);
return; return;
} }
// unpack this resource if appropriate rsrc.applyAttrs();
rsrc.unpackIfNeeded();
unpacked.add(rsrc); unpacked.add(rsrc);
rsrc.markAsValid(); rsrc.markAsValid();
return; return;
@@ -1672,15 +1673,15 @@ public class Application
} }
/** Helper function for creating {@link Resource} instances. */ /** Helper function for creating {@link Resource} instances. */
protected Resource createResource (String path, boolean unpack) protected Resource createResource (String path, EnumSet<Resource.Attr> attrs)
throws MalformedURLException throws MalformedURLException
{ {
return new Resource(path, getRemoteURL(path), getLocalPath(path), unpack); return new Resource(path, getRemoteURL(path), getLocalPath(path), attrs);
} }
/** Used to parse resources with the specified name. */ /** Used to parse resources with the specified name. */
protected void parseResources (Map<String,Object> cdata, String name, boolean unpack, protected void parseResources (Map<String,Object> cdata, String name,
List<Resource> list) EnumSet<Resource.Attr> attrs, List<Resource> list)
{ {
String[] rsrcs = ConfigUtil.getMultiValue(cdata, name); String[] rsrcs = ConfigUtil.getMultiValue(cdata, name);
if (rsrcs == null) { if (rsrcs == null) {
@@ -1688,7 +1689,7 @@ public class Application
} }
for (String rsrc : rsrcs) { for (String rsrc : rsrcs) {
try { try {
list.add(createResource(rsrc, unpack)); list.add(createResource(rsrc, attrs));
} catch (Exception e) { } catch (Exception e) {
log.warning("Invalid resource '" + rsrc + "'. " + e); log.warning("Invalid resource '" + rsrc + "'. " + e);
} }
@@ -10,6 +10,7 @@ import java.net.URL;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.EnumSet;
import java.util.List; import java.util.List;
import java.util.jar.JarEntry; import java.util.jar.JarEntry;
import java.util.jar.JarFile; import java.util.jar.JarFile;
@@ -27,6 +28,18 @@ import static com.threerings.getdown.Log.log;
*/ */
public class Resource implements Comparable<Resource> public class Resource implements Comparable<Resource>
{ {
/** Defines special attributes for resources. */
public static enum Attr {
/** Indicates that the resource should be unpacked. */
UNPACK,
/** Indicates that the resource should be marked executable. */
EXEC
};
public static EnumSet<Attr> NORMAL = EnumSet.noneOf(Attr.class);
public static EnumSet<Attr> UNPACK = EnumSet.of(Attr.UNPACK);
public static EnumSet<Attr> EXEC = EnumSet.of(Attr.EXEC);
/** /**
* Computes the MD5 hash of the supplied file. * Computes the MD5 hash of the supplied file.
* @param version the version of the digest protocol to use. * @param version the version of the digest protocol to use.
@@ -115,7 +128,7 @@ public class Resource implements Comparable<Resource>
/** /**
* Creates a resource with the supplied remote URL and local path. * Creates a resource with the supplied remote URL and local path.
*/ */
public Resource (String path, URL remote, File local, boolean unpack) public Resource (String path, URL remote, File local, EnumSet<Attr> attrs)
{ {
_path = path; _path = path;
_remote = remote; _remote = remote;
@@ -124,12 +137,13 @@ public class Resource implements Comparable<Resource>
String lpath = _local.getPath(); String lpath = _local.getPath();
_marker = new File(lpath + "v"); _marker = new File(lpath + "v");
_unpack = unpack; _attrs = attrs;
_isJar = isJar(lpath); _isJar = isJar(lpath);
_isPacked200Jar = isPacked200Jar(lpath); _isPacked200Jar = isPacked200Jar(lpath);
if (_unpack && _isJar) { boolean unpack = attrs.contains(Attr.UNPACK);
if (unpack && _isJar) {
_unpacked = _local.getParentFile(); _unpacked = _local.getParentFile();
} else if(_unpack && _isPacked200Jar) { } else if(unpack && _isPacked200Jar) {
String dotJar = ".jar", lname = _local.getName(); String dotJar = ".jar", lname = _local.getName();
String uname = lname.substring(0, lname.lastIndexOf(dotJar) + dotJar.length()); String uname = lname.substring(0, lname.lastIndexOf(dotJar) + dotJar.length());
_unpacked = new File(_local.getParent(), uname); _unpacked = new File(_local.getParent(), uname);
@@ -189,7 +203,7 @@ public class Resource implements Comparable<Resource>
*/ */
public boolean shouldUnpack () public boolean shouldUnpack ()
{ {
return _unpack && !SysProps.noUnpack(); return _attrs.contains(Attr.UNPACK) && !SysProps.noUnpack();
} }
/** /**
@@ -254,8 +268,7 @@ public class Resource implements Comparable<Resource>
if (!FileUtil.renameTo(source, dest)) { if (!FileUtil.renameTo(source, dest)) {
throw new IOException("Failed to rename " + source + " to " + dest); throw new IOException("Failed to rename " + source + " to " + dest);
} }
// unpack the resource, now that it's installed, and mark it as valid applyAttrs();
unpackIfNeeded();
markAsValid(); markAsValid();
} }
@@ -276,12 +289,16 @@ public class Resource implements Comparable<Resource>
} }
/** /**
* Unpacks this resource if needed. * Applies this resources special attributes: unpacks this resource if needed, marks it as
* executable if needed.
*/ */
public void unpackIfNeeded () throws IOException { public void applyAttrs () throws IOException {
if (shouldUnpack()) { if (shouldUnpack()) {
unpack(); unpack();
} }
if (_attrs.contains(Attr.EXEC)) {
FileUtil.makeExecutable(_local);
}
} }
/** /**
@@ -341,7 +358,8 @@ public class Resource implements Comparable<Resource>
protected String _path; protected String _path;
protected URL _remote; protected URL _remote;
protected File _local, _localNew, _marker, _unpacked; protected File _local, _localNew, _marker, _unpacked;
protected boolean _unpack, _isJar, _isPacked200Jar; protected EnumSet<Attr> _attrs;
protected boolean _isJar, _isPacked200Jar;
/** Used to sort the entries in a jar file. */ /** Used to sort the entries in a jar file. */
protected static final Comparator<JarEntry> ENTRY_COMP = new Comparator<JarEntry>() { protected static final Comparator<JarEntry> ENTRY_COMP = new Comparator<JarEntry>() {
@@ -633,9 +633,9 @@ public abstract class Getdown extends Thread
// these only run on non-Windows platforms, so we use Unix file separators // these only run on non-Windows platforms, so we use Unix file separators
String localJavaDir = LaunchUtil.LOCAL_JAVA_DIR + "/"; String localJavaDir = LaunchUtil.LOCAL_JAVA_DIR + "/";
makeExecutable(localJavaDir + "bin/java"); FileUtil.makeExecutable(_app.getLocalPath(localJavaDir + "bin/java"));
makeExecutable(localJavaDir + "lib/jspawnhelper"); FileUtil.makeExecutable(_app.getLocalPath(localJavaDir + "lib/jspawnhelper"));
makeExecutable(localJavaDir + "lib/amd64/jspawnhelper"); FileUtil.makeExecutable(_app.getLocalPath(localJavaDir + "lib/amd64/jspawnhelper"));
// lastly regenerate the .jsa dump file that helps Java to start up faster // lastly regenerate the .jsa dump file that helps Java to start up faster
String vmpath = LaunchUtil.getJVMPath(_app.getLocalPath("")); String vmpath = LaunchUtil.getJVMPath(_app.getLocalPath(""));
@@ -649,23 +649,6 @@ public abstract class Getdown extends Thread
reportTrackingEvent("jvm_complete", -1); reportTrackingEvent("jvm_complete", -1);
} }
protected void makeExecutable (String path) {
// Java doesn't know anything about file permissions (and by extension then,
// neither does Jar), so on Unix we have to hackily do so via chmod
if (!RunAnywhere.isWindows()) {
File target = _app.getLocalPath(path);
String cmd = "chmod a+rx " + target;
try {
if (target.exists()) {
log.info("Running: " + cmd);
Runtime.getRuntime().exec(cmd);
}
} catch (Exception e) {
log.warning("Failed to mark VM binary as executable", "cmd", cmd, "error", e);
}
}
}
/** /**
* Called if the application is determined to be of an old version. * Called if the application is determined to be of an old version.
*/ */
@@ -169,7 +169,7 @@ public class GetdownApp
if (icons.isEmpty()) { if (icons.isEmpty()) {
log.warning("Failed to load any icons", "iconImages", _ifc.iconImages); log.warning("Failed to load any icons", "iconImages", _ifc.iconImages);
} else { } else {
SwingUtil.setFrameIcons(_frame, icons); _frame.setIconImages(icons);
} }
} }
@@ -206,6 +206,21 @@ public class FileUtil
} }
} }
/**
* Marks {@code file} as executable, if it exists. Catches and logs any errors that occur.
*/
public static void makeExecutable (File file) {
try {
if (file.exists()) {
if (!file.setExecutable(true, false)) {
log.warning("Failed to mark as executable", "file", file);
}
}
} catch (Exception e) {
log.warning("Failed to mark as executable", "file", file, "error", e);
}
}
/** /**
* Used by {@link #walkTree}. * Used by {@link #walkTree}.
*/ */