HADOOP-9527. Add symlink support to LocalFileSystem on Windows. Contributed by Arpit Agarwal.

git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1511118 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Chris Nauroth 2013-08-06 20:35:52 +00:00
parent 3e0203a3a4
commit f2942687d4
10 changed files with 199 additions and 83 deletions

View File

@ -347,6 +347,9 @@ Release 2.1.1-beta - UNRELEASED
HADOOP-9806 PortmapInterface should check if the procedure is out-of-range HADOOP-9806 PortmapInterface should check if the procedure is out-of-range
(brandonli) (brandonli)
HADOOP-9527. Add symlink support to LocalFileSystem on Windows.
(Arpit Agarwal via cnauroth)
Release 2.1.0-beta - 2013-08-06 Release 2.1.0-beta - 2013-08-06
INCOMPATIBLE CHANGES INCOMPATIBLE CHANGES

View File

@ -142,7 +142,28 @@ public class FileUtil {
} }
return deleteImpl(dir, true); return deleteImpl(dir, true);
} }
/**
* Returns the target of the given symlink. Returns the empty string if
* the given path does not refer to a symlink or there is an error
* accessing the symlink.
* @param f File representing the symbolic link.
* @return The target of the symbolic link, empty string on error or if not
* a symlink.
*/
public static String readLink(File f) {
/* NB: Use readSymbolicLink in java.nio.file.Path once available. Could
* use getCanonicalPath in File to get the target of the symlink but that
* does not indicate if the given path refers to a symlink.
*/
try {
return Shell.execCommand(
Shell.getReadlinkCommand(f.toString())).trim();
} catch (IOException x) {
return "";
}
}
/* /*
* Pure-Java implementation of "chmod +rwx f". * Pure-Java implementation of "chmod +rwx f".
*/ */
@ -737,15 +758,18 @@ public class FileUtil {
* On Windows, when symlink creation fails due to security * On Windows, when symlink creation fails due to security
* setting, we will log a warning. The return code in this * setting, we will log a warning. The return code in this
* case is 2. * case is 2.
*
* @param target the target for symlink * @param target the target for symlink
* @param linkname the symlink * @param linkname the symlink
* @return value returned by the command * @return 0 on success
*/ */
public static int symLink(String target, String linkname) throws IOException{ public static int symLink(String target, String linkname) throws IOException{
// Run the input paths through Java's File so that they are converted to the // Run the input paths through Java's File so that they are converted to the
// native OS form // native OS form
File targetFile = new File(target); File targetFile = new File(
File linkFile = new File(linkname); Path.getPathWithoutSchemeAndAuthority(new Path(target)).toString());
File linkFile = new File(
Path.getPathWithoutSchemeAndAuthority(new Path(linkname)).toString());
// If not on Java7+, copy a file instead of creating a symlink since // If not on Java7+, copy a file instead of creating a symlink since
// Java6 has close to no support for symlinks on Windows. Specifically // Java6 has close to no support for symlinks on Windows. Specifically
@ -757,9 +781,16 @@ public class FileUtil {
// is symlinked under userlogs and userlogs are generated afterwards). // is symlinked under userlogs and userlogs are generated afterwards).
if (Shell.WINDOWS && !Shell.isJava7OrAbove() && targetFile.isFile()) { if (Shell.WINDOWS && !Shell.isJava7OrAbove() && targetFile.isFile()) {
try { try {
LOG.info("FileUtil#symlink: On Java6, copying file instead " LOG.warn("FileUtil#symlink: On Windows+Java6, copying file instead " +
+ linkname + " -> " + target); "of creating a symlink. Copying " + target + " -> " + linkname);
org.apache.commons.io.FileUtils.copyFile(targetFile, linkFile);
if (!linkFile.getParentFile().exists()) {
LOG.warn("Parent directory " + linkFile.getParent() +
" does not exist.");
return 1;
} else {
org.apache.commons.io.FileUtils.copyFile(targetFile, linkFile);
}
} catch (IOException ex) { } catch (IOException ex) {
LOG.warn("FileUtil#symlink failed to copy the file with error: " LOG.warn("FileUtil#symlink failed to copy the file with error: "
+ ex.getMessage()); + ex.getMessage());
@ -769,10 +800,23 @@ public class FileUtil {
return 0; return 0;
} }
String[] cmd = Shell.getSymlinkCommand(targetFile.getPath(), String[] cmd = Shell.getSymlinkCommand(
linkFile.getPath()); targetFile.toString(),
ShellCommandExecutor shExec = new ShellCommandExecutor(cmd); linkFile.toString());
ShellCommandExecutor shExec;
try { try {
if (Shell.WINDOWS &&
linkFile.getParentFile() != null &&
!new Path(target).isAbsolute()) {
// Relative links on Windows must be resolvable at the time of
// creation. To ensure this we run the shell command in the directory
// of the link.
//
shExec = new ShellCommandExecutor(cmd, linkFile.getParentFile());
} else {
shExec = new ShellCommandExecutor(cmd);
}
shExec.execute(); shExec.execute();
} catch (Shell.ExitCodeException ec) { } catch (Shell.ExitCodeException ec) {
int returnVal = ec.getExitCode(); int returnVal = ec.getExitCode();
@ -795,7 +839,7 @@ public class FileUtil {
} }
return shExec.getExitCode(); return shExec.getExitCode();
} }
/** /**
* Change the permissions on a filename. * Change the permissions on a filename.
* @param filename the name of the file to change * @param filename the name of the file to change

View File

@ -682,31 +682,13 @@ public class RawLocalFileSystem extends FileSystem {
if (createParent) { if (createParent) {
mkdirs(link.getParent()); mkdirs(link.getParent());
} }
// NB: Use createSymbolicLink in java.nio.file.Path once available
try {
Shell.execCommand(Shell.getSymlinkCommand(
Path.getPathWithoutSchemeAndAuthority(target).toString(),
Path.getPathWithoutSchemeAndAuthority(makeAbsolute(link)).toString()));
} catch (IOException x) {
throw new IOException("Unable to create symlink: "+x.getMessage());
}
}
/** // NB: Use createSymbolicLink in java.nio.file.Path once available
* Returns the target of the given symlink. Returns the empty string if int result = FileUtil.symLink(target.toString(),
* the given path does not refer to a symlink or there is an error makeAbsolute(link).toString());
* accessing the symlink. if (result != 0) {
*/ throw new IOException("Error " + result + " creating symlink " +
private String readLink(Path p) { link + " to " + target);
/* NB: Use readSymbolicLink in java.nio.file.Path once available. Could
* use getCanonicalPath in File to get the target of the symlink but that
* does not indicate if the given path refers to a symlink.
*/
try {
final String path = p.toUri().getPath();
return Shell.execCommand(Shell.READ_LINK_COMMAND, path).trim();
} catch (IOException x) {
return "";
} }
} }
@ -729,7 +711,7 @@ public class RawLocalFileSystem extends FileSystem {
} }
private FileStatus getFileLinkStatusInternal(final Path f) throws IOException { private FileStatus getFileLinkStatusInternal(final Path f) throws IOException {
String target = readLink(f); String target = FileUtil.readLink(new File(f.toString()));
try { try {
FileStatus fs = getFileStatus(f); FileStatus fs = getFileStatus(f);

View File

@ -17,6 +17,7 @@
*/ */
package org.apache.hadoop.fs.local; package org.apache.hadoop.fs.local;
import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
@ -28,12 +29,12 @@ import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.AbstractFileSystem; import org.apache.hadoop.fs.AbstractFileSystem;
import org.apache.hadoop.fs.DelegateToFileSystem; import org.apache.hadoop.fs.DelegateToFileSystem;
import org.apache.hadoop.fs.FileStatus; import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileUtil;
import org.apache.hadoop.fs.FsConstants; import org.apache.hadoop.fs.FsConstants;
import org.apache.hadoop.fs.FsServerDefaults; import org.apache.hadoop.fs.FsServerDefaults;
import org.apache.hadoop.fs.Path; import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.RawLocalFileSystem; import org.apache.hadoop.fs.RawLocalFileSystem;
import org.apache.hadoop.fs.permission.FsPermission; import org.apache.hadoop.fs.permission.FsPermission;
import org.apache.hadoop.util.Shell;
/** /**
* The RawLocalFs implementation of AbstractFileSystem. * The RawLocalFs implementation of AbstractFileSystem.
@ -75,47 +76,29 @@ public class RawLocalFs extends DelegateToFileSystem {
@Override @Override
public boolean supportsSymlinks() { public boolean supportsSymlinks() {
return true; return true;
} }
@Override @Override
public void createSymlink(Path target, Path link, boolean createParent) public void createSymlink(Path target, Path link, boolean createParent)
throws IOException { throws IOException {
final String targetScheme = target.toUri().getScheme(); final String targetScheme = target.toUri().getScheme();
if (targetScheme != null && !"file".equals(targetScheme)) { if (targetScheme != null && !"file".equals(targetScheme)) {
throw new IOException("Unable to create symlink to non-local file "+ throw new IOException("Unable to create symlink to non-local file "+
"system: "+target.toString()); "system: "+target.toString());
} }
if (createParent) { if (createParent) {
mkdir(link.getParent(), FsPermission.getDirDefault(), true); mkdir(link.getParent(), FsPermission.getDirDefault(), true);
} }
// NB: Use createSymbolicLink in java.nio.file.Path once available // NB: Use createSymbolicLink in java.nio.file.Path once available
try { int result = FileUtil.symLink(target.toString(), link.toString());
Shell.execCommand(Shell.getSymlinkCommand( if (result != 0) {
Path.getPathWithoutSchemeAndAuthority(target).toString(), throw new IOException("Error " + result + " creating symlink " +
Path.getPathWithoutSchemeAndAuthority(link).toString())); link + " to " + target);
} catch (IOException x) {
throw new IOException("Unable to create symlink: "+x.getMessage());
} }
} }
/**
* Returns the target of the given symlink. Returns the empty string if
* the given path does not refer to a symlink or there is an error
* acessing the symlink.
*/
private String readLink(Path p) {
/* NB: Use readSymbolicLink in java.nio.file.Path once available. Could
* use getCanonicalPath in File to get the target of the symlink but that
* does not indicate if the given path refers to a symlink.
*/
try {
final String path = p.toUri().getPath();
return Shell.execCommand(Shell.READ_LINK_COMMAND, path).trim();
} catch (IOException x) {
return "";
}
}
/** /**
* Return a FileStatus representing the given path. If the path refers * Return a FileStatus representing the given path. If the path refers
* to a symlink return a FileStatus representing the link rather than * to a symlink return a FileStatus representing the link rather than
@ -123,7 +106,7 @@ public class RawLocalFs extends DelegateToFileSystem {
*/ */
@Override @Override
public FileStatus getFileLinkStatus(final Path f) throws IOException { public FileStatus getFileLinkStatus(final Path f) throws IOException {
String target = readLink(f); String target = FileUtil.readLink(new File(f.toString()));
try { try {
FileStatus fs = getFileStatus(f); FileStatus fs = getFileStatus(f);
// If f refers to a regular file or directory // If f refers to a regular file or directory

View File

@ -123,6 +123,12 @@ abstract public class Shell {
: new String[] { "ln", "-s", target, link }; : new String[] { "ln", "-s", target, link };
} }
/** Return a command to read the target of the a symbolic link*/
public static String[] getReadlinkCommand(String link) {
return WINDOWS ? new String[] { WINUTILS, "readlink", link }
: new String[] { "readlink", link };
}
/** Return a command for determining if process with specified pid is alive. */ /** Return a command for determining if process with specified pid is alive. */
public static String[] getCheckProcessIsAliveCommand(String pid) { public static String[] getCheckProcessIsAliveCommand(String pid) {
return Shell.WINDOWS ? return Shell.WINDOWS ?

View File

@ -71,7 +71,8 @@ public abstract class FSTestWrapper implements FSWrapper {
public String getAbsoluteTestRootDir() throws IOException { public String getAbsoluteTestRootDir() throws IOException {
if (absTestRootDir == null) { if (absTestRootDir == null) {
if (testRootDir.startsWith("/")) { Path testRootPath = new Path(testRootDir);
if (testRootPath.isAbsolute()) {
absTestRootDir = testRootDir; absTestRootDir = testRootDir;
} else { } else {
absTestRootDir = getWorkingDirectory().toString() + "/" absTestRootDir = getWorkingDirectory().toString() + "/"

View File

@ -20,13 +20,10 @@ package org.apache.hadoop.fs;
import java.io.*; import java.io.*;
import java.net.URI; import java.net.URI;
import java.util.EnumSet; import java.util.EnumSet;
import org.apache.hadoop.fs.FileContext;
import org.apache.hadoop.fs.Options.CreateOpts; import org.apache.hadoop.fs.Options.CreateOpts;
import org.apache.hadoop.fs.Options.Rename; import org.apache.hadoop.fs.Options.Rename;
import org.apache.hadoop.fs.permission.FsPermission; import org.apache.hadoop.fs.permission.FsPermission;
import org.apache.hadoop.fs.CreateFlag;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.test.GenericTestUtils; import org.apache.hadoop.test.GenericTestUtils;
import static org.junit.Assert.*; import static org.junit.Assert.*;
@ -51,6 +48,13 @@ public abstract class SymlinkBaseTest {
abstract protected String testBaseDir2() throws IOException; abstract protected String testBaseDir2() throws IOException;
abstract protected URI testURI(); abstract protected URI testURI();
// Returns true if the filesystem is emulating symlink support. Certain
// checks will be bypassed if that is the case.
//
protected boolean emulatingSymlinksOnWindows() {
return false;
}
protected IOException unwrapException(IOException e) { protected IOException unwrapException(IOException e) {
return e; return e;
} }
@ -156,8 +160,11 @@ public abstract class SymlinkBaseTest {
@Test(timeout=10000) @Test(timeout=10000)
/** Try to create a directory given a path that refers to a symlink */ /** Try to create a directory given a path that refers to a symlink */
public void testMkdirExistingLink() throws IOException { public void testMkdirExistingLink() throws IOException {
Path file = new Path(testBaseDir1() + "/targetFile");
createAndWriteFile(file);
Path dir = new Path(testBaseDir1()+"/link"); Path dir = new Path(testBaseDir1()+"/link");
wrapper.createSymlink(new Path("/doesNotExist"), dir, false); wrapper.createSymlink(file, dir, false);
try { try {
wrapper.mkdir(dir, FileContext.DEFAULT_PERM, false); wrapper.mkdir(dir, FileContext.DEFAULT_PERM, false);
fail("Created a dir where a symlink exists"); fail("Created a dir where a symlink exists");
@ -224,6 +231,7 @@ public abstract class SymlinkBaseTest {
@Test(timeout=10000) @Test(timeout=10000)
/** Stat a link to a file */ /** Stat a link to a file */
public void testStatLinkToFile() throws IOException { public void testStatLinkToFile() throws IOException {
assumeTrue(!emulatingSymlinksOnWindows());
Path file = new Path(testBaseDir1()+"/file"); Path file = new Path(testBaseDir1()+"/file");
Path linkToFile = new Path(testBaseDir1()+"/linkToFile"); Path linkToFile = new Path(testBaseDir1()+"/linkToFile");
createAndWriteFile(file); createAndWriteFile(file);
@ -232,8 +240,7 @@ public abstract class SymlinkBaseTest {
assertTrue(wrapper.isSymlink(linkToFile)); assertTrue(wrapper.isSymlink(linkToFile));
assertTrue(wrapper.isFile(linkToFile)); assertTrue(wrapper.isFile(linkToFile));
assertFalse(wrapper.isDir(linkToFile)); assertFalse(wrapper.isDir(linkToFile));
assertEquals(file.toUri().getPath(), assertEquals(file, wrapper.getLinkTarget(linkToFile));
wrapper.getLinkTarget(linkToFile).toString());
// The local file system does not fully resolve the link // The local file system does not fully resolve the link
// when obtaining the file status // when obtaining the file status
if (!"file".equals(getScheme())) { if (!"file".equals(getScheme())) {
@ -277,8 +284,7 @@ public abstract class SymlinkBaseTest {
assertFalse(wrapper.isFile(linkToDir)); assertFalse(wrapper.isFile(linkToDir));
assertTrue(wrapper.isDir(linkToDir)); assertTrue(wrapper.isDir(linkToDir));
assertEquals(dir.toUri().getPath(), assertEquals(dir, wrapper.getLinkTarget(linkToDir));
wrapper.getLinkTarget(linkToDir).toString());
} }
@Test(timeout=10000) @Test(timeout=10000)
@ -351,6 +357,12 @@ public abstract class SymlinkBaseTest {
/* Assert that the given link to a file behaves as expected. */ /* Assert that the given link to a file behaves as expected. */
private void checkLink(Path linkAbs, Path expectedTarget, Path targetQual) private void checkLink(Path linkAbs, Path expectedTarget, Path targetQual)
throws IOException { throws IOException {
// If we are emulating symlinks then many of these checks will fail
// so we skip them.
//
assumeTrue(!emulatingSymlinksOnWindows());
Path dir = new Path(testBaseDir1()); Path dir = new Path(testBaseDir1());
// isFile/Directory // isFile/Directory
assertTrue(wrapper.isFile(linkAbs)); assertTrue(wrapper.isFile(linkAbs));
@ -400,7 +412,7 @@ public abstract class SymlinkBaseTest {
failureExpected = false; failureExpected = false;
} }
try { try {
readFile(new Path(getScheme()+"://"+testBaseDir1()+"/linkToFile")); readFile(new Path(getScheme()+":///"+testBaseDir1()+"/linkToFile"));
assertFalse(failureExpected); assertFalse(failureExpected);
} catch (Exception e) { } catch (Exception e) {
if (!failureExpected) { if (!failureExpected) {
@ -646,6 +658,7 @@ public abstract class SymlinkBaseTest {
@Test(timeout=10000) @Test(timeout=10000)
/** Create symlink through a symlink */ /** Create symlink through a symlink */
public void testCreateLinkViaLink() throws IOException { public void testCreateLinkViaLink() throws IOException {
assumeTrue(!emulatingSymlinksOnWindows());
Path dir1 = new Path(testBaseDir1()); Path dir1 = new Path(testBaseDir1());
Path file = new Path(testBaseDir1(), "file"); Path file = new Path(testBaseDir1(), "file");
Path linkToDir = new Path(testBaseDir2(), "linkToDir"); Path linkToDir = new Path(testBaseDir2(), "linkToDir");
@ -688,6 +701,7 @@ public abstract class SymlinkBaseTest {
@Test(timeout=10000) @Test(timeout=10000)
/** Test create symlink using the same path */ /** Test create symlink using the same path */
public void testCreateLinkTwice() throws IOException { public void testCreateLinkTwice() throws IOException {
assumeTrue(!emulatingSymlinksOnWindows());
Path file = new Path(testBaseDir1(), "file"); Path file = new Path(testBaseDir1(), "file");
Path link = new Path(testBaseDir1(), "linkToFile"); Path link = new Path(testBaseDir1(), "linkToFile");
createAndWriteFile(file); createAndWriteFile(file);
@ -783,7 +797,7 @@ public abstract class SymlinkBaseTest {
Path linkToDir = new Path(testBaseDir2(), "linkToDir"); Path linkToDir = new Path(testBaseDir2(), "linkToDir");
Path fileViaLink = new Path(linkToDir, "test/file"); Path fileViaLink = new Path(linkToDir, "test/file");
// Symlink to .. is not a problem since the .. is squashed early // Symlink to .. is not a problem since the .. is squashed early
assertEquals(testBaseDir1(), dotDot.toString()); assertEquals(new Path(testBaseDir1()), dotDot);
createAndWriteFile(file); createAndWriteFile(file);
wrapper.createSymlink(dotDot, linkToDir, false); wrapper.createSymlink(dotDot, linkToDir, false);
readFile(fileViaLink); readFile(fileViaLink);
@ -876,7 +890,8 @@ public abstract class SymlinkBaseTest {
assertFalse(wrapper.exists(linkViaLink)); assertFalse(wrapper.exists(linkViaLink));
// Check that we didn't rename the link target // Check that we didn't rename the link target
assertTrue(wrapper.exists(file)); assertTrue(wrapper.exists(file));
assertTrue(wrapper.getFileLinkStatus(linkNewViaLink).isSymlink()); assertTrue(wrapper.getFileLinkStatus(linkNewViaLink).isSymlink() ||
emulatingSymlinksOnWindows());
readFile(linkNewViaLink); readFile(linkNewViaLink);
} }
@ -1014,7 +1029,8 @@ public abstract class SymlinkBaseTest {
createAndWriteFile(file); createAndWriteFile(file);
wrapper.createSymlink(file, link1, false); wrapper.createSymlink(file, link1, false);
wrapper.rename(link1, link2); wrapper.rename(link1, link2);
assertTrue(wrapper.getFileLinkStatus(link2).isSymlink()); assertTrue(wrapper.getFileLinkStatus(link2).isSymlink() ||
emulatingSymlinksOnWindows());
readFile(link2); readFile(link2);
readFile(file); readFile(file);
assertFalse(wrapper.exists(link1)); assertFalse(wrapper.exists(link1));
@ -1038,8 +1054,11 @@ public abstract class SymlinkBaseTest {
} }
wrapper.rename(link, file1, Rename.OVERWRITE); wrapper.rename(link, file1, Rename.OVERWRITE);
assertFalse(wrapper.exists(link)); assertFalse(wrapper.exists(link));
assertTrue(wrapper.getFileLinkStatus(file1).isSymlink());
assertEquals(file2, wrapper.getLinkTarget(file1)); if (!emulatingSymlinksOnWindows()) {
assertTrue(wrapper.getFileLinkStatus(file1).isSymlink());
assertEquals(file2, wrapper.getLinkTarget(file1));
}
} }
@Test(timeout=10000) @Test(timeout=10000)
@ -1078,16 +1097,21 @@ public abstract class SymlinkBaseTest {
@Test(timeout=10000) @Test(timeout=10000)
/** Rename a symlink to itself */ /** Rename a symlink to itself */
public void testRenameSymlinkToItself() throws IOException { public void testRenameSymlinkToItself() throws IOException {
Path file = new Path(testBaseDir1(), "file");
createAndWriteFile(file);
Path link = new Path(testBaseDir1(), "linkToFile1"); Path link = new Path(testBaseDir1(), "linkToFile1");
wrapper.createSymlink(new Path("/doestNotExist"), link, false); wrapper.createSymlink(file, link, false);
try { try {
wrapper.rename(link, link); wrapper.rename(link, link);
fail("Failed to get expected IOException");
} catch (IOException e) { } catch (IOException e) {
assertTrue(unwrapException(e) instanceof FileAlreadyExistsException); assertTrue(unwrapException(e) instanceof FileAlreadyExistsException);
} }
// Fails with overwrite as well // Fails with overwrite as well
try { try {
wrapper.rename(link, link, Rename.OVERWRITE); wrapper.rename(link, link, Rename.OVERWRITE);
fail("Failed to get expected IOException");
} catch (IOException e) { } catch (IOException e) {
assertTrue(unwrapException(e) instanceof FileAlreadyExistsException); assertTrue(unwrapException(e) instanceof FileAlreadyExistsException);
} }
@ -1096,6 +1120,7 @@ public abstract class SymlinkBaseTest {
@Test(timeout=10000) @Test(timeout=10000)
/** Rename a symlink */ /** Rename a symlink */
public void testRenameSymlink() throws IOException { public void testRenameSymlink() throws IOException {
assumeTrue(!emulatingSymlinksOnWindows());
Path file = new Path(testBaseDir1(), "file"); Path file = new Path(testBaseDir1(), "file");
Path link1 = new Path(testBaseDir1(), "linkToFile1"); Path link1 = new Path(testBaseDir1(), "linkToFile1");
Path link2 = new Path(testBaseDir1(), "linkToFile2"); Path link2 = new Path(testBaseDir1(), "linkToFile2");
@ -1193,6 +1218,7 @@ public abstract class SymlinkBaseTest {
@Test(timeout=10000) @Test(timeout=10000)
/** Test rename the symlink's target */ /** Test rename the symlink's target */
public void testRenameLinkTarget() throws IOException { public void testRenameLinkTarget() throws IOException {
assumeTrue(!emulatingSymlinksOnWindows());
Path file = new Path(testBaseDir1(), "file"); Path file = new Path(testBaseDir1(), "file");
Path fileNew = new Path(testBaseDir1(), "fileNew"); Path fileNew = new Path(testBaseDir1(), "fileNew");
Path link = new Path(testBaseDir1(), "linkToFile"); Path link = new Path(testBaseDir1(), "linkToFile");

View File

@ -30,6 +30,7 @@ import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import org.apache.hadoop.fs.permission.FsPermission; import org.apache.hadoop.fs.permission.FsPermission;
import org.apache.hadoop.util.Shell;
import org.junit.Test; import org.junit.Test;
/** /**
@ -61,6 +62,16 @@ abstract public class TestSymlinkLocalFS extends SymlinkBaseTest {
} }
} }
@Override
protected boolean emulatingSymlinksOnWindows() {
// Java 6 on Windows has very poor symlink support. Specifically
// Specifically File#length and File#renameTo do not work as expected.
// (see HADOOP-9061 for additional details)
// Hence some symlink tests will be skipped.
//
return (Shell.WINDOWS && !Shell.isJava7OrAbove());
}
@Override @Override
public void testCreateDanglingLink() throws IOException { public void testCreateDanglingLink() throws IOException {
// Dangling symlinks are not supported on Windows local file system. // Dangling symlinks are not supported on Windows local file system.
@ -171,6 +182,7 @@ abstract public class TestSymlinkLocalFS extends SymlinkBaseTest {
* file scheme (eg file://host/tmp/test). * file scheme (eg file://host/tmp/test).
*/ */
public void testGetLinkStatusPartQualTarget() throws IOException { public void testGetLinkStatusPartQualTarget() throws IOException {
assumeTrue(!emulatingSymlinksOnWindows());
Path fileAbs = new Path(testBaseDir1()+"/file"); Path fileAbs = new Path(testBaseDir1()+"/file");
Path fileQual = new Path(testURI().toString(), fileAbs); Path fileQual = new Path(testURI().toString(), fileAbs);
Path dir = new Path(testBaseDir1()); Path dir = new Path(testBaseDir1());
@ -205,4 +217,14 @@ abstract public class TestSymlinkLocalFS extends SymlinkBaseTest {
// Excpected. // Excpected.
} }
} }
/** Test create symlink to . */
@Override
public void testCreateLinkToDot() throws IOException {
try {
super.testCreateLinkToDot();
} catch (IllegalArgumentException iae) {
// Expected.
}
}
} }

View File

@ -17,8 +17,13 @@
*/ */
package org.apache.hadoop.fs; package org.apache.hadoop.fs;
import org.apache.hadoop.util.Shell;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import java.io.IOException;
import static org.junit.Assume.assumeTrue;
public class TestSymlinkLocalFSFileContext extends TestSymlinkLocalFS { public class TestSymlinkLocalFSFileContext extends TestSymlinkLocalFS {
@BeforeClass @BeforeClass
@ -27,4 +32,9 @@ public class TestSymlinkLocalFSFileContext extends TestSymlinkLocalFS {
wrapper = new FileContextTestWrapper(context); wrapper = new FileContextTestWrapper(context);
} }
@Override
public void testRenameFileWithDestParentSymlink() throws IOException {
assumeTrue(!Shell.WINDOWS);
super.testRenameFileWithDestParentSymlink();
}
} }

View File

@ -17,13 +17,20 @@
*/ */
package org.apache.hadoop.fs; package org.apache.hadoop.fs;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Options.Rename;
import org.apache.hadoop.util.Shell;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
public class TestSymlinkLocalFSFileSystem extends TestSymlinkLocalFS { public class TestSymlinkLocalFSFileSystem extends TestSymlinkLocalFS {
@BeforeClass @BeforeClass
@ -54,4 +61,36 @@ public class TestSymlinkLocalFSFileSystem extends TestSymlinkLocalFS {
@Override @Override
@Test(timeout=1000) @Test(timeout=1000)
public void testAccessFileViaInterSymlinkAbsTarget() throws IOException {} public void testAccessFileViaInterSymlinkAbsTarget() throws IOException {}
@Override
public void testRenameFileWithDestParentSymlink() throws IOException {
assumeTrue(!Shell.WINDOWS);
super.testRenameFileWithDestParentSymlink();
}
@Override
@Test(timeout=10000)
/** Rename a symlink to itself */
public void testRenameSymlinkToItself() throws IOException {
Path file = new Path(testBaseDir1(), "file");
createAndWriteFile(file);
Path link = new Path(testBaseDir1(), "linkToFile1");
wrapper.createSymlink(file, link, false);
try {
wrapper.rename(link, link);
fail("Failed to get expected IOException");
} catch (IOException e) {
assertTrue(unwrapException(e) instanceof FileAlreadyExistsException);
}
// Fails with overwrite as well
try {
wrapper.rename(link, link, Rename.OVERWRITE);
fail("Failed to get expected IOException");
} catch (IOException e) {
// Todo: Fix this test when HADOOP-9819 is fixed.
assertTrue(unwrapException(e) instanceof FileAlreadyExistsException ||
unwrapException(e) instanceof FileNotFoundException);
}
}
} }