diff --git a/autoupdate.services/libsrc/org/netbeans/updater/ModuleUpdater.java b/autoupdate.services/libsrc/org/netbeans/updater/ModuleUpdater.java --- a/autoupdate.services/libsrc/org/netbeans/updater/ModuleUpdater.java +++ b/autoupdate.services/libsrc/org/netbeans/updater/ModuleUpdater.java @@ -42,6 +42,7 @@ package org.netbeans.updater; import java.io.*; +import java.lang.reflect.Method; import java.util.*; import java.util.jar.*; @@ -354,6 +355,8 @@ try { jarFile = new JarFile (nbm); Enumeration entries = jarFile.entries(); + List list = new ArrayList (jarFile.size()); + List executableList = new ArrayList (); while( entries.hasMoreElements() ) { JarEntry entry = (JarEntry) entries.nextElement(); checkStop(); @@ -375,14 +378,14 @@ File bckFile = new File( getBackupDirectory (cluster), entry.getName() ); bckFile.getParentFile ().mkdirs (); // System.out.println("Backing up" ); // NOI18N - copyStreams( new FileInputStream( destFile ), new FileOutputStream( bckFile ), -1 ); + copyStreams( new FileInputStream( destFile ), bckFile, -1, list, executableList ); if (!destFile.delete() && isWindows()) { trickyDeleteOnWindows(destFile); } } else { destFile.getParentFile ().mkdirs (); } - bytesRead = copyStreams( jarFile.getInputStream( entry ), new FileOutputStream( destFile ), bytesRead ); + bytesRead = copyStreams( jarFile.getInputStream( entry ), destFile, bytesRead, list, executableList ); UpdaterFrame.setProgressValue( bytesRead ); } } else if ( entry.getName().startsWith( UPDATE_MAIN_DIR )&& @@ -392,10 +395,11 @@ entry.getName().substring(UPDATE_MAIN_DIR.length() + 1) ); destFile.getParentFile ().mkdirs (); hasMainClass = true; - bytesRead = copyStreams( jarFile.getInputStream( entry ), new FileOutputStream( destFile ), bytesRead ); + bytesRead = copyStreams( jarFile.getInputStream( entry ), destFile, bytesRead, list, executableList ); UpdaterFrame.setProgressValue( bytesRead ); } } + fixPermissions(list,executableList); if ( hasMainClass ) { MainConfig mconfig = new MainConfig (getMainDirString (cluster) + UpdateTracking.FILE_SEPARATOR + JVM_PARAMS_FILE, cluster); if (mconfig.isValid()) { @@ -532,6 +536,118 @@ private String getMainDirString (File activeCluster) { return getMainDirectory (activeCluster).getPath (); } + + + private void fixPermissions(List list, List executableList) { + if (!isWindows() && !list.isEmpty()) { + List commands = new ArrayList(); + commands.add("/usr/bin/file"); + commands.addAll(list); + + String filesInfo = readCommandOutput(commands); + List fileExecutables = new ArrayList(); + for (String s : filesInfo.split("(?:\r\n|\n|\r)")) {//split by lines + int colonIndex = s.indexOf(":"); + if (colonIndex != -1) { + String filePath = s.substring(0, colonIndex).trim(); + String fileType = s.substring(colonIndex + 1); + if (fileType.contains("executable") || + fileType.contains("dynamic lib") || + fileType.contains("shared object") || + fileType.contains("shared library") || + fileType.contains("universal binary") || + fileType.contains("shell script")) { + boolean exist = false; + try { + exist = new File(filePath).exists(); + } catch (Exception e) { + } + if(exist) { + fileExecutables.add(filePath); + //System.out.println("Executable [/usr/bin/file]: " + filePath); + } + + } + } + } + List allExecutables = new ArrayList (); + allExecutables.addAll(fileExecutables); + for(String s : executableList) { + if(!allExecutables.contains(s)) { + //System.out.println("Executable [ interpreter ]: " + s); + allExecutables.add(s); + } + } + chmod(allExecutables); + } + } + + private void chmod(List executables) { + if(executables.isEmpty()) { + return ; + } + if(System.getProperty("java.version").startsWith("1.5")) { + // Find chmod + File chmod = new File("/bin/chmod"); // NOI18N + if (!chmod.isFile()) { + chmod = new File("/usr/bin/chmod"); // NOI18N + } + if (chmod.isFile()) { + List commands = new ArrayList(executables.size() + 2); + commands.add(chmod.getAbsolutePath()); + commands.add("a+x"); // NOI18N + commands.addAll(executables); + try { + System.out.println("Executing command : " + commands); + new ProcessBuilder(commands).start().waitFor(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } else { + // Determine if java.io.File.setExecutable method is supported + try { + Method setExecutableMethod = File.class.getMethod("setExecutable", Boolean.TYPE, Boolean.TYPE); + for(String s : executables) { + setExecutableMethod.invoke(new File(s), true, false); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + private String readCommandOutput(List command) { + ProcessBuilder builder = new ProcessBuilder(command); + boolean doRun = true; + StringBuilder sb = new StringBuilder(); + byte[] bytes = new byte[8192]; + int c = 0; + + try { + Process process = builder.start(); + while (doRun) { + try { + Thread.sleep(20); + } catch (InterruptedException e) { + } + try { + process.exitValue(); + doRun = false; + } catch (IllegalThreadStateException e) { + ; // do nothing - the process is still running + } + InputStream is = process.getInputStream(); + while ((c = is.read(bytes)) != -1) { + sb.append(new String(bytes, 0, c)); + } + } + return sb.toString(); + } catch (IOException e) { + e.printStackTrace(); + return new String(); + } + } /** Quotes string correctly, eg. removes all quotes from the string and adds * just one at the start and @@ -564,8 +680,10 @@ * @param progressVal The current progress bar value. If this is * negative, we don't want to update the progress bar. */ - private long copyStreams( InputStream src, OutputStream dest, - long progressVal ) throws java.io.IOException { + private long copyStreams( InputStream src, File destFile, + long progressVal, List list, List executableList ) throws java.io.IOException { + OutputStream dest = new FileOutputStream(destFile); + list.add(destFile.getAbsolutePath()); BufferedInputStream bsrc = new BufferedInputStream( src ); BufferedOutputStream bdest = new BufferedOutputStream( dest ); @@ -574,10 +692,32 @@ int c; byte [] bytes = new byte [8192]; - + boolean read = false; try { while( ( c = bsrc.read(bytes) ) != -1 ) { bdest.write(bytes, 0, c); + if (!read) { + read = true; + if (c >= 4) { // length of ELF header and min length of "#!/X" string + if (bytes[0] == '\177' && + bytes[1] == 'E' && + bytes[2] == 'L' && + bytes[3] == 'F') { + executableList.add(destFile.getAbsolutePath()); + } else if (bytes[0] == '#' && bytes[1] == '!') { + String s = new String(bytes, 0, c); + String[] array = s.split("(?:\r\n|\n|\r)"); + + if (array.length > 0) { + //read the first line only + //allow lines like "#! /bin/sh" + if (array[0].replaceAll("#!(\\s)+/", "#!/").startsWith("#!/")) { + executableList.add(destFile.getAbsolutePath()); + } + } + } + } + } count+=c; if ( count > 8500 ) { if (progressVal >= 0) { diff --git a/autoupdate.services/test/unit/src/org/netbeans/api/autoupdate/data/org-yourorghere-executable-permissions.nbm b/autoupdate.services/test/unit/src/org/netbeans/api/autoupdate/data/org-yourorghere-executable-permissions.nbm new file mode 100644 index 0000000000000000000000000000000000000000..fb9f9ffb04d1753fd768a7bfadaa0c6396502ea2 GIT binary patch literal 5122 zc$~FZOKclO7~aH5DVB^{+CzC9rdx$RV0Z15zTDWwPt_!LoH{{jDT>ziINoL-?(Vv2 z!hr%(ML~!I^iYsE0cwRfR2&dhc^!Hvlpqi{qzVafsf0j+svsf6KfAVfy`FU)B-RrW-VmvLRAFmh^56N()X`RrnJ=BEoc9}X}K8KJg_1aJIl zMV>eWZ|D@(4M|gDY=jT92&)yXDya)Gc5XJy?Pm|ggA-fR)2Z2`#SBukYE#CjIG3Et zr4Y*rLIImeEUJbeq-WD8(KNMYqbiyhLb zYx;svHLHdocF-KrkQ<-h!@~m>G-yo>s$o@Ba9K2Pj0GUStTlD`U&lJ;@o8LXnqpbT zd;{x>WDv6q7F8v~)TMIMw5){h+L9`3V%3mVfCX%>YYJ|N3z$5Vr3zLFdm?=xn@pw- zuq~@WySk1A~BnLs=dhJec=eG z8Y0!wv9plzrwh{v0yk4AWl*V@N#(M+O!}GN zgwO&&pb54T@nF$6)9z(Pn0#h7!Q~2B;h!%VrY*XjxdWmj<|n@$gKu>hn_+ZB^NB(( zn<>rm`Rwoc8(&RLa{RZUNp9Ebjc-0Y68?Vok3Z`-Qum*H?C{j;;W_5zp|P(M->qgY zog2Hf^Ge}7x?NjYcO`dr-Fv|Yzj${!`00h~nZK@`zczO1<7>gUm#?q9dG+|!<4ixb z8K#|ct|Bq^;$qY-7IKS~CDkRm14MVlo_*sU_)NwIuPByl&t;~vLqkIj==B#CqvJEW zyn_(H*yIK2O_ew5+qPO?jCv@DD|HRAvvtWp@MqRBGU`~CQB9ID>kzX*EWO37CgC#~ z4^W8bjfPmk7Cjn>A1z5{9a$(OsqklFy@BD)HSr%F$9B1)qNz1$!R55u9_cL~Sjm&QQ?Pr;5!ov=y!>Ru&E8u@TK8ij%gXl(@F&|z<&o8Xxqdg?ZywXi>e z`u75_m5%hv+3sHZ5JlSMXMf=K{y_HvIV7(E$*pq-XGo@!v43!qp5>rz1_z5bPsNdr zJ%K-h&t!xLSEv(qYi?ajpjmx=9O0N4`adK#QbHKmg zwWH~m1mmu0cKHJ+<6i*n3&D8QryRN`r-isP`76WifcHmi=t6ujOiKVqV_FJ0JU>v- zT?ye&w7rXHb6^b#;*io(#PR3R!AW}(#ytxy?(ORgj9Y`x(#eA2OqyZ(AN!H&9T VXz;aciS4Am4_==^+;i4~{0jqTZ3qAW diff --git a/autoupdate.services/test/unit/src/org/netbeans/modules/autoupdate/services/ExecutablePermissionsTest.java b/autoupdate.services/test/unit/src/org/netbeans/modules/autoupdate/services/ExecutablePermissionsTest.java new file mode 100644 --- /dev/null +++ b/autoupdate.services/test/unit/src/org/netbeans/modules/autoupdate/services/ExecutablePermissionsTest.java @@ -0,0 +1,178 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 1997-2008 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Sun Microsystems, Inc. Portions Copyright 1997-2008 Sun + * Microsystems, Inc. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ +package org.netbeans.modules.autoupdate.services; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; +import org.netbeans.api.autoupdate.UpdateUnit; +import org.netbeans.spi.autoupdate.UpdateItem; +import org.netbeans.spi.autoupdate.UpdateProvider; + +/** + * + * @author Dmitry Lipin + */ +public class ExecutablePermissionsTest extends NbmAdvancedTestCase { + + private UpdateProvider p = null; + private String testModuleVersion = "1.0"; + private String testModuleName = "org.yourorghere.executable.permissions"; + + public ExecutablePermissionsTest(String name) { + super(name); + } + + @Override + protected void setUp() throws IOException, Exception { + super.setUp(); + } + + @Override + public boolean canRun() { + return super.canRun() && !System.getProperty("os.name").startsWith("Windows"); + } + + private String generateExecutablePermissionsModuleElement() { + String res = "\n\n"; + res += "\n"; + res += ""; + return res; + } + + public void testExecutablePermissionsModule() throws IOException { + String os = !org.openide.util.Utilities.isUnix() ? "Windows" : "Unix"; + String catalog = generateCatalog(generateExecutablePermissionsModuleElement()); + + p = createUpdateProvider(catalog); + p.refresh(true); + + Map unitImpls = new HashMap(); + Map updates = p.getUpdateItems(); + assertNotNull("Some modules are installed.", updates); + assertFalse("Some modules are installed.", updates.isEmpty()); + assertTrue(testModuleName + " found in parsed items.", updates.keySet().contains(testModuleName + "_" + testModuleVersion)); + + Map newImpls = UpdateUnitFactory.getDefault().appendUpdateItems(unitImpls, p); + assertNotNull("Some units found.", newImpls); + assertFalse("Some units found.", newImpls.isEmpty()); + + UpdateUnit u1 = newImpls.get(testModuleName); + installUpdateUnit(u1); + File binDir = new File(userDir, "bin"); + File elfFile = new File(binDir, "elf"); + File runFile = new File(binDir, "run.sh"); + File runSpaceFile = new File(binDir, "run_space.sh"); + for (File f : new File[]{elfFile, runFile, runSpaceFile}) { + assertTrue("File " + f + " should exist after module installation", f.exists()); + if (System.getProperty("java.version").startsWith("1.5")) { + File ls = new File("/bin/ls"); + if (!ls.isFile()) { + ls = new File("/usr/bin/ls"); + } + String output = readCommandOutput(ls.getAbsolutePath(), "-la", f.getAbsolutePath()).trim(); + int index = output.indexOf(" "); + assertFalse("Can`t read permissions from output:\n" + output, index == -1); + String permissions = output.substring(0, index); + assertTrue("File " + f + " does not have executable permissions after installation, actual perms : " + permissions, + permissions.matches(".*x.*x.*x.*")); + } else { + Method canExecuteMethod = null; + try { + canExecuteMethod = File.class.getMethod("canExecute", new Class[]{}); + } catch (Exception e) { + assertTrue("java.io.File.canExecute method is not accessible", false); + } + boolean canExecute = false; + try { + canExecute = (Boolean) canExecuteMethod.invoke(f); + } catch (Exception e) { + assertTrue("File " + f + " is not executable after module installation", canExecute); + e.printStackTrace(); + } + } + + } + } + + private String readCommandOutput(String... command) { + ProcessBuilder builder = new ProcessBuilder(command); + boolean doRun = true; + StringBuilder sb = new StringBuilder(); + byte[] bytes = new byte[8192]; + int c = 0; + + try { + Process process = builder.start(); + while (doRun) { + try { + Thread.sleep(20); + } catch (InterruptedException e) { + } + try { + process.exitValue(); + doRun = false; + } catch (IllegalThreadStateException e) { + ; // do nothing - the process is still running + } + InputStream is = process.getInputStream(); + while ((c = is.read(bytes)) != -1) { + sb.append(new String(bytes, 0, c)); + } + } + return sb.toString(); + } catch (IOException e) { + e.printStackTrace(); + return new String(); + } + } +}