package io.eguan.utils; /* * #%L * Project eguan * %% * Copyright (C) 2012 - 2017 Oodrive * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import io.eguan.utils.RunCmdUtils; import io.eguan.utils.unix.UnixFsFile; import io.eguan.utils.unix.UnixMount; import io.eguan.utils.unix.UnixUser; import java.io.File; import java.io.IOException; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; import java.nio.file.attribute.PosixFilePermission; import java.util.HashSet; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import junit.framework.AssertionFailedError; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; /** * Test the class {@link Files}. * * @author oodrive * @author llambert * */ public final class TestFiles { private static final String TEMP_PREFIX = "files-"; private static final String TEMP_SUFFIX = ".tmp"; /* * Recursive delete. */ /** * Delete success. * * @throws IOException */ @Test public void testDeleteRecursive() throws IOException { // Create file tree final Path tempD1 = Files.createTempDirectory(TEMP_PREFIX); final Path tempF1 = Files.createTempFile(tempD1, TEMP_PREFIX, TEMP_SUFFIX); final Path tempD2 = Files.createTempDirectory(tempD1, TEMP_PREFIX); final Path tempF21 = Files.createTempFile(tempD2, TEMP_PREFIX, TEMP_SUFFIX); final Path tempF22 = Files.createTempFile(tempD2, TEMP_PREFIX, TEMP_SUFFIX); // Create various links final Path tempF23 = Files.createTempFile(tempD2, TEMP_PREFIX, TEMP_SUFFIX); Assert.assertTrue(Files.deleteIfExists(tempF23)); final Path tempL21 = Files.createSymbolicLink(new File(tempD2.toFile(), "l1").toPath(), tempF22); final Path tempL22 = Files.createSymbolicLink(new File(tempD2.toFile(), "l2").toPath(), tempF23.getFileName()); final Path tempL23 = Files.createLink(new File(tempD2.toFile(), "l3").toPath(), tempF22); // Delete and check io.eguan.utils.Files.deleteRecursive(tempD1); Assert.assertFalse(tempD1.toFile().exists()); Assert.assertFalse(tempF1.toFile().exists()); Assert.assertFalse(tempD2.toFile().exists()); Assert.assertFalse(tempF21.toFile().exists()); Assert.assertFalse(tempF22.toFile().exists()); Assert.assertFalse(tempF23.toFile().exists()); Assert.assertFalse(tempL21.toFile().exists()); Assert.assertFalse(tempL22.toFile().exists()); Assert.assertFalse(tempL23.toFile().exists()); } /** * Delete failure. * * @throws IOException */ @Test public void testDeleteRecursiveFailure() throws IOException { // Create file tree final Path tempD1 = Files.createTempDirectory(TEMP_PREFIX); final Path tempF1 = Files.createTempFile(tempD1, TEMP_PREFIX, TEMP_SUFFIX); final Path tempD2 = Files.createTempDirectory(tempD1, TEMP_PREFIX); final Path tempD3 = Files.createTempDirectory(tempD2, TEMP_PREFIX); final Path tempF31 = Files.createTempFile(tempD3, TEMP_PREFIX, TEMP_SUFFIX); // Change directory mode to disable delete final Set<PosixFilePermission> tempD3Attr = Files.getPosixFilePermissions(tempD3); final Set<PosixFilePermission> tempD3AttrOwnerRX = new HashSet<PosixFilePermission>(); tempD3AttrOwnerRX.add(PosixFilePermission.OWNER_READ); tempD3AttrOwnerRX.add(PosixFilePermission.OWNER_EXECUTE); Files.setPosixFilePermissions(tempD3, tempD3AttrOwnerRX); // Delete and fail // 2012-07-24: with the JVM 'OpenJDK Runtime Environment (IcedTea6 1.11.3) (6b24-1.11.3-1ubuntu0.12.04.1) // OpenJDK 64-Bit Server VM (build 20.0-b12, mixed mode)', the postVisitDirectory() is not called with // (exc != null) when the deleting a file in a directory fails! try { io.eguan.utils.Files.deleteRecursive(tempD1); throw new AssertionFailedError("Should not be reached"); } catch (final IOException e) { // Ok Assert.assertTrue(tempD3.toFile().exists()); } // Restore default mode, delete and check Files.setPosixFilePermissions(tempD3, tempD3Attr); io.eguan.utils.Files.deleteRecursive(tempD1); Assert.assertFalse(tempD1.toFile().exists()); Assert.assertFalse(tempF1.toFile().exists()); Assert.assertFalse(tempD2.toFile().exists()); Assert.assertFalse(tempD3.toFile().exists()); Assert.assertFalse(tempF31.toFile().exists()); } /** * Deletes a path that does not exist. * * @throws IOException */ @Test public void testDeleteRecursiveNotExisting() throws IOException { final File tempFile = File.createTempFile(TEMP_PREFIX, TEMP_SUFFIX); Assert.assertTrue(tempFile.delete()); io.eguan.utils.Files.deleteRecursive(tempFile.toPath()); Assert.assertFalse(tempFile.exists()); } /** * Recursive delete of a file. * * @throws IOException */ @Test public void testDeleteRecursiveFile() throws IOException { final File tempFile = File.createTempFile(TEMP_PREFIX, TEMP_SUFFIX); io.eguan.utils.Files.deleteRecursive(tempFile.toPath()); Assert.assertFalse(tempFile.exists()); } /** * Recursive delete of a symbolic link. * * @throws IOException */ @Test public void testDeleteRecursiveSymbolicLink() throws IOException { final File tempName = File.createTempFile(TEMP_PREFIX, TEMP_SUFFIX); Assert.assertTrue(tempName.delete()); final File tempFile = File.createTempFile(TEMP_PREFIX, TEMP_SUFFIX); try { final Path tempLink = Files.createSymbolicLink(tempName.toPath(), tempFile.toPath()); io.eguan.utils.Files.deleteRecursive(tempLink); Assert.assertFalse(tempName.exists()); Assert.assertFalse(tempLink.toFile().exists()); Assert.assertTrue(tempFile.exists()); } finally { tempFile.delete(); } } /** * Delete the whole tree. * * @throws IOException */ @Test public void testDeleteRecursiveProgress() throws IOException { deleteRecursiveProgress(false); } /** * Delete the whole tree, except the starting directory. * * @throws IOException */ @Test public void testDeleteRecursiveProgressKeep() throws IOException { deleteRecursiveProgress(true); } private void deleteRecursiveProgress(final boolean keepPath) throws IOException { final Set<Path> exists = new HashSet<>(); final Set<Path> deleted = new HashSet<>(); // Create file tree final Path tempD1 = Files.createTempDirectory(TEMP_PREFIX); try { final Path tempF1 = Files.createTempFile(tempD1, TEMP_PREFIX, TEMP_SUFFIX); final Path tempD2 = Files.createTempDirectory(tempD1, TEMP_PREFIX); final Path tempF21 = Files.createTempFile(tempD2, TEMP_PREFIX, TEMP_SUFFIX); final Path tempF22 = Files.createTempFile(tempD2, TEMP_PREFIX, TEMP_SUFFIX); // Create various links final Path tempF23 = Files.createTempFile(tempD2, TEMP_PREFIX, TEMP_SUFFIX); Assert.assertTrue(Files.deleteIfExists(tempF23)); final Path tempL21 = Files.createSymbolicLink(new File(tempD2.toFile(), "l1").toPath(), tempF22); final Path tempL22 = Files.createSymbolicLink(new File(tempD2.toFile(), "l2").toPath(), tempF23.getFileName()); final Path tempL23 = Files.createLink(new File(tempD2.toFile(), "l3").toPath(), tempF22); // Fill collection exists.add(tempD1); exists.add(tempF1); exists.add(tempD2); exists.add(tempF21); exists.add(tempF22); exists.add(tempL21); exists.add(tempL22); exists.add(tempL23); final int count = exists.size(); // Delete and check io.eguan.utils.Files.deleteRecursive(tempD1, keepPath, new io.eguan.utils.Files.DeleteRecursiveProgress() { @Override public final FileVisitResult notify(final Path path) { Assert.assertTrue(exists.remove(path)); deleted.add(path); return FileVisitResult.CONTINUE; } }); Assert.assertEquals(keepPath ? 1 : 0, exists.size()); Assert.assertEquals(count - exists.size(), deleted.size()); Assert.assertEquals(Boolean.valueOf(keepPath), Boolean.valueOf(tempD1.toFile().exists())); Assert.assertFalse(tempF1.toFile().exists()); Assert.assertFalse(tempD2.toFile().exists()); Assert.assertFalse(tempF21.toFile().exists()); Assert.assertFalse(tempF22.toFile().exists()); Assert.assertFalse(tempF23.toFile().exists()); Assert.assertFalse(tempL21.toFile().exists()); Assert.assertFalse(tempL22.toFile().exists()); Assert.assertFalse(tempL23.toFile().exists()); } finally { // Cleanup remaining elements io.eguan.utils.Files.deleteRecursive(tempD1); } } /** * Delete partial tree. * * @throws IOException */ @Test public void testDeleteRecursiveProgressPartial() throws IOException { deleteRecursiveProgressPartial(false); } /** * Delete partial tree. * * @throws IOException */ @Test public void testDeleteRecursiveProgressPartialKeep() throws IOException { deleteRecursiveProgressPartial(true); } private void deleteRecursiveProgressPartial(final boolean keepPath) throws IOException { final Set<Path> exists = new HashSet<>(); final Set<Path> deleted = new HashSet<>(); // Create file tree final Path tempD1 = Files.createTempDirectory(TEMP_PREFIX); try { final Path tempF1 = Files.createTempFile(tempD1, TEMP_PREFIX, TEMP_SUFFIX); final Path tempD2 = Files.createTempDirectory(tempD1, TEMP_PREFIX); final Path tempF21 = Files.createTempFile(tempD2, TEMP_PREFIX, TEMP_SUFFIX); final Path tempF22 = Files.createTempFile(tempD2, TEMP_PREFIX, TEMP_SUFFIX); // Create various links final Path tempF23 = Files.createTempFile(tempD2, TEMP_PREFIX, TEMP_SUFFIX); Assert.assertTrue(Files.deleteIfExists(tempF23)); final Path tempL21 = Files.createSymbolicLink(new File(tempD2.toFile(), "l1").toPath(), tempF22); final Path tempL22 = Files.createSymbolicLink(new File(tempD2.toFile(), "l2").toPath(), tempF23.getFileName()); final Path tempL23 = Files.createLink(new File(tempD2.toFile(), "l3").toPath(), tempF22); // Fill collection exists.add(tempD1); exists.add(tempF1); exists.add(tempD2); exists.add(tempF21); exists.add(tempF22); exists.add(tempL21); exists.add(tempL22); exists.add(tempL23); final int count = exists.size(); final int delCount = count / 2; final AtomicInteger toDel = new AtomicInteger(delCount); // Partial delete and check io.eguan.utils.Files.deleteRecursive(tempD1, keepPath, new io.eguan.utils.Files.DeleteRecursiveProgress() { @Override public final FileVisitResult notify(final Path path) { Assert.assertTrue(exists.remove(path)); deleted.add(path); return toDel.decrementAndGet() > 0 ? FileVisitResult.CONTINUE : FileVisitResult.TERMINATE; } }); Assert.assertEquals(count - delCount, exists.size()); Assert.assertEquals(delCount, deleted.size()); for (final Path path : exists) { Assert.assertTrue(Files.exists(path, LinkOption.NOFOLLOW_LINKS)); } for (final Path path : deleted) { Assert.assertFalse(Files.exists(path, LinkOption.NOFOLLOW_LINKS)); } } finally { // Cleanup remaining elements io.eguan.utils.Files.deleteRecursive(tempD1); } } @Test public void testDeleteRecursiveProgressNotExisting() throws IOException { final File tempFile = File.createTempFile(TEMP_PREFIX, TEMP_SUFFIX); Assert.assertTrue(tempFile.delete()); io.eguan.utils.Files.deleteRecursive(tempFile.toPath(), false, new io.eguan.utils.Files.DeleteRecursiveProgress() { @Override public final FileVisitResult notify(final Path path) { return FileVisitResult.CONTINUE; } }); Assert.assertFalse(tempFile.exists()); } @Test public void testDeleteRecursiveProgressFile() throws IOException { final File tempFile = File.createTempFile(TEMP_PREFIX, TEMP_SUFFIX); deleteRecursiveProgressSingle(tempFile.toPath(), false, true); } @Test public void testDeleteRecursiveProgressFileKeep() throws IOException { final File tempFile = File.createTempFile(TEMP_PREFIX, TEMP_SUFFIX); deleteRecursiveProgressSingle(tempFile.toPath(), true, true); } @Test public void testDeleteRecursiveSymbolicLinkProgress() throws IOException { final File tempName = File.createTempFile(TEMP_PREFIX, TEMP_SUFFIX); Assert.assertTrue(tempName.delete()); final File tempFile = File.createTempFile(TEMP_PREFIX, TEMP_SUFFIX); try { final Path tempLink = Files.createSymbolicLink(tempName.toPath(), tempFile.toPath()); deleteRecursiveProgressSingle(tempLink, false, false); Assert.assertFalse(tempName.exists()); Assert.assertFalse(tempLink.toFile().exists()); Assert.assertTrue(tempFile.exists()); } finally { tempFile.delete(); } } @Test public void testDeleteRecursiveSymbolicLinkProgressKeep() throws IOException { final File tempName = File.createTempFile(TEMP_PREFIX, TEMP_SUFFIX); Assert.assertTrue(tempName.delete()); final File tempFile = File.createTempFile(TEMP_PREFIX, TEMP_SUFFIX); try { final Path tempLink = Files.createSymbolicLink(tempName.toPath(), tempFile.toPath()); try { deleteRecursiveProgressSingle(tempLink, true, false); Assert.assertTrue(tempName.exists()); Assert.assertTrue(tempLink.toFile().exists()); Assert.assertTrue(tempFile.exists()); } finally { tempLink.toFile().delete(); } } finally { tempFile.delete(); } } @Test public void testDeleteRecursiveSymbolicLinkOnDirProgress() throws IOException { final File tempName = File.createTempFile(TEMP_PREFIX, TEMP_SUFFIX); Assert.assertTrue(tempName.delete()); final Path tempDir = Files.createTempDirectory(TEMP_PREFIX); try { final Path tempLink = Files.createSymbolicLink(tempName.toPath(), tempDir); deleteRecursiveProgressSingle(tempLink, false, false); Assert.assertFalse(tempName.exists()); Assert.assertFalse(tempLink.toFile().exists()); Assert.assertTrue(tempDir.toFile().exists()); } finally { tempDir.toFile().delete(); } } @Test public void testDeleteRecursiveSymbolicLinkOnDirProgressKeep() throws IOException { final File tempName = File.createTempFile(TEMP_PREFIX, TEMP_SUFFIX); Assert.assertTrue(tempName.delete()); final Path tempDir = Files.createTempDirectory(TEMP_PREFIX); try { final Path tempLink = Files.createSymbolicLink(tempName.toPath(), tempDir); try { deleteRecursiveProgressSingle(tempLink, true, false); Assert.assertTrue(tempName.exists()); Assert.assertTrue(Files.isSymbolicLink(tempLink)); Assert.assertTrue(Files.isDirectory(tempDir)); } finally { tempLink.toFile().delete(); } } finally { tempDir.toFile().delete(); } } private void deleteRecursiveProgressSingle(final Path toDel, final boolean keepPath, final boolean cleanup) throws IOException { try { // Delete and check io.eguan.utils.Files.deleteRecursive(toDel, keepPath, new io.eguan.utils.Files.DeleteRecursiveProgress() { @Override public final FileVisitResult notify(final Path path) { return FileVisitResult.CONTINUE; } }); Assert.assertEquals(Boolean.valueOf(keepPath), Boolean.valueOf(toDel.toFile().exists())); } finally { // Cleanup remaining elements if (cleanup) io.eguan.utils.Files.deleteRecursive(toDel); } } /** * Test file existence.<br> * TODO add test with file created in another thread * * @throws IOException */ @Test public void testFileExistsTimeout() throws IOException { final File tempFile = File.createTempFile(TEMP_PREFIX, TEMP_SUFFIX); try { Assert.assertTrue(io.eguan.utils.Files.waitForFile(tempFile, 100)); } finally { Assert.assertTrue(tempFile.delete()); } Assert.assertFalse(io.eguan.utils.Files.waitForFile(tempFile, 100)); } /** * Test file nonexistence.<br> * TODO add test with file created in another thread * * @throws IOException */ @Test public void testFileNotExistsTimeout() throws IOException { final File tempFile = File.createTempFile(TEMP_PREFIX, TEMP_SUFFIX); try { Assert.assertFalse(io.eguan.utils.Files.waitForFileDeletion(tempFile, 100)); } finally { Assert.assertTrue(tempFile.delete()); } Assert.assertTrue(io.eguan.utils.Files.waitForFileDeletion(tempFile, 100)); } /* * User-defined attributes */ private static File ext2fs; private static File vfatfs; // vfat does not support user defined attributes private static File mountPoint; @BeforeClass public static void createFileFs() throws IOException { ext2fs = File.createTempFile("tst-fs-", ".ext2"); UnixFsFile fsFile = new UnixFsFile(ext2fs, 20 * 1024 * 1024, "ext2", null); fsFile.create(); vfatfs = File.createTempFile("tst-fs-", ".vfat"); fsFile = new UnixFsFile(vfatfs, 20 * 1024 * 1024, "vfat", null); fsFile.create(); mountPoint = Files.createTempDirectory("tst-mnt-").toFile(); } @AfterClass public static void deleteFileFs() { if (ext2fs != null) { ext2fs.delete(); ext2fs = null; } if (vfatfs != null) { vfatfs.delete(); vfatfs = null; } if (mountPoint != null) { mountPoint.delete(); mountPoint = null; } } @Test public void userAttrExt2() throws IOException { final File aa; final String ATTR1 = "MyAttr"; final String ATTR2 = "SomeLongAttribute,But not that long..."; final String ATTR3 = "NeverSet"; final String VALUE2 = "SomeLong value, with some special chars: \u20AC\u00E7\u00E0 ~ &\u00EF"; { // Create file and check attr supported final UnixFsFile fsFile = new UnixFsFile(ext2fs, 20 * 1024 * 1024, "ext2", null); final UnixMount unixMount = fsFile.newUnixMount(mountPoint); unixMount.mount(); try { // Create a file aa = new File(mountPoint, "all-access"); final String aaAbsPath = aa.getAbsolutePath(); final String[] touchArray = new String[] { "sudo", "touch", aaAbsPath }; RunCmdUtils.runCmd(touchArray, aa); final String[] chmodArray = new String[] { "sudo", "chmod", "777", aaAbsPath }; RunCmdUtils.runCmd(chmodArray, aa); assertTrue(aa.isFile()); final Path aaPath = aa.toPath(); Assert.assertEquals(0, io.eguan.utils.Files.listUserAttr(aaPath).length); io.eguan.utils.Files.checkUserAttrSupported(aaPath); // Set an attribute io.eguan.utils.Files.setUserAttr(aaPath, ATTR1); assertTrue(io.eguan.utils.Files.isUserAttrSet(aaPath, ATTR1)); Assert.assertEquals("y", io.eguan.utils.Files.getUserAttr(aaPath, ATTR1)); io.eguan.utils.Files.setUserAttr(aaPath, ATTR2, ""); assertTrue(io.eguan.utils.Files.isUserAttrSet(aaPath, ATTR2)); Assert.assertEquals("", io.eguan.utils.Files.getUserAttr(aaPath, ATTR2)); io.eguan.utils.Files.setUserAttr(aaPath, ATTR2, VALUE2); assertTrue(io.eguan.utils.Files.isUserAttrSet(aaPath, ATTR2)); Assert.assertEquals(VALUE2, io.eguan.utils.Files.getUserAttr(aaPath, ATTR2)); Assert.assertEquals(2, io.eguan.utils.Files.listUserAttr(aaPath).length); io.eguan.utils.Files.unsetUserAttr(aaPath, ATTR1); Assert.assertEquals(1, io.eguan.utils.Files.listUserAttr(aaPath).length); Assert.assertArrayEquals(new String[] { ATTR2 }, io.eguan.utils.Files.listUserAttr(aaPath)); // Does nothing if the attribute is not set io.eguan.utils.Files.unsetUserAttr(aaPath, ATTR1); assertFalse(io.eguan.utils.Files.isUserAttrSet(aaPath, ATTR1)); assertTrue(io.eguan.utils.Files.isUserAttrSet(aaPath, ATTR2)); assertFalse(io.eguan.utils.Files.isUserAttrSet(aaPath, ATTR3)); Assert.assertNull(io.eguan.utils.Files.getUserAttr(aaPath, ATTR3)); } finally { unixMount.umount(); } } { // Mount FS read-only and check attr not supported final UnixFsFile fsFile = new UnixFsFile(ext2fs, 20 * 1024 * 1024, "ext2", "ro"); final UnixMount unixMount = fsFile.newUnixMount(mountPoint); unixMount.mount(); final Path aaPath = aa.toPath(); try { // Can not set attr assertTrue(aa.isFile()); try { io.eguan.utils.Files.checkUserAttrSupported(aaPath); throw new AssertionFailedError("Should not be reached"); } catch (final IOException e) { // Ok } // Should be able to read attr assertFalse(io.eguan.utils.Files.isUserAttrSet(aaPath, ATTR1)); assertTrue(io.eguan.utils.Files.isUserAttrSet(aaPath, ATTR2)); assertFalse(io.eguan.utils.Files.isUserAttrSet(aaPath, ATTR3)); Assert.assertNull(io.eguan.utils.Files.getUserAttr(aaPath, ATTR1)); Assert.assertEquals(VALUE2, io.eguan.utils.Files.getUserAttr(aaPath, ATTR2)); Assert.assertNull(io.eguan.utils.Files.getUserAttr(aaPath, ATTR3)); Assert.assertEquals(1, io.eguan.utils.Files.listUserAttr(aaPath).length); Assert.assertArrayEquals(new String[] { ATTR2 }, io.eguan.utils.Files.listUserAttr(aaPath)); // Can not delete try { io.eguan.utils.Files.unsetUserAttr(aaPath, ATTR2); throw new AssertionFailedError("Should not be reached"); } catch (final IOException e) { // Ok } // Can not set try { io.eguan.utils.Files.setUserAttr(aaPath, ATTR3, ""); throw new AssertionFailedError("Should not be reached"); } catch (final IOException e) { // Ok } Assert.assertEquals(VALUE2, io.eguan.utils.Files.getUserAttr(aaPath, ATTR2)); Assert.assertNull(io.eguan.utils.Files.getUserAttr(aaPath, ATTR3)); } finally { unixMount.umount(); } } } @Test(expected = IOException.class) public void userAttrExt2NoAccess() throws IOException { final UnixFsFile fsFile = new UnixFsFile(ext2fs, 20 * 1024 * 1024, "ext2", null); final UnixMount unixMount = fsFile.newUnixMount(mountPoint); unixMount.mount(); try { // Must have a lost+found directory: current user has no access final File lf = new File(mountPoint, "lost+found"); assertTrue(lf.isDirectory()); io.eguan.utils.Files.checkUserAttrSupported(lf.toPath()); } finally { unixMount.umount(); } } @Test public void userAttrVfat() throws IOException { final UnixUser user = UnixUser.getCurrentUser(); final UnixFsFile fsFile = new UnixFsFile(vfatfs, 20 * 1024 * 1024, "vfat", "utf8,uid=" + user.getUid() + ",gid=" + user.getGid()); final UnixMount unixMount = fsFile.newUnixMount(mountPoint); unixMount.mount(); try { // Check we can create a file final File tf = new File(mountPoint, "test-file"); assertTrue(tf.createNewFile()); assertTrue(tf.isFile()); try { io.eguan.utils.Files.checkUserAttrSupported(tf.toPath()); throw new AssertionFailedError("Should not be reached"); } catch (final IOException e) { // Ok } } finally { unixMount.umount(); } } }