diff options
Diffstat (limited to 'src/java/com/jogamp/common/util/IOUtil.java')
-rw-r--r-- | src/java/com/jogamp/common/util/IOUtil.java | 299 |
1 files changed, 193 insertions, 106 deletions
diff --git a/src/java/com/jogamp/common/util/IOUtil.java b/src/java/com/jogamp/common/util/IOUtil.java index b9a0a33..342c557 100644 --- a/src/java/com/jogamp/common/util/IOUtil.java +++ b/src/java/com/jogamp/common/util/IOUtil.java @@ -40,6 +40,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; import java.io.Reader; +import java.io.SyncFailedException; import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; import java.net.URISyntaxException; @@ -52,6 +53,7 @@ import jogamp.common.Debug; import jogamp.common.os.AndroidUtils; import jogamp.common.os.PlatformPropsImpl; +import com.jogamp.common.ExceptionUtils; import com.jogamp.common.net.AssetURLContext; import com.jogamp.common.net.Uri; import com.jogamp.common.nio.Buffers; @@ -59,7 +61,19 @@ import com.jogamp.common.os.MachineDataInfo; import com.jogamp.common.os.Platform; public class IOUtil { - public static final boolean DEBUG = Debug.debug("IOUtil"); + public static final boolean DEBUG; + private static final boolean DEBUG_EXE; + private static final boolean DEBUG_EXE_NOSTREAM; + private static final boolean DEBUG_EXE_EXISTING_FILE; + + static { + Debug.initSingleton(); + DEBUG = Debug.debug("IOUtil"); + DEBUG_EXE = PropertyAccess.isPropertyDefined("jogamp.debug.IOUtil.Exe", true); + DEBUG_EXE_NOSTREAM = PropertyAccess.isPropertyDefined("jogamp.debug.IOUtil.Exe.NoStream", true); + // For security reasons, we have to hardcode this, i.e. disable this manual debug feature! + DEBUG_EXE_EXISTING_FILE = false; // PropertyAccess.isPropertyDefined("jogamp.debug.IOUtil.Exe.ExistingFile", true); + } /** Std. temporary directory property key <code>java.io.tmpdir</code>. */ private static final String java_io_tmpdir_propkey = "java.io.tmpdir"; @@ -424,7 +438,7 @@ public class IOUtil { /*** * - * RESOURCE LOCATION STUFF + * RESOURCE LOCATION HELPER * */ @@ -433,7 +447,10 @@ public class IOUtil { * to be {@link #resolve(int) resolved} at a later time. */ public static class ClassResources { - /** Class instance used to {@link #resolve(int)} the {@link #resourcePaths}. */ + /** Optional {@link ClassLoader} used to {@link #resolve(int)} {@link #resourcePaths}. */ + public final ClassLoader classLoader; + + /** Optional class instance used to {@link #resolve(int)} relative {@link #resourcePaths}. */ public final Class<?> contextCL; /** Resource paths, see {@link #resolve(int)}. */ @@ -443,67 +460,73 @@ public class IOUtil { public final int resourceCount() { return resourcePaths.length; } /** - * @param contextCL class instance to {@link #resolve(int)} {@link #resourcePaths}. - * @param resourcePaths array of strings denominating multiple resource paths. None shall be null. + * @param resourcePaths multiple relative or absolute resource locations + * @param classLoader optional {@link ClassLoader}, see {@link IOUtil#getResource(String, ClassLoader, Class)} + * @param relContext optional relative context, see {@link IOUtil#getResource(String, ClassLoader, Class)} */ - public ClassResources(final Class<?> contextCL, final String[] resourcePaths) { + public ClassResources(final String[] resourcePaths, final ClassLoader classLoader, final Class<?> relContext) { for(int i=resourcePaths.length-1; i>=0; i--) { if( null == resourcePaths[i] ) { throw new IllegalArgumentException("resourcePath["+i+"] is null"); } } - this.contextCL = contextCL; + this.classLoader = classLoader; + this.contextCL = relContext; this.resourcePaths = resourcePaths; } /** - * Resolving one of the {@link #resourcePaths} indexed by <code>uriIndex</code> using {@link #contextCL} and {@link IOUtil#getResource(Class, String)}. + * Resolving one of the {@link #resourcePaths} indexed by <code>uriIndex</code> using + * {@link #classLoader}, {@link #contextCL} through {@link IOUtil#getResource(String, ClassLoader, Class)}. * @throws ArrayIndexOutOfBoundsException if <code>uriIndex</code> is < 0 or >= {@link #resourceCount()}. */ public URLConnection resolve(final int uriIndex) throws ArrayIndexOutOfBoundsException { - return getResource(contextCL, resourcePaths[uriIndex]); + return getResource(resourcePaths[uriIndex], classLoader, contextCL); } } /** * Locating a resource using {@link #getResource(String, ClassLoader)}: * <ul> - * <li><i>relative</i>: <code>context</code>'s package name-path plus <code>resourcePath</code> via <code>context</code>'s ClassLoader. + * <li><i>relative</i>: <code>relContext</code>'s package name-path plus <code>resourcePath</code> via <code>classLoader</code>. * This allows locations relative to JAR- and other URLs. - * The <code>resourcePath</code> may start with <code>../</code> to navigate to parent folder.</li> - * <li><i>absolute</i>: <code>context</code>'s ClassLoader and the <code>resourcePath</code> as is (filesystem)</li> + * The <code>resourcePath</code> may start with <code>../</code> to navigate to parent folder. + * This attempt is skipped if {@code relContext} is {@code null}.</li> + * <li><i>absolute</i>: <code>resourcePath</code> as is via <code>classLoader</code>. * </ul> - * * <p> * Returns the resolved and open URLConnection or null if not found. * </p> * + * @param resourcePath the resource path to locate relative or absolute + * @param classLoader the optional {@link ClassLoader}, recommended + * @param relContext relative context, i.e. position, of the {@code resourcePath}, + * to perform the relative lookup, if not {@code null}. * @see #getResource(String, ClassLoader) * @see ClassLoader#getResource(String) * @see ClassLoader#getSystemResource(String) */ - public static URLConnection getResource(final Class<?> context, final String resourcePath) { + public static URLConnection getResource(final String resourcePath, final ClassLoader classLoader, final Class<?> relContext) { if(null == resourcePath) { return null; } - final ClassLoader contextCL = (null!=context)?context.getClassLoader():IOUtil.class.getClassLoader(); URLConnection conn = null; - if(null != context) { + if(null != relContext) { // scoping the path within the class's package - final String className = context.getName().replace('.', '/'); + final String className = relContext.getName().replace('.', '/'); final int lastSlash = className.lastIndexOf('/'); if (lastSlash >= 0) { final String pkgName = className.substring(0, lastSlash + 1); - conn = getResource(pkgName + resourcePath, contextCL); + conn = getResource(pkgName + resourcePath, classLoader); if(DEBUG) { - System.err.println("IOUtil: found <"+resourcePath+"> within class package <"+pkgName+"> of given class <"+context.getName()+">: "+(null!=conn)); + System.err.println("IOUtil: found <"+resourcePath+"> within class package <"+pkgName+"> of given class <"+relContext.getName()+">: "+(null!=conn)); } } } else if(DEBUG) { - System.err.println("IOUtil: null context"); + System.err.println("IOUtil: null context, skip rel. lookup"); } if(null == conn) { - conn = getResource(resourcePath, contextCL); + conn = getResource(resourcePath, classLoader); if(DEBUG) { System.err.println("IOUtil: found <"+resourcePath+"> by classloader: "+(null!=conn)); } @@ -534,8 +557,7 @@ public class IOUtil { return AssetURLContext.createURL(resourcePath, cl).openConnection(); } catch (final IOException ioe) { if(DEBUG) { - System.err.println("IOUtil: Caught Exception:"); - ioe.printStackTrace(); + ExceptionUtils.dumpThrowable("IOUtil", ioe); } return null; } @@ -544,8 +566,7 @@ public class IOUtil { return AssetURLContext.resolve(resourcePath, cl); } catch (final IOException ioe) { if(DEBUG) { - System.err.println("IOUtil: Caught Exception:"); - ioe.printStackTrace(); + ExceptionUtils.dumpThrowable("IOUtil", ioe); } } } @@ -573,7 +594,7 @@ public class IOUtil { } /** - * @param path assuming a slashified path beginning with "/" as it's root directory, either denotes a file or directory. + * @param path assuming a slashified path, either denotes a file or directory, either relative or absolute. * @return parent of path * @throws URISyntaxException if path is empty or has no parent directory available */ @@ -585,11 +606,11 @@ public class IOUtil { final int e = path.lastIndexOf("/"); if( e < 0 ) { - throw new URISyntaxException(path, "path contains no '/'"); + throw new URISyntaxException(path, "path contains no '/': <"+path+">"); } if( e == 0 ) { // path is root directory - throw new URISyntaxException(path, "path has no parents"); + throw new URISyntaxException(path, "path has no parents: <"+path+">"); } if( e < pl - 1 ) { // path is file, return it's parent directory @@ -599,23 +620,45 @@ public class IOUtil { // path is a directory .. final int p = path.lastIndexOf("/", e-1); if( p >= j) { + // parent itself has '/' - post '!' or no '!' at all return path.substring(0, p+1); + } else { + // parent itself has no '/' + final String parent = path.substring(j, e); + if( parent.equals("..") ) { + throw new URISyntaxException(path, "parent is unresolved: <"+path+">"); + } else { + // parent is '!' or empty (relative path) + return path.substring(0, j); + } } - throw new URISyntaxException(path, "parent of path contains no '/'"); } /** - * @param path assuming a slashified path beginning with "/" as it's root directory, either denotes a file or directory. - * @return clean path string where <code>../</code> and <code>./</code> is resolved. + * @param path assuming a slashified path, either denoting a file or directory, either relative or absolute. + * @return clean path string where {@code ./} and {@code ../} is resolved, + * while keeping a starting {@code ../} at the beginning of a relative path. * @throws URISyntaxException if path is empty or has no parent directory available while resolving <code>../</code> */ public static String cleanPathString(String path) throws URISyntaxException { - int idx; - while ( ( idx = path.indexOf("../") ) >= 0 ) { - path = getParentOf(path.substring(0, idx)) + path.substring(idx+3); + // Resolve './' before '../' to handle case 'parent/./../a.txt' properly. + int idx = path.length() - 1; + while ( idx >= 1 && ( idx = path.lastIndexOf("./", idx) ) >= 0 ) { + if( 0 < idx && path.charAt(idx-1) == '.' ) { + idx-=2; // skip '../' -> idx upfront + } else { + path = path.substring(0, idx) + path.substring(idx+2); + idx--; // idx upfront + } } - while ( ( idx = path.indexOf("./") ) >= 0 ) { - path = path.substring(0, idx) + path.substring(idx+2); + idx = 0; + while ( ( idx = path.indexOf("../", idx) ) >= 0 ) { + if( 0 == idx ) { + idx += 3; // skip starting '../' + } else { + path = getParentOf(path.substring(0, idx)) + path.substring(idx+3); + idx = 0; + } } return path; } @@ -658,8 +701,7 @@ public class IOUtil { return c; } catch (final IOException ioe) { if(DEBUG) { - System.err.println("IOUtil: urlExists("+url+") ["+dbgmsg+"] - false - "+ioe.getClass().getSimpleName()+": "+ioe.getMessage()); - ioe.printStackTrace(); + ExceptionUtils.dumpThrowable("IOUtil: urlExists("+url+") ["+dbgmsg+"] - false -", ioe); } } } else if(DEBUG) { @@ -697,48 +739,49 @@ public class IOUtil { return new String[] { scriptFile }; } } - private static final byte[] getBytesFromRelFile(final byte[] res, final String fname, final int size) throws IOException { - final URLConnection con = IOUtil.getResource(IOUtil.class, fname); + + private static final byte[] readCode(final String fname) throws IOException { + final URLConnection con = IOUtil.getResource(fname, IOUtil.class.getClassLoader(), IOUtil.class); final InputStream in = con.getInputStream(); - int numBytes = 0; + byte[] output = null; try { - while (true) { - final int remBytes = size - numBytes; - int count; - if ( 0 >= remBytes || (count = in.read(res, numBytes, remBytes)) == -1 ) { - break; - } - numBytes += count; - } + output = CustomCompress.inflateFromStream(in); } finally { in.close(); } - if( size != numBytes ) { - throw new IOException("Got "+numBytes+" bytes != expected "+size); - } - return res; + return output; } - private static final Object exeTestBytesLock = new Object(); - private static WeakReference<byte[]> exeTestBytesRef = null; + private static final Object exeTestLock = new Object(); + private static WeakReference<byte[]> exeTestCodeRef = null; private static void fillExeTestFile(final File exefile) throws IOException { if( Platform.OSType.WINDOWS == PlatformPropsImpl.OS_TYPE && Platform.CPUFamily.X86 == PlatformPropsImpl.CPU_ARCH.family ) { - final int codeSize = 268; - final byte[] code; - synchronized ( exeTestBytesLock ) { - byte[] _code; - if( null == exeTestBytesRef || null == ( _code = exeTestBytesRef.get() ) ) { - code = getBytesFromRelFile(new byte[512], "bin/exe-windows-i586-268b.bin", codeSize); - exeTestBytesRef = new WeakReference<byte[]>(code); + final byte[] exeTestCode; + synchronized ( exeTestLock ) { + byte[] _exeTestCode = null; + if( null == exeTestCodeRef || null == ( _exeTestCode = exeTestCodeRef.get() ) ) { + final String fname; + if( Platform.CPUType.X86_64 == PlatformPropsImpl.CPU_ARCH ) { + fname = "bin/exe-windows-x86_64.defl"; + } else { + fname = "bin/exe-windows-i386.defl"; + } + exeTestCode = readCode(fname); + exeTestCodeRef = new WeakReference<byte[]>(exeTestCode); } else { - code = _code; + exeTestCode = _exeTestCode; } } - final OutputStream out = new FileOutputStream(exefile); + final FileOutputStream out = new FileOutputStream(exefile); try { - out.write(code, 0, codeSize); + out.write(exeTestCode, 0, exeTestCode.length); + try { + out.getFD().sync(); + } catch (final SyncFailedException sfe) { + ExceptionUtils.dumpThrowable("", sfe); + } } finally { out.close(); } @@ -748,6 +791,11 @@ public class IOUtil { final FileWriter fout = new FileWriter(exefile); try { fout.write(shellCode); + try { + fout.flush(); + } catch (final IOException sfe) { + ExceptionUtils.dumpThrowable("", sfe); + } } finally { fout.close(); } @@ -812,44 +860,52 @@ public class IOUtil { public static class StreamMonitor implements Runnable { private final InputStream[] istreams; + private final boolean[] eos; private final PrintStream ostream; private final String prefix; public StreamMonitor(final InputStream[] streams, final PrintStream ostream, final String prefix) { this.istreams = streams; + this.eos = new boolean[streams.length]; this.ostream = ostream; this.prefix = prefix; - new Thread(this, "StreamMonitor-"+Thread.currentThread().getName()).start(); + final InterruptSource.Thread t = new InterruptSource.Thread(null, this, "StreamMonitor-"+Thread.currentThread().getName()); + t.setDaemon(true); + t.start(); } + @Override public void run() { final byte[] buffer = new byte[4096]; try { - int numRead; + final int streamCount = istreams.length; + int eosCount = 0; do { - numRead = 0; for(int i=0; i<istreams.length; i++) { - final int numReadI = istreams[i].read(buffer); - if (numReadI > 0) { - if( null != ostream ) { - if( null != prefix ) { - ostream.write(prefix.getBytes()); + if( !eos[i] ) { + final int numReadI = istreams[i].read(buffer); + if (numReadI > 0) { + if( null != ostream ) { + if( null != prefix ) { + ostream.write(prefix.getBytes()); + } + ostream.write(buffer, 0, numReadI); } - ostream.write(buffer, 0, numReadI); + } else { + // numReadI == -1 + eosCount++; + eos[i] = true; } - numRead += numReadI; } } if( null != ostream ) { ostream.flush(); } - } while (numRead >= 0); - } - catch (final IOException e) { - for(int i=0; i<istreams.length; i++) { - try { - istreams[i].close(); - } catch (final IOException e2) { } + } while ( eosCount < streamCount ); + } catch (final IOException e) { + } finally { + if( null != ostream ) { + ostream.flush(); } // Should allow clean exit when process shuts down } @@ -871,6 +927,8 @@ public class IOUtil { public static boolean testDirExec(final File dir) throws SecurityException { + final boolean debug = DEBUG_EXE || DEBUG; + if( !testTempDirExec ) { if(DEBUG) { System.err.println("IOUtil.testDirExec: <"+dir.getAbsolutePath()+">: Disabled "+_TestTempDirExec); @@ -878,64 +936,93 @@ public class IOUtil { return false; } if (!testFile(dir, true, true)) { - if(DEBUG) { + if( debug ) { System.err.println("IOUtil.testDirExec: <"+dir.getAbsolutePath()+">: Not writeable dir"); } return false; } if(!getOSHasNoexecFS()) { - if(DEBUG) { + if( debug ) { System.err.println("IOUtil.testDirExec: <"+dir.getAbsolutePath()+">: Always executable"); } return true; } - final long t0 = DEBUG ? System.currentTimeMillis() : 0; + final long t0 = debug ? System.currentTimeMillis() : 0; final File exeTestFile; + final boolean existingExe; try { - exeTestFile = File.createTempFile("jogamp_exe_tst", getExeTestFileSuffix(), dir); + final File permExeTestFile = DEBUG_EXE_EXISTING_FILE ? new File(dir, "jogamp_exe_tst"+getExeTestFileSuffix()) : null; + if( null != permExeTestFile && permExeTestFile.exists() ) { + exeTestFile = permExeTestFile; + existingExe = true; + } else { + exeTestFile = File.createTempFile("jogamp_exe_tst", getExeTestFileSuffix(), dir); + existingExe = false; + } } catch (final SecurityException se) { throw se; // fwd Security exception } catch (final IOException e) { - if(DEBUG) { + if( debug ) { e.printStackTrace(); } return false; } - final long t1 = DEBUG ? System.currentTimeMillis() : 0; + final long t1 = debug ? System.currentTimeMillis() : 0; + long t2; int res = -1; - if(exeTestFile.setExecutable(true /* exec */, true /* ownerOnly */)) { + int exitValue = -1; + if( existingExe || exeTestFile.setExecutable(true /* exec */, true /* ownerOnly */) ) { + Process pr = null; try { - fillExeTestFile(exeTestFile); - + if( !existingExe ) { + fillExeTestFile(exeTestFile); + } + t2 = debug ? System.currentTimeMillis() : 0; // Using 'Process.exec(String[])' avoids StringTokenizer of 'Process.exec(String)' // and hence splitting up command by spaces! - final Process pr = Runtime.getRuntime().exec( getExeTestCommandArgs( exeTestFile.getCanonicalPath() ) ); - /** - * Disable StreamMonitor, which throttles exec-test performance a lot! - * - * if( isStringSet(shellCode) ) { + // Note: All no-exec cases throw an IOExceptions at ProcessBuilder.start(), i.e. below exec() call! + pr = Runtime.getRuntime().exec( getExeTestCommandArgs( exeTestFile.getCanonicalPath() ), null, null ); + if( DEBUG_EXE && !DEBUG_EXE_NOSTREAM ) { new StreamMonitor(new InputStream[] { pr.getInputStream(), pr.getErrorStream() }, System.err, "Exe-Tst: "); - } - */ - pr.waitFor() ; - res = pr.exitValue(); + } + pr.waitFor(); + exitValue = pr.exitValue(); // Note: Bug 1219 Comment 50: On reporter's machine exit value 1 is being returned + res = 0; // file has been executed } catch (final SecurityException se) { throw se; // fwd Security exception } catch (final Throwable t) { + t2 = debug ? System.currentTimeMillis() : 0; res = -2; - if(DEBUG) { + if( debug ) { System.err.println("IOUtil.testDirExec: <"+exeTestFile.getAbsolutePath()+">: Caught "+t.getClass().getSimpleName()+": "+t.getMessage()); t.printStackTrace(); } + } finally { + if( null != pr ) { + // Bug 1219 Comment 58: Ensure that the launched process gets terminated! + // This is Process implementation specific and varies on different platforms, + // hence it may be required. + try { + pr.destroy(); + } catch (final Throwable t) { + ExceptionUtils.dumpThrowable("", t); + } + } } + } else { + t2 = debug ? System.currentTimeMillis() : 0; } + final boolean ok = 0 == res; - final long t2 = DEBUG ? System.currentTimeMillis() : 0; - exeTestFile.delete(); - if( DEBUG) { - System.err.println("IOUtil.testDirExec(): <"+dir.getAbsolutePath()+">: res "+res+" -> "+ok); - System.err.println("IOUtil.testDirExec(): total "+(t2-t0)+"ms, create "+(t1-t0)+"ms, execute "+(t2-t1)+"ms"); + if( !DEBUG_EXE && !existingExe ) { + exeTestFile.delete(); + } + if( debug ) { + final long t3 = System.currentTimeMillis(); + System.err.println("IOUtil.testDirExec(): test-exe <"+exeTestFile.getAbsolutePath()+">, existingFile "+existingExe+", returned "+exitValue); + System.err.println("IOUtil.testDirExec(): abs-path <"+dir.getAbsolutePath()+">: res "+res+" -> "+ok); + System.err.println("IOUtil.testDirExec(): total "+(t3-t0)+"ms, create "+(t1-t0)+"ms, fill "+(t2-t1)+"ms, execute "+(t3-t2)+"ms"); } return ok; } |