summaryrefslogtreecommitdiffstats
path: root/jack-launcher/src
diff options
context:
space:
mode:
authorYohann Roussel <yroussel@google.com>2015-04-14 10:59:21 +0200
committerYohann Roussel <yroussel@google.com>2015-04-14 21:45:40 +0200
commit6909f869f245885370755490be2a6cbf99db5216 (patch)
treeb930339534debb829b9c48bd38ea165d59987acb /jack-launcher/src
parent97fcd1f2f8d4a6f15dc56e04107817283bc521c4 (diff)
downloadtoolchain_jack-6909f869f245885370755490be2a6cbf99db5216.zip
toolchain_jack-6909f869f245885370755490be2a6cbf99db5216.tar.gz
toolchain_jack-6909f869f245885370755490be2a6cbf99db5216.tar.bz2
A launcher protecting for classpath entries deletion
Allows to run application loaded in a classloader resistant to classpath entries deletion. Bug: 20132430 Change-Id: If6f999956b2230651d2ca1aea073848cfd8ff60d
Diffstat (limited to 'jack-launcher/src')
-rw-r--r--jack-launcher/src/com/android/jack/launcher/Main.java113
-rw-r--r--jack-launcher/src/com/android/jack/launcher/ZipLoader.java211
-rw-r--r--jack-launcher/src/com/android/jack/launcher/util/BytesStreamSucker.java68
3 files changed, 392 insertions, 0 deletions
diff --git a/jack-launcher/src/com/android/jack/launcher/Main.java b/jack-launcher/src/com/android/jack/launcher/Main.java
new file mode 100644
index 0000000..31a09bd
--- /dev/null
+++ b/jack-launcher/src/com/android/jack/launcher/Main.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.jack.launcher;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Pattern;
+import java.util.zip.ZipFile;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Launcher protecting called main class from deletion of its classpath.
+ */
+public class Main {
+
+ @Nonnull
+ private static Logger logger = Logger.getLogger(Main.class.getName());
+
+ private static final int CMD_IDX_CLASSPATH_OPTION = 0;
+ private static final int CMD_IDX_CLASSPATH_VALUE = 1;
+ private static final int CMD_IDX_MAIN_CLASS = 2;
+ private static final int CMD_HANDLED_IDX = 3;
+
+
+ public static void main(@Nonnull String[] args) {
+ if (args.length < CMD_HANDLED_IDX) {
+ if (args.length < CMD_HANDLED_IDX) {
+ logUsage();
+ abort();
+ }
+ }
+
+ if (!args[CMD_IDX_CLASSPATH_OPTION].equals("-cp")) {
+ logUsage();
+ abort();
+ }
+
+ String classpath = args[CMD_IDX_CLASSPATH_VALUE];
+ String[] classpathEntries = classpath.split(Pattern.quote(File.pathSeparator));
+
+ ZipFile[] zips = new ZipFile[classpathEntries.length];
+ for (int i = 0; i < zips.length; i++) {
+ try {
+ zips[i] = new ZipFile(classpathEntries[i]);
+ } catch (IOException e) {
+ logger.log(Level.INFO, "IOEception while trying to open '" + classpathEntries[i] + "'", e);
+ logger.log(Level.SEVERE, "Failed to open '" + classpathEntries[i] + "'");
+ abort();
+ }
+ }
+
+ ClassLoader loader = new ZipLoader(zips);
+ Class<?> clazz;
+ try {
+ clazz = loader.loadClass(args[CMD_IDX_MAIN_CLASS]);
+ } catch (ClassNotFoundException e) {
+ logger.log(Level.SEVERE, "Failed to find class '" + args[CMD_IDX_MAIN_CLASS] + "'");
+ abort();
+ return;
+ }
+ Method method;
+ try {
+ method = clazz.getMethod("main", args.getClass());
+ } catch (SecurityException e) {
+ throw new AssertionError();
+ } catch (NoSuchMethodException e) {
+ logger.log(Level.SEVERE, "Failed to find main method in class '" + args[CMD_IDX_MAIN_CLASS]
+ + "'");
+ abort();
+ return;
+ }
+ String[] invokeArgs = new String[args.length - CMD_HANDLED_IDX];
+ System.arraycopy(args, CMD_HANDLED_IDX, invokeArgs, 0, args.length - CMD_HANDLED_IDX);
+ try {
+ method.invoke(null, (Object) invokeArgs);
+ } catch (IllegalArgumentException e) {
+ throw new AssertionError();
+ } catch (IllegalAccessException e) {
+ throw new AssertionError();
+ } catch (InvocationTargetException e) {
+ logger.log(Level.SEVERE, "An error occured during delegate execution", e.getCause());
+ abort();
+ }
+ }
+
+ private static void logUsage() {
+ logger.log(Level.SEVERE, "Usage: -cp <classpath> <main class> ...");
+ }
+
+ private static void abort() {
+ System.exit(1);
+ }
+
+}
diff --git a/jack-launcher/src/com/android/jack/launcher/ZipLoader.java b/jack-launcher/src/com/android/jack/launcher/ZipLoader.java
new file mode 100644
index 0000000..2994823
--- /dev/null
+++ b/jack-launcher/src/com/android/jack/launcher/ZipLoader.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.jack.launcher;
+
+import com.android.jack.launcher.util.BytesStreamSucker;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+import java.util.Enumeration;
+import java.util.Vector;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+class ZipLoader extends ClassLoader {
+
+ private static final char BANG = '!';
+ static {
+ // ensure class is loaded early, don't wait for first usage
+ ZipURLConnection.class.getName();
+ }
+
+ private static class ZipURLConnection extends URLConnection {
+ @Nonnull
+ private final ZipFile zip;
+ @Nonnull
+ private final ZipEntry entry;
+
+ ZipURLConnection(@Nonnull URL u, @Nonnull ZipFile zip, @Nonnull ZipEntry entry) {
+ super(u);
+ this.zip = zip;
+ this.entry = entry;
+ }
+
+ @Override
+ public void connect() {
+ // nothing to do
+ }
+
+ @Override
+ @Nonnull
+ public InputStream getInputStream() throws IOException {
+ return zip.getInputStream(entry);
+ }
+
+ }
+
+ private static class ZipURLStreamHandler extends URLStreamHandler {
+ @Nonnull
+ private final ZipFile zip;
+
+ ZipURLStreamHandler(@Nonnull ZipFile zip) {
+ this.zip = zip;
+ }
+
+ @Override
+ protected URLConnection openConnection(@Nonnull URL u) throws IOException {
+ ZipEntry entry = zip.getEntry(getZipEntry(u));
+ if (entry == null) {
+ throw new FileNotFoundException(u.toString());
+ }
+ return new ZipURLConnection(u, zip, entry);
+ }
+ }
+
+ @Nonnull
+ private static URL makeURL(@Nonnull ZipFile file, @Nonnull ZipEntry entry,
+ @Nonnull ZipURLStreamHandler handler) {
+ try {
+ assert entry.getName().indexOf(BANG) == -1;
+ return new URL("launcherzip", "", -1, file.getName() + BANG + entry.getName(), handler);
+ } catch (MalformedURLException e) {
+ throw new AssertionError();
+ }
+ }
+
+ @Nonnull
+ private static String getZipEntry(@Nonnull URL url) {
+ String file = url.getFile();
+ int dashIndex = file.lastIndexOf(BANG);
+ return file.substring(dashIndex + 1);
+ }
+
+ @Nonnull
+ private final ZipFile[] entries;
+ @Nonnull
+ private final ZipURLStreamHandler[] handlers;
+
+
+ public ZipLoader(@Nonnull ZipFile[] entries) {
+ this.entries = entries;
+ handlers = new ZipURLStreamHandler[entries.length];
+ for (int i = 0; i < entries.length; i++) {
+ handlers[i] = new ZipURLStreamHandler(entries[i]);
+ }
+ }
+
+ @Nonnull
+ @Override
+ protected Class<?> findClass(@Nonnull String name) throws ClassNotFoundException {
+
+ ZipEntry foundEntry = null;
+ ZipFile foundZip = null;
+ for (ZipFile zip : entries) {
+ foundEntry = zip.getEntry(name.replace('.', '/') + ".class");
+ if (foundEntry != null) {
+ foundZip = zip;
+ break;
+ }
+ }
+ if (foundEntry == null) {
+ throw new ClassNotFoundException(name);
+ }
+
+ InputStream in = null;
+ try {
+ // FINDBUGS
+ assert foundZip != null;
+ in = foundZip.getInputStream(foundEntry);
+ long size = foundEntry.getSize();
+ assert size >= -1 && size <= Integer.MAX_VALUE;
+ ByteArrayOutputStream out = size == -1 ? new ByteArrayOutputStream() :
+ new ByteArrayOutputStream((int) size);
+ new BytesStreamSucker(in, out, /* toBeClose = */ true).suck();
+ byte[] classData = out.toByteArray();
+ return defineClass(name, classData, 0, classData.length);
+ } catch (IOException e) {
+ throw new ClassNotFoundException(name);
+ } finally {
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+ }
+
+ @CheckForNull
+ @Override
+ public InputStream getResourceAsStream(@Nonnull String name) {
+ InputStream found = getParent().getResourceAsStream(name);
+ if (found != null) {
+ return found;
+ } else {
+ for (ZipFile zip : entries) {
+ ZipEntry foundEntry = zip.getEntry(name);
+ if (foundEntry != null) {
+ try {
+ return zip.getInputStream(foundEntry);
+ } catch (IOException e) {
+ AssertionError error = new AssertionError();
+ error.initCause(e);
+ throw error;
+ }
+ }
+ }
+ return null;
+ }
+ }
+
+ @CheckForNull
+ @Override
+ protected URL findResource(@Nonnull String name) {
+ for (int i = 0; i < entries.length; i++) {
+ ZipFile zip = entries[i];
+ ZipEntry foundEntry = zip.getEntry(name);
+ if (foundEntry != null) {
+ return makeURL(zip, foundEntry, handlers[i]);
+ }
+ }
+ return null;
+ }
+
+ @Nonnull
+ @Override
+ protected Enumeration<URL> findResources(@Nonnull String name) {
+ Vector<URL> vector = new Vector<URL>();
+ for (int i = 0; i < entries.length; i++) {
+ ZipFile zip = entries[i];
+ ZipEntry foundEntry = zip.getEntry(name);
+ if (foundEntry != null) {
+ vector.add(makeURL(zip, foundEntry, handlers[i]));
+ }
+ }
+ return vector.elements();
+ }
+}
diff --git a/jack-launcher/src/com/android/jack/launcher/util/BytesStreamSucker.java b/jack-launcher/src/com/android/jack/launcher/util/BytesStreamSucker.java
new file mode 100644
index 0000000..8d5d672
--- /dev/null
+++ b/jack-launcher/src/com/android/jack/launcher/util/BytesStreamSucker.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.jack.launcher.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Class that continuously read an {@link InputStream} and optionally could write the input in a
+ * {@link OutputStream}.
+ */
+public class BytesStreamSucker {
+
+ private static final int BUFFER_SIZE = 4096;
+
+ @Nonnull
+ private final byte[] buffer = new byte[BUFFER_SIZE];
+
+ @Nonnull
+ private final InputStream is;
+
+ @Nonnull
+ private final OutputStream os;
+
+ private final boolean toBeClose;
+
+ public BytesStreamSucker(
+ @Nonnull InputStream is, @Nonnull OutputStream os, boolean toBeClose) {
+ this.is = is;
+ this.os = os;
+ this.toBeClose = toBeClose;
+ }
+
+ public BytesStreamSucker(@Nonnull InputStream is, @Nonnull OutputStream os) {
+ this(is, os, false);
+ }
+
+ public void suck() throws IOException {
+ try {
+ int bytesRead;
+ while ((bytesRead = is.read(buffer)) >= 0) {
+ os.write(buffer, 0, bytesRead);
+ os.flush();
+ }
+ } finally {
+ if (toBeClose) {
+ os.close();
+ }
+ }
+ }
+} \ No newline at end of file