Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Tar/untar now correctly supports symlinks.
  • Loading branch information
kohsuke committed Mar 10, 2012
1 parent 48daab0 commit d184d53
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 4 deletions.
2 changes: 2 additions & 0 deletions changelog.html
Expand Up @@ -55,6 +55,8 @@
<!-- Record your changes in the trunk here. -->
<div id="trunk" style="display:none"><!--=TRUNK-BEGIN=-->
<ul class=image>
<li class=bug>
tar/untar now correctly handles symlinks.
<li class=bug>
Fixed a bug in the auto-overwrite of bundled plugins on Windows.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-12514">issue 12514</a>)
Expand Down
73 changes: 71 additions & 2 deletions core/src/main/java/hudson/FilePath.java
Expand Up @@ -46,8 +46,9 @@
import hudson.util.HeadBufferingStream;
import hudson.util.FormValidation;
import hudson.util.IOUtils;

import static hudson.Util.*;
import static hudson.util.jna.GNUCLibrary.LIBC;
import static hudson.Util.fixEmpty;
import static hudson.FilePath.TarCompression.GZIP;
import hudson.org.apache.tools.tar.TarInputStream;
import hudson.util.io.Archiver;
Expand Down Expand Up @@ -75,6 +76,7 @@
import java.io.Serializable;
import java.io.Writer;
import java.io.OutputStreamWriter;
import java.lang.reflect.Field;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
Expand Down Expand Up @@ -331,6 +333,15 @@ public void zip(OutputStream os) throws IOException, InterruptedException {
zip(os,(FileFilter)null);
}

public void zip(FilePath dst) throws IOException, InterruptedException {
OutputStream os = dst.write();
try {
zip(os);
} finally {
os.close();
}
}

/**
* Creates a zip file from this directory by using the specified filter,
* and sends the result to the given output stream.
Expand Down Expand Up @@ -529,6 +540,39 @@ public String invoke(File f, VirtualChannel channel) throws IOException {
}));
}

/**
* Creates a symlink to the specified target.
*
* @param target
* The file that the symlink should point to.
* @param listener
* If symlink creation requires a help of an external process, the error will be reported here.
* @since 1.456
*/
public void symlinkTo(final String target, final TaskListener listener) throws IOException, InterruptedException {
act(new FileCallable<Void>() {
public Void invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
Util.createSymlink(f.getParentFile(),target,f.getName(),listener);
return null;
}
});
}

/**
* Resolves symlink, if the given file is a symlink. Otherwise return null.
* <p>
* If the resolution fails, report an error.
*
* @since 1.456
*/
public String readLink() throws IOException, InterruptedException {
return act(new FileCallable<String>() {
public String invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
return Util.resolveSymlink(f);
}
});
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
Expand Down Expand Up @@ -1756,7 +1800,13 @@ private static void readFromTar(String name, File baseDir, InputStream in) throw
File parent = f.getParentFile();
if (parent != null) parent.mkdirs();

IOUtils.copy(t,f);
byte linkFlag = (Byte) LINKFLAG_FIELD.get(te);
if (linkFlag==TarEntry.LF_SYMLINK) {
new FilePath(f).symlinkTo(te.getLinkName(), TaskListener.NULL);
} else {
IOUtils.copy(t,f);
}

f.setLastModified(te.getModTime().getTime());
int mode = te.getMode()&0777;
if(mode!=0 && !Functions.isWindows()) // be defensive
Expand All @@ -1765,6 +1815,11 @@ private static void readFromTar(String name, File baseDir, InputStream in) throw
}
} catch(IOException e) {
throw new IOException2("Failed to extract "+name,e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // process this later
throw new IOException2("Failed to extract "+name,e);
} catch (IllegalAccessException e) {
throw new IOException2("Failed to extract "+name,e);
} finally {
t.close();
}
Expand Down Expand Up @@ -2124,4 +2179,18 @@ public int compare(String o1, String o2) {
return o1.length()-o2.length();
}
};

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);
}
}
}
10 changes: 9 additions & 1 deletion core/src/main/java/hudson/Util.java
Expand Up @@ -1022,6 +1022,14 @@ public static void createSymlink(File baseDir, String targetPath, String symlink
}
}

/**
* @deprecated as of 1.456
* Use {@link #resolveSymlink(File)}
*/
public static String resolveSymlink(File link, TaskListener listener) throws InterruptedException, IOException {
return resolveSymlink(link);
}

/**
* Resolves symlink, if the given file is a symlink. Otherwise return null.
* <p>
Expand All @@ -1030,7 +1038,7 @@ public static void createSymlink(File baseDir, String targetPath, String symlink
* @param listener
* If we rely on an external command to resolve symlink, this is it.
*/
public static String resolveSymlink(File link, TaskListener listener) throws InterruptedException, IOException {
public static String resolveSymlink(File link) throws InterruptedException, IOException {
if(Functions.isWindows()) return null;

String filename = link.getAbsolutePath();
Expand Down
12 changes: 12 additions & 0 deletions core/src/main/java/hudson/util/DirScanner.java
Expand Up @@ -101,6 +101,18 @@ public void scan(File dir, FileVisitor visitor) throws IOException {
DirectoryScanner ds = fs.getDirectoryScanner(new org.apache.tools.ant.Project());
for( String f : ds.getIncludedFiles()) {
File file = new File(dir, f);

if (visitor.understandsSymlink()) {
try {
String target = Util.resolveSymlink(file,TaskListener.NULL);
if (target!=null) {
visitor.visitSymlink(file,target,f);
continue;
}
} catch (InterruptedException e) {
throw (IOException)new InterruptedIOException().initCause(e);
}
}
visitor.visit(file,f);
}
}
Expand Down
23 changes: 23 additions & 0 deletions core/src/test/java/hudson/FilePathTest.java
Expand Up @@ -23,6 +23,8 @@
*/
package hudson;

import hudson.FilePath.TarCompression;
import hudson.model.TaskListener;
import hudson.remoting.LocalChannel;
import hudson.remoting.VirtualChannel;
import hudson.util.IOException2;
Expand Down Expand Up @@ -356,4 +358,25 @@ public void testCopyToWithPermission() throws IOException, InterruptedException
}
}

public void testSymlinkInTar() throws Exception {
if (Functions.isWindows()) return; // can't test on Windows

FilePath tmp = new FilePath(Util.createTempDir());
try {
FilePath in = tmp.child("in");
in.mkdirs();
in.child("a").touch(0);
in.child("b").symlinkTo("a", TaskListener.NULL);

FilePath tar = tmp.child("test.tar");
in.tar(tar.write(), "**/*");

FilePath dst = in.child("dst");
tar.untar(dst, TarCompression.NONE);

assertEquals("a",dst.child("b").readLink());
} finally {
tmp.deleteRecursive();
}
}
}
2 changes: 1 addition & 1 deletion pom.xml
Expand Up @@ -136,7 +136,7 @@ THE SOFTWARE.
<dependency>
<groupId>org.apache.ant</groupId>
<artifactId>ant</artifactId>
<version>1.8.0</version>
<version>1.8.3</version>
</dependency>

<dependency>
Expand Down

0 comments on commit d184d53

Please sign in to comment.