diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/AzureBlobFileSystem.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/AzureBlobFileSystem.java index b4277c2f75..4c24ac8fe3 100644 --- a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/AzureBlobFileSystem.java +++ b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/AzureBlobFileSystem.java @@ -193,6 +193,8 @@ public FSDataOutputStream create(final Path f, final FsPermission permission, fi overwrite, blockSize); + trailingPeriodCheck(f); + Path qualifiedPath = makeQualified(f); performAbfsAuthCheck(FsAction.WRITE, qualifiedPath); @@ -272,6 +274,8 @@ public boolean rename(final Path src, final Path dst) throws IOException { LOG.debug( "AzureBlobFileSystem.rename src: {} dst: {}", src.toString(), dst.toString()); + trailingPeriodCheck(dst); + Path parentFolder = src.getParent(); if (parentFolder == null) { return false; @@ -376,11 +380,38 @@ public FileStatus[] listStatus(final Path f) throws IOException { } } + /** + * Performs a check for (.) until root in the path to throw an exception. + * The purpose is to differentiate between dir/dir1 and dir/dir1. + * Without the exception the behavior seen is dir1. will appear + * to be present without it's actual creation as dir/dir1 and dir/dir1. are + * treated as identical. + * @param path the path to be checked for trailing period (.) + * @throws IllegalArgumentException if the path has a trailing period (.) + */ + private void trailingPeriodCheck(Path path) throws IllegalArgumentException { + while (!path.isRoot()){ + String pathToString = path.toString(); + if (pathToString.length() != 0) { + if (pathToString.charAt(pathToString.length() - 1) == '.') { + throw new IllegalArgumentException( + "ABFS does not allow files or directories to end with a dot."); + } + path = path.getParent(); + } + else { + break; + } + } + } + @Override public boolean mkdirs(final Path f, final FsPermission permission) throws IOException { LOG.debug( "AzureBlobFileSystem.mkdirs path: {} permissions: {}", f, permission); + trailingPeriodCheck(f); + final Path parentFolder = f.getParent(); if (parentFolder == null) { // Cannot create root diff --git a/hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azurebfs/ITestAzureBlobFileSystemListStatus.java b/hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azurebfs/ITestAzureBlobFileSystemListStatus.java index 60e0fbc723..25a1567926 100644 --- a/hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azurebfs/ITestAzureBlobFileSystemListStatus.java +++ b/hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azurebfs/ITestAzureBlobFileSystemListStatus.java @@ -19,6 +19,7 @@ package org.apache.hadoop.fs.azurebfs; import java.io.FileNotFoundException; +import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; @@ -34,6 +35,11 @@ import org.apache.hadoop.fs.Path; import org.apache.hadoop.fs.contract.ContractTestUtils; +import static org.apache.hadoop.fs.contract.ContractTestUtils.assertMkdirs; +import static org.apache.hadoop.fs.contract.ContractTestUtils.createFile; +import static org.apache.hadoop.fs.contract.ContractTestUtils.assertPathExists; +import static org.apache.hadoop.fs.contract.ContractTestUtils.rename; + import static org.apache.hadoop.test.LambdaTestUtils.intercept; /** @@ -169,4 +175,65 @@ private void assertIsFileReference(FileStatus status) { assertFalse("Not a file: " + status, status.isDirectory()); assertTrue("Not a file: " + status, status.isFile()); } + + @Test + public void testMkdirTrailingPeriodDirName() throws IOException { + boolean exceptionThrown = false; + final AzureBlobFileSystem fs = getFileSystem(); + + Path nontrailingPeriodDir = path("testTrailingDir/dir"); + Path trailingPeriodDir = path("testTrailingDir/dir."); + + assertMkdirs(fs, nontrailingPeriodDir); + + try { + fs.mkdirs(trailingPeriodDir); + } + catch(IllegalArgumentException e) { + exceptionThrown = true; + } + assertTrue("Attempt to create file that ended with a dot should" + + " throw IllegalArgumentException", exceptionThrown); + } + + @Test + public void testCreateTrailingPeriodFileName() throws IOException { + boolean exceptionThrown = false; + final AzureBlobFileSystem fs = getFileSystem(); + + Path trailingPeriodFile = path("testTrailingDir/file."); + Path nontrailingPeriodFile = path("testTrailingDir/file"); + + createFile(fs, nontrailingPeriodFile, false, new byte[0]); + assertPathExists(fs, "Trailing period file does not exist", + nontrailingPeriodFile); + + try { + createFile(fs, trailingPeriodFile, false, new byte[0]); + } + catch(IllegalArgumentException e) { + exceptionThrown = true; + } + assertTrue("Attempt to create file that ended with a dot should" + + " throw IllegalArgumentException", exceptionThrown); + } + + @Test + public void testRenameTrailingPeriodFile() throws IOException { + boolean exceptionThrown = false; + final AzureBlobFileSystem fs = getFileSystem(); + + Path nonTrailingPeriodFile = path("testTrailingDir/file"); + Path trailingPeriodFile = path("testTrailingDir/file."); + + createFile(fs, nonTrailingPeriodFile, false, new byte[0]); + try { + rename(fs, nonTrailingPeriodFile, trailingPeriodFile); + } + catch(IllegalArgumentException e) { + exceptionThrown = true; + } + assertTrue("Attempt to create file that ended with a dot should" + + " throw IllegalArgumentException", exceptionThrown); + } }