diff --git a/changelog.html b/changelog.html
index 219bed655b84..57107f8810fc 100644
--- a/changelog.html
+++ b/changelog.html
@@ -58,6 +58,12 @@
Race condition in triggers could cause various NullPointerException
s.
(issue 29790)
+
+ Archiving of large artifacts. Tar implementation cannot handle files having a size >8GB.
+ (issue 10629)
+
+ Allow plugins to augment or replace the plugin manager UI.
+ (PR 1788)
@@ -289,7 +295,7 @@
Since 1.598 overrides of Descriptor.getId
were not correctly handled by form binding, breaking at least the CloudBees Templates plugin.
(issue 26781)
- Reverted in 1.611. Archiving of large artifacts. Tar implementation cannot handle files having a size >8GB.
+ Reverted in 1.611, reimplemented in 1.627. Archiving of large artifacts. Tar implementation cannot handle files having a size >8GB.
(issue 10629)
The queue state was not updated between scheduling builds.
diff --git a/core/pom.xml b/core/pom.xml
index ed8e8cd3083b..41c9fec6696d 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -276,6 +276,11 @@ THE SOFTWARE.
commons-beanutils
1.8.3
+
+ org.apache.commons
+ commons-compress
+ 1.10
+
javax.mail
mail
diff --git a/core/src/main/java/hudson/FilePath.java b/core/src/main/java/hudson/FilePath.java
index ca137d2e1160..07fdc9d17ced 100644
--- a/core/src/main/java/hudson/FilePath.java
+++ b/core/src/main/java/hudson/FilePath.java
@@ -33,7 +33,6 @@
import hudson.model.Computer;
import hudson.model.Item;
import hudson.model.TaskListener;
-import hudson.org.apache.tools.tar.TarInputStream;
import hudson.os.PosixAPI;
import hudson.os.PosixException;
import hudson.remoting.Callable;
@@ -70,7 +69,6 @@
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.types.FileSet;
-import org.apache.tools.tar.TarEntry;
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipFile;
import org.kohsuke.stapler.Stapler;
@@ -120,6 +118,8 @@
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import jenkins.security.MasterToSlaveCallable;
+import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
+import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.jenkinsci.remoting.RoleChecker;
import org.jenkinsci.remoting.RoleSensitive;
@@ -2268,12 +2268,15 @@ private Integer writeToTar(File baseDir, DirScanner scanner, OutputStream out) t
/**
* Reads from a tar stream and stores obtained files to the base dir.
+ * @since TODO supports large files > 10 GB, migration to commons-compress
*/
private void readFromTar(String name, File baseDir, InputStream in) throws IOException {
- TarInputStream t = new TarInputStream(in);
+ TarArchiveInputStream t = new TarArchiveInputStream(in);
+
+ // TarInputStream t = new TarInputStream(in);
try {
- TarEntry te;
- while ((te = t.getNextEntry()) != null) {
+ TarArchiveEntry te;
+ while ((te = t.getNextTarEntry()) != null) {
File f = new File(baseDir,te.getName());
if(te.isDirectory()) {
mkdirs(f);
@@ -2282,8 +2285,7 @@ private void readFromTar(String name, File baseDir, InputStream in) throws IOExc
if (parent != null) mkdirs(parent);
writing(f);
- byte linkFlag = (Byte) LINKFLAG_FIELD.get(te);
- if (linkFlag==TarEntry.LF_SYMLINK) {
+ if (te.isSymbolicLink()) {
new FilePath(f).symlinkTo(te.getLinkName(), TaskListener.NULL);
} else {
IOUtils.copy(t,f);
@@ -2300,8 +2302,6 @@ private void readFromTar(String name, File baseDir, InputStream in) throws IOExc
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // process this later
throw new IOException("Failed to extract "+name,e);
- } catch (IllegalAccessException e) {
- throw new IOException("Failed to extract "+name,e);
} finally {
t.close();
}
@@ -2725,20 +2725,6 @@ public int compare(String o1, String o2) {
}
};
- private static final Field LINKFLAG_FIELD = getTarEntryLinkFlagField();
-
- private static Field getTarEntryLinkFlagField() {
- try {
- Field f = TarEntry.class.getDeclaredField("linkFlag");
- f.setAccessible(true);
- return f;
- } catch (SecurityException e) {
- throw new AssertionError(e);
- } catch (NoSuchFieldException e) {
- throw new AssertionError(e);
- }
- }
-
/**
* Gets the {@link FilePath} representation of the "~" directory
* (User's home directory in the Unix sense) of the given channel.
diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java
index 443716eed586..623de0f13797 100644
--- a/core/src/main/java/hudson/PluginManager.java
+++ b/core/src/main/java/hudson/PluginManager.java
@@ -71,6 +71,7 @@
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpResponses;
import org.kohsuke.stapler.QueryParameter;
+import org.kohsuke.stapler.StaplerOverridable;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exported;
@@ -128,7 +129,7 @@
* @author Kohsuke Kawaguchi
*/
@ExportedBean
-public abstract class PluginManager extends AbstractModelObject implements OnMaster {
+public abstract class PluginManager extends AbstractModelObject implements OnMaster, StaplerOverridable {
/**
* All discovered plugins.
*/
@@ -215,6 +216,16 @@ public Api getApi() {
return new Api(this);
}
+ /**
+ * Find all registered overrides (intended to allow overriding/adding views)
+ * @return List of extensions
+ * @since 1.627
+ */
+ @Override
+ public Collection getOverrides() {
+ return PluginManagerStaplerOverride.all();
+ }
+
/**
* Called immediately after the construction.
* This is a separate method so that code executed from here will see a valid value in
diff --git a/core/src/main/java/hudson/PluginManagerStaplerOverride.java b/core/src/main/java/hudson/PluginManagerStaplerOverride.java
new file mode 100644
index 000000000000..2de4ef90eded
--- /dev/null
+++ b/core/src/main/java/hudson/PluginManagerStaplerOverride.java
@@ -0,0 +1,28 @@
+package hudson;
+
+import jenkins.model.Jenkins;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+/**
+ * Extension point for selectively overriding parts of the {@link PluginManager} views
+ * Anything extending this and registered with an @Extension can replace existing views and define new views.
+ *
+ * It is also possible to add/modify API calls coming via Stapler, but this requires caution.
+ *
+ * In both cases, this is simply done by defining a resource or method that matches the existing one
+ *
+ * @author Sam Van Oort
+ * @since 1.627
+ */
+public abstract class PluginManagerStaplerOverride implements ExtensionPoint {
+
+ /**
+ * Return all implementations of this extension point
+ * @return All implementations of this extension point
+ */
+ public static @Nonnull ExtensionList all() {
+ return ExtensionList.lookup(PluginManagerStaplerOverride.class);
+ }
+}
diff --git a/core/src/main/java/hudson/org/apache/tools/tar/TarInputStream.java b/core/src/main/java/hudson/org/apache/tools/tar/TarInputStream.java
index f2d10c1c3996..d7d8a5b54da3 100644
--- a/core/src/main/java/hudson/org/apache/tools/tar/TarInputStream.java
+++ b/core/src/main/java/hudson/org/apache/tools/tar/TarInputStream.java
@@ -37,8 +37,9 @@
* methods are provided to position at each successive entry in
* the archive, and the read each entry as a normal input stream
* using read().
- *
+ * @deprecated Use {@link org.apache.commons.compress.archivers.tar.TarArchiveInputStream} instead
*/
+@Deprecated
public class TarInputStream extends FilterInputStream {
// CheckStyle:VisibilityModifier OFF - bc
diff --git a/core/src/main/java/hudson/org/apache/tools/tar/TarOutputStream.java b/core/src/main/java/hudson/org/apache/tools/tar/TarOutputStream.java
index f23e44262847..48f4876bd45c 100644
--- a/core/src/main/java/hudson/org/apache/tools/tar/TarOutputStream.java
+++ b/core/src/main/java/hudson/org/apache/tools/tar/TarOutputStream.java
@@ -35,8 +35,11 @@
* The TarOutputStream writes a UNIX tar archive as an OutputStream.
* Methods are provided to put entries, and then write their contents
* by writing to this stream using write().
+ *
+ * @deprecated Use {@link org.apache.commons.compress.archivers.tar.TarArchiveOutputStream} instead
*
*/
+@Deprecated
public class TarOutputStream extends FilterOutputStream {
/** Fail if a long file name is required in the archive. */
public static final int LONGFILE_ERROR = 0;
diff --git a/core/src/main/java/hudson/util/io/TarArchiver.java b/core/src/main/java/hudson/util/io/TarArchiver.java
index 7e7a3dd4f0ec..600a16cba67f 100644
--- a/core/src/main/java/hudson/util/io/TarArchiver.java
+++ b/core/src/main/java/hudson/util/io/TarArchiver.java
@@ -37,6 +37,8 @@
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
+import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
+import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import static org.apache.tools.tar.TarConstants.LF_SYMLINK;
@@ -47,24 +49,17 @@
*/
final class TarArchiver extends Archiver {
private final byte[] buf = new byte[8192];
- private final TarOutputStream tar;
+ private final TarArchiveOutputStream tar;
TarArchiver(OutputStream out) {
- tar = new TarOutputStream(new BufferedOutputStream(out) {
- // TarOutputStream uses TarBuffer internally,
- // which flushes the stream for each block. this creates unnecessary
- // data stream fragmentation, and flush request to a remote, which slows things down.
- @Override
- public void flush() throws IOException {
- // so don't do anything in flush
- }
- });
- tar.setLongFileMode(TarOutputStream.LONGFILE_GNU);
+ tar = new TarArchiveOutputStream(out);
+ tar.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_STAR);
+ tar.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
}
@Override
public void visitSymlink(File link, String target, String relativePath) throws IOException {
- TarEntry e = new TarEntry(relativePath, LF_SYMLINK);
+ TarArchiveEntry e = new TarArchiveEntry(relativePath, LF_SYMLINK);
try {
int mode = IOUtils.mode(link);
if (mode != -1) {
@@ -73,16 +68,11 @@ public void visitSymlink(File link, String target, String relativePath) throws I
} catch (PosixException x) {
// ignore
}
+
+ e.setLinkName(target);
- try {
- StringBuffer linkName = (StringBuffer) LINKNAME_FIELD.get(e);
- linkName.setLength(0);
- linkName.append(target);
- } catch (IllegalAccessException x) {
- throw new IOException("Failed to set linkName", x);
- }
-
- tar.putNextEntry(e);
+ tar.putArchiveEntry(e);
+ tar.closeArchiveEntry();
entriesWritten++;
}
@@ -97,14 +87,14 @@ public void visit(File file, String relativePath) throws IOException {
if(file.isDirectory())
relativePath+='/';
- TarEntry te = new TarEntry(relativePath);
+ TarArchiveEntry te = new TarArchiveEntry(relativePath);
int mode = IOUtils.mode(file);
if (mode!=-1) te.setMode(mode);
te.setModTime(file.lastModified());
if(!file.isDirectory())
te.setSize(file.length());
- tar.putNextEntry(te);
+ tar.putArchiveEntry(te);
if (!file.isDirectory()) {
FileInputStream in = new FileInputStream(file);
@@ -117,25 +107,11 @@ public void visit(File file, String relativePath) throws IOException {
}
}
- tar.closeEntry();
+ tar.closeArchiveEntry();
entriesWritten++;
}
public void close() throws IOException {
tar.close();
}
-
- private static final Field LINKNAME_FIELD = getTarEntryLinkNameField();
-
- private static Field getTarEntryLinkNameField() {
- try {
- Field f = TarEntry.class.getDeclaredField("linkName");
- f.setAccessible(true);
- return f;
- } catch (SecurityException e) {
- throw new AssertionError(e);
- } catch (NoSuchFieldException e) {
- throw new AssertionError(e);
- }
- }
}
diff --git a/core/src/test/java/hudson/FilePathTest.java b/core/src/test/java/hudson/FilePathTest.java
index 801e184906a8..daae3fcf6fc6 100644
--- a/core/src/test/java/hudson/FilePathTest.java
+++ b/core/src/test/java/hudson/FilePathTest.java
@@ -371,7 +371,7 @@ private void checkTarUntarRoundTrip(String filePrefix, long fileSize) throws Exc
// Decompress
FilePath outDir = new FilePath(temp.newFolder(filePrefix + "_out"));
final FilePath outFile = outDir.child(tempFile.getName());
- tmpDirPath.child( filePrefix + ".tar").untar(outDir, TarCompression.NONE);
+ tmpDirPath.child(tarFile.getName()).untar(outDir, TarCompression.NONE);
assertEquals("Result file after the roundtrip differs from the initial file",
new FilePath(tempFile).digest(), outFile.digest());
}
@@ -659,4 +659,35 @@ private InputStream someZippedContent() throws IOException {
// test conflict subdir
src.moveAllChildrenTo(dst);
}
+
+ @Issue("JENKINS-10629")
+ @Test
+ public void testEOFbrokenFlush() throws IOException, InterruptedException {
+ final File srcFolder = temp.newFolder("src");
+ // simulate magic structure with magic sizes:
+ // |- dir/pom.xml (2049)
+ // |- pom.xml (2049)
+ // \- small.tar (1537)
+ final File smallTar = new File(srcFolder, "small.tar");
+ givenSomeContentInFile(smallTar, 1537);
+ final File dir = new File(srcFolder, "dir");
+ dir.mkdirs();
+ final File pomFile = new File(dir, "pom.xml");
+ givenSomeContentInFile(pomFile, 2049);
+ FileUtils.copyFileToDirectory(pomFile, srcFolder);
+
+ final File archive = temp.newFile("archive.tar");
+
+ // Compress archive
+ final FilePath tmpDirPath = new FilePath(srcFolder);
+ int tarred = tmpDirPath.tar(new FileOutputStream(archive), "**");
+ assertEquals("One file should have been compressed", 3, tarred);
+
+ // Decompress
+ final File dstFolder = temp.newFolder("dst");
+ dstFolder.mkdirs();
+ FilePath outDir = new FilePath(dstFolder);
+ // and now fail when flush is bad!
+ tmpDirPath.child("../" + archive.getName()).untar(outDir, TarCompression.NONE);
+ }
}
diff --git a/test/src/main/java/hudson/core/PluginManagerOverrideTest.java b/test/src/main/java/hudson/core/PluginManagerOverrideTest.java
new file mode 100644
index 000000000000..7cf367477830
--- /dev/null
+++ b/test/src/main/java/hudson/core/PluginManagerOverrideTest.java
@@ -0,0 +1,41 @@
+package hudson.core;
+
+import com.gargoylesoftware.htmlunit.html.HtmlPage;
+import hudson.PluginManagerStaplerOverride;
+import org.junit.Rule;
+import org.junit.Test;
+import org.jvnet.hudson.test.JenkinsRule;
+import org.jvnet.hudson.test.TestExtension;
+
+import static org.junit.Assert.*;
+
+
+/**
+ * Verify that the PluginManagerStaplerOverride extensions register and allow safely modifying PluginManager views
+ * @author Sam Van Oort
+ */
+public class PluginManagerOverrideTest {
+
+ @Rule
+ public JenkinsRule j = new JenkinsRule();
+
+ @Test
+ public void testViewOverrides() throws Exception {
+ // Verify extension registered correctly and comes back in overrides
+ assertEquals(1,PluginManagerStaplerOverride.all().size());
+ assertTrue(PluginManagerStaplerOverride.all().get(0) instanceof BasicPluginManagerOverride);
+
+ // Verify we can load untouched resources
+ JenkinsRule.WebClient client = j.createWebClient();
+ assertEquals(200, client.goTo("self/pluginManager/available").getWebResponse().getStatusCode());
+
+ // Verify new view loads
+ HtmlPage p = j.createWebClient().goTo("self/pluginManager/newview");
+ assertEquals("LoremIpsum", p.getElementById("dummyElement").getTextContent());
+ }
+
+ /** Micro-implementation simply to allow adding a view resource */
+ @TestExtension("testViewOverrides")
+ public static class BasicPluginManagerOverride extends PluginManagerStaplerOverride {
+ }
+}
diff --git a/test/src/main/resources/hudson/core/PluginManagerOverrideTest/BasicPluginManagerOverride/newview.jelly b/test/src/main/resources/hudson/core/PluginManagerOverrideTest/BasicPluginManagerOverride/newview.jelly
new file mode 100644
index 000000000000..e8c7438fe76a
--- /dev/null
+++ b/test/src/main/resources/hudson/core/PluginManagerOverrideTest/BasicPluginManagerOverride/newview.jelly
@@ -0,0 +1,6 @@
+
+
+
+
+ LoremIpsum
+
\ No newline at end of file