提交 52066f64 编写于 作者: K kohsuke

added symlink resolution code

git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@24373 71c3de6d-444a-0410-be80-ed276b4c234a
上级 6e2d9650
......@@ -90,6 +90,8 @@ import java.util.regex.Pattern;
import java.nio.charset.Charset;
import com.sun.jna.Native;
import com.sun.jna.Memory;
import com.sun.jna.NativeLong;
/**
* Various utility methods that don't have more proper home.
......@@ -958,39 +960,77 @@ public class Util {
* If there's a prior symlink at baseDir+symlinkPath, it will be overwritten.
*/
public static void createSymlink(File baseDir, String targetPath, String symlinkPath, TaskListener listener) throws InterruptedException {
if(!isWindows() && !NO_SYMLINK) {
try {
// if a file or a directory exists here, delete it first.
// try simple delete first (whether exists() or not, as it may be symlink pointing
// to non-existent target), but fallback to "rm -rf" to delete non-empty dir.
File symlinkFile = new File(baseDir, symlinkPath);
if (!symlinkFile.delete() && symlinkFile.exists())
// ignore a failure.
new LocalProc(new String[]{"rm","-rf", symlinkPath},new String[0],listener.getLogger(), baseDir).join();
int r;
if (!SYMLINK_ESCAPEHATCH) {
try {
r = GNUCLibrary.LIBC.symlink(targetPath,symlinkFile.getAbsolutePath());
if (r!=0)
r = Native.getLastError();
} catch (LinkageError e) {
// if JNA is unavailable, fall back.
// we still prefer to try JNA first as PosixAPI supports even smaller platforms.
r = PosixAPI.get().symlink(targetPath,symlinkFile.getAbsolutePath());
}
} else // escape hatch, until we know that the above works well.
r = new LocalProc(new String[]{
"ln","-s", targetPath, symlinkPath},
new String[0],listener.getLogger(), baseDir).join();
if(r!=0)
listener.getLogger().println("ln failed: "+r);
} catch (IOException e) {
PrintStream log = listener.getLogger();
log.println("ln failed");
Util.displayIOException(e,listener);
e.printStackTrace( log );
if(isWindows() || NO_SYMLINK) return;
try {
// if a file or a directory exists here, delete it first.
// try simple delete first (whether exists() or not, as it may be symlink pointing
// to non-existent target), but fallback to "rm -rf" to delete non-empty dir.
File symlinkFile = new File(baseDir, symlinkPath);
if (!symlinkFile.delete() && symlinkFile.exists())
// ignore a failure.
new LocalProc(new String[]{"rm","-rf", symlinkPath},new String[0],listener.getLogger(), baseDir).join();
int r;
if (!SYMLINK_ESCAPEHATCH) {
try {
r = GNUCLibrary.LIBC.symlink(targetPath,symlinkFile.getAbsolutePath());
if (r!=0)
r = Native.getLastError();
} catch (LinkageError e) {
// if JNA is unavailable, fall back.
// we still prefer to try JNA first as PosixAPI supports even smaller platforms.
r = PosixAPI.get().symlink(targetPath,symlinkFile.getAbsolutePath());
}
} else // escape hatch, until we know that the above works well.
r = new LocalProc(new String[]{
"ln","-s", targetPath, symlinkPath},
new String[0],listener.getLogger(), baseDir).join();
if(r!=0)
listener.getLogger().println("ln failed: "+r);
} catch (IOException e) {
PrintStream log = listener.getLogger();
log.println("ln failed");
Util.displayIOException(e,listener);
e.printStackTrace( log );
}
}
/**
* Resolves symlink, if the given file is a symlink. Otherwise return null.
* <p>
* If the resolution fails, report an error.
*
* @param listener
* If we rely on an external command to resolve symlink, this is it.
* (TODO: try readlink(1) available on some platforms)
*/
public static String resolveSymlink(File link, TaskListener listener) throws InterruptedException, IOException {
if(isWindows()) return null;
String filename = link.getAbsolutePath();
try {
for (int sz=512; sz < 65536; sz*=2) {
Memory m = new Memory(sz);
int r = GNUCLibrary.LIBC.readlink(filename,m,new NativeLong(sz));
if (r<0) {
if (r==22/*EINVAL --- but is this really portable?*/)
return null; // this means it's not a symlink
throw new IOException("Failed to readlink "+link+" error="+Native.getLastError());
}
if (r==sz)
continue; // buffer too small
byte[] buf = new byte[r];
m.read(0,buf,0,r);
return new String(buf);
}
// something is wrong. It can't be this long!
throw new IOException("Symlink too long: "+link);
} catch (LinkageError e) {
// if JNA is unavailable, fall back.
// we still prefer to try JNA first as PosixAPI supports even smaller platforms.
return PosixAPI.get().readlink(filename);
}
}
......
......@@ -27,6 +27,8 @@ import com.sun.jna.Library;
import com.sun.jna.StringArray;
import com.sun.jna.Pointer;
import com.sun.jna.Native;
import com.sun.jna.Memory;
import com.sun.jna.NativeLong;
import com.sun.jna.ptr.IntByReference;
import org.jvnet.libpam.impl.CLibrary.passwd;
......@@ -87,5 +89,15 @@ public interface GNUCLibrary extends Library {
*/
int symlink(String oldname, String newname);
/**
* Read a symlink. The name will be copied into the specified memory, and returns the number of
* bytes copied. The string is not null-terminated.
*
* @return
* if the return value equals size, the caller needs to retry with a bigger buffer.
* If -1, error.
*/
int readlink(String filename, Memory buffer, NativeLong size);
public static final GNUCLibrary LIBC = (GNUCLibrary) Native.loadLibrary("c",GNUCLibrary.class);
}
......@@ -28,6 +28,10 @@ import junit.framework.TestCase;
import java.util.Map;
import java.util.HashMap;
import java.util.Locale;
import java.io.File;
import hudson.model.Hudson;
import hudson.util.StreamTaskListener;
/**
* @author Kohsuke Kawaguchi
......@@ -138,4 +142,25 @@ public class UtilTest extends TestCase {
assertEquals("Parsing empty string did not return the default value", 10, Util.tryParseNumber("", 10).intValue());
assertEquals("Parsing null string did not return the default value", 10, Util.tryParseNumber(null, 10).intValue());
}
public void testSymlink() throws Exception {
if (Hudson.isWindows()) return;
StreamTaskListener l = new StreamTaskListener(System.out);
File d = Util.createTempDir();
try {
new FilePath(new File(d, "a")).touch(0);
Util.createSymlink(d,"a","x", l);
assertEquals("a",Util.resolveSymlink(new File(d,"x"),l));
// test a long name
StringBuilder buf = new StringBuilder(768);
for( int i=0; i<768; i++)
buf.append('0'+(i%10));
Util.createSymlink(d,buf.toString(),"x", l);
assertEquals(buf.toString(),Util.resolveSymlink(new File(d,"x"),l));
} finally {
Util.deleteRecursive(d);
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册