diff --git a/hadoop-common-project/hadoop-common/CHANGES.txt b/hadoop-common-project/hadoop-common/CHANGES.txt index 603f47f707a..aae6ddde762 100644 --- a/hadoop-common-project/hadoop-common/CHANGES.txt +++ b/hadoop-common-project/hadoop-common/CHANGES.txt @@ -467,6 +467,9 @@ Release 2.1.0-beta - UNRELEASED HADOOP-9637. Adding Native Fstat for Windows as needed by YARN. (Chuan Liu via cnauroth) + HADOOP-9264. Port change to use Java untar API on Windows from + branch-1-win to trunk. (Chris Nauroth via suresh) + Release 2.0.5-alpha - 06/06/2013 INCOMPATIBLE CHANGES diff --git a/hadoop-common-project/hadoop-common/pom.xml b/hadoop-common-project/hadoop-common/pom.xml index 6c10fe2fea2..263285bcb66 100644 --- a/hadoop-common-project/hadoop-common/pom.xml +++ b/hadoop-common-project/hadoop-common/pom.xml @@ -249,6 +249,11 @@ test-jar test + + org.apache.commons + commons-compress + 1.4 + @@ -395,6 +400,23 @@ + + copy-test-tarballs + process-test-resources + + run + + + + + + + + + + + + pre-site @@ -426,6 +448,7 @@ src/test/all-tests src/test/resources/kdc/ldif/users.ldif src/main/native/src/org/apache/hadoop/io/compress/lz4/lz4.c + src/test/java/org/apache/hadoop/fs/test-untar.tgz diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/FileUtil.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/FileUtil.java index e24f445245c..cb216e96ef8 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/FileUtil.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/FileUtil.java @@ -27,10 +27,13 @@ import java.util.Map; import java.util.jar.Attributes; import java.util.jar.JarOutputStream; import java.util.jar.Manifest; +import java.util.zip.GZIPInputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import org.apache.commons.collections.map.CaseInsensitiveMap; +import org.apache.commons.compress.archivers.tar.TarArchiveEntry; +import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.classification.InterfaceStability; import org.apache.hadoop.conf.Configuration; @@ -609,14 +612,28 @@ public class FileUtil { * @throws IOException */ public static void unTar(File inFile, File untarDir) throws IOException { - if (!untarDir.mkdirs()) { + if (!untarDir.mkdirs()) { if (!untarDir.isDirectory()) { throw new IOException("Mkdirs failed to create " + untarDir); } } - StringBuilder untarCommand = new StringBuilder(); boolean gzipped = inFile.toString().endsWith("gz"); + if(Shell.WINDOWS) { + // Tar is not native to Windows. Use simple Java based implementation for + // tests and simple tar archives + unTarUsingJava(inFile, untarDir, gzipped); + } + else { + // spawn tar utility to untar archive for full fledged unix behavior such + // as resolving symlinks in tar archives + unTarUsingTar(inFile, untarDir, gzipped); + } + } + + private static void unTarUsingTar(File inFile, File untarDir, + boolean gzipped) throws IOException { + StringBuffer untarCommand = new StringBuffer(); if (gzipped) { untarCommand.append(" gzip -dc '"); untarCommand.append(FileUtil.makeShellPath(inFile)); @@ -641,7 +658,62 @@ public class FileUtil { ". Tar process exited with exit code " + exitcode); } } + + private static void unTarUsingJava(File inFile, File untarDir, + boolean gzipped) throws IOException { + InputStream inputStream = null; + if (gzipped) { + inputStream = new BufferedInputStream(new GZIPInputStream( + new FileInputStream(inFile))); + } else { + inputStream = new BufferedInputStream(new FileInputStream(inFile)); + } + TarArchiveInputStream tis = new TarArchiveInputStream(inputStream); + + for (TarArchiveEntry entry = tis.getNextTarEntry(); entry != null;) { + unpackEntries(tis, entry, untarDir); + entry = tis.getNextTarEntry(); + } + } + + private static void unpackEntries(TarArchiveInputStream tis, + TarArchiveEntry entry, File outputDir) throws IOException { + if (entry.isDirectory()) { + File subDir = new File(outputDir, entry.getName()); + if (!subDir.mkdir() && !subDir.isDirectory()) { + throw new IOException("Mkdirs failed to create tar internal dir " + + outputDir); + } + + for (TarArchiveEntry e : entry.getDirectoryEntries()) { + unpackEntries(tis, e, subDir); + } + + return; + } + + File outputFile = new File(outputDir, entry.getName()); + if (!outputDir.exists()) { + if (!outputDir.mkdirs()) { + throw new IOException("Mkdirs failed to create tar internal dir " + + outputDir); + } + } + + int count; + byte data[] = new byte[2048]; + BufferedOutputStream outputStream = new BufferedOutputStream( + new FileOutputStream(outputFile)); + + while ((count = tis.read(data)) != -1) { + outputStream.write(data, 0, count); + } + + outputStream.flush(); + outputStream.close(); + } + /** * Class for creating hardlinks. * Supports Unix, WindXP. diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/TestFileUtil.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/TestFileUtil.java index cf59ef12522..5672508127a 100644 --- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/TestFileUtil.java +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/TestFileUtil.java @@ -563,6 +563,176 @@ public class TestFileUtil { Assert.assertEquals(expected, du); } + @Test (timeout = 30000) + public void testSymlink() throws Exception { + Assert.assertFalse(del.exists()); + del.mkdirs(); + + byte[] data = "testSymLink".getBytes(); + + File file = new File(del, FILE); + File link = new File(del, "_link"); + + //write some data to the file + FileOutputStream os = new FileOutputStream(file); + os.write(data); + os.close(); + + //create the symlink + FileUtil.symLink(file.getAbsolutePath(), link.getAbsolutePath()); + + //ensure that symlink length is correctly reported by Java + Assert.assertEquals(data.length, file.length()); + Assert.assertEquals(data.length, link.length()); + + //ensure that we can read from link. + FileInputStream in = new FileInputStream(link); + long len = 0; + while (in.read() > 0) { + len++; + } + in.close(); + Assert.assertEquals(data.length, len); + } + + /** + * Test that rename on a symlink works as expected. + */ + @Test (timeout = 30000) + public void testSymlinkRenameTo() throws Exception { + Assert.assertFalse(del.exists()); + del.mkdirs(); + + File file = new File(del, FILE); + file.createNewFile(); + File link = new File(del, "_link"); + + // create the symlink + FileUtil.symLink(file.getAbsolutePath(), link.getAbsolutePath()); + + Assert.assertTrue(file.exists()); + Assert.assertTrue(link.exists()); + + File link2 = new File(del, "_link2"); + + // Rename the symlink + Assert.assertTrue(link.renameTo(link2)); + + // Make sure the file still exists + // (NOTE: this would fail on Java6 on Windows if we didn't + // copy the file in FileUtil#symlink) + Assert.assertTrue(file.exists()); + + Assert.assertTrue(link2.exists()); + Assert.assertFalse(link.exists()); + } + + /** + * Test that deletion of a symlink works as expected. + */ + @Test (timeout = 30000) + public void testSymlinkDelete() throws Exception { + Assert.assertFalse(del.exists()); + del.mkdirs(); + + File file = new File(del, FILE); + file.createNewFile(); + File link = new File(del, "_link"); + + // create the symlink + FileUtil.symLink(file.getAbsolutePath(), link.getAbsolutePath()); + + Assert.assertTrue(file.exists()); + Assert.assertTrue(link.exists()); + + // make sure that deleting a symlink works properly + Assert.assertTrue(link.delete()); + Assert.assertFalse(link.exists()); + Assert.assertTrue(file.exists()); + } + + /** + * Test that length on a symlink works as expected. + */ + @Test (timeout = 30000) + public void testSymlinkLength() throws Exception { + Assert.assertFalse(del.exists()); + del.mkdirs(); + + byte[] data = "testSymLinkData".getBytes(); + + File file = new File(del, FILE); + File link = new File(del, "_link"); + + // write some data to the file + FileOutputStream os = new FileOutputStream(file); + os.write(data); + os.close(); + + Assert.assertEquals(0, link.length()); + + // create the symlink + FileUtil.symLink(file.getAbsolutePath(), link.getAbsolutePath()); + + // ensure that File#length returns the target file and link size + Assert.assertEquals(data.length, file.length()); + Assert.assertEquals(data.length, link.length()); + + file.delete(); + Assert.assertFalse(file.exists()); + + if (Shell.WINDOWS && !Shell.isJava7OrAbove()) { + // On Java6 on Windows, we copied the file + Assert.assertEquals(data.length, link.length()); + } else { + // Otherwise, the target file size is zero + Assert.assertEquals(0, link.length()); + } + + link.delete(); + Assert.assertFalse(link.exists()); + } + + private void doUntarAndVerify(File tarFile, File untarDir) + throws IOException { + if (untarDir.exists() && !FileUtil.fullyDelete(untarDir)) { + throw new IOException("Could not delete directory '" + untarDir + "'"); + } + FileUtil.unTar(tarFile, untarDir); + + String parentDir = untarDir.getCanonicalPath() + Path.SEPARATOR + "name"; + File testFile = new File(parentDir + Path.SEPARATOR + "version"); + Assert.assertTrue(testFile.exists()); + Assert.assertTrue(testFile.length() == 0); + String imageDir = parentDir + Path.SEPARATOR + "image"; + testFile = new File(imageDir + Path.SEPARATOR + "fsimage"); + Assert.assertTrue(testFile.exists()); + Assert.assertTrue(testFile.length() == 157); + String currentDir = parentDir + Path.SEPARATOR + "current"; + testFile = new File(currentDir + Path.SEPARATOR + "fsimage"); + Assert.assertTrue(testFile.exists()); + Assert.assertTrue(testFile.length() == 4331); + testFile = new File(currentDir + Path.SEPARATOR + "edits"); + Assert.assertTrue(testFile.exists()); + Assert.assertTrue(testFile.length() == 1033); + testFile = new File(currentDir + Path.SEPARATOR + "fstime"); + Assert.assertTrue(testFile.exists()); + Assert.assertTrue(testFile.length() == 8); + } + + @Test (timeout = 30000) + public void testUntar() throws IOException { + String tarGzFileName = System.getProperty("test.cache.data", + "build/test/cache") + "/test-untar.tgz"; + String tarFileName = System.getProperty("test.cache.data", + "build/test/cache") + "/test-untar.tar"; + String dataDir = System.getProperty("test.build.data", "build/test/data"); + File untarDir = new File(dataDir, "untarDir"); + + doUntarAndVerify(new File(tarGzFileName), untarDir); + doUntarAndVerify(new File(tarFileName), untarDir); + } + @Test (timeout = 30000) public void testCreateJarWithClassPath() throws Exception { // setup test directory for files diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/test-untar.tar b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/test-untar.tar new file mode 100644 index 00000000000..949e985c731 Binary files /dev/null and b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/test-untar.tar differ diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/test-untar.tgz b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/test-untar.tgz new file mode 100644 index 00000000000..9e9ef40f6fd Binary files /dev/null and b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/test-untar.tgz differ