| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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. |
| */ |
| package org.apache.lucene.util; |
| |
| |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.net.URI; |
| import java.nio.channels.FileChannel; |
| import java.nio.charset.StandardCharsets; |
| import java.nio.file.AccessDeniedException; |
| import java.nio.file.FileStore; |
| import java.nio.file.FileSystem; |
| import java.nio.file.Files; |
| import java.nio.file.LinkOption; |
| import java.nio.file.NoSuchFileException; |
| import java.nio.file.OpenOption; |
| import java.nio.file.Path; |
| import java.nio.file.attribute.FileAttribute; |
| import java.nio.file.attribute.FileAttributeView; |
| import java.nio.file.attribute.FileStoreAttributeView; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Random; |
| import java.util.Set; |
| import java.util.UUID; |
| |
| import org.apache.lucene.mockfile.FilterFileSystem; |
| import org.apache.lucene.mockfile.FilterFileSystemProvider; |
| import org.apache.lucene.mockfile.FilterPath; |
| import org.junit.AssumptionViolatedException; |
| |
| /** Simple test methods for IOUtils */ |
| public class TestIOUtils extends LuceneTestCase { |
| |
| public void testDeleteFileIgnoringExceptions() throws Exception { |
| Path dir = createTempDir(); |
| Path file1 = dir.resolve("file1"); |
| Files.createFile(file1); |
| IOUtils.deleteFilesIgnoringExceptions(file1); |
| assertFalse(Files.exists(file1)); |
| // actually deletes |
| } |
| |
| public void testDontDeleteFileIgnoringExceptions() throws Exception { |
| Path dir = createTempDir(); |
| Path file1 = dir.resolve("file1"); |
| IOUtils.deleteFilesIgnoringExceptions(file1); |
| // no exception |
| } |
| |
| public void testDeleteTwoFilesIgnoringExceptions() throws Exception { |
| Path dir = createTempDir(); |
| Path file1 = dir.resolve("file1"); |
| Path file2 = dir.resolve("file2"); |
| // only create file2 |
| Files.createFile(file2); |
| IOUtils.deleteFilesIgnoringExceptions(file1, file2); |
| assertFalse(Files.exists(file2)); |
| // no exception |
| // actually deletes file2 |
| } |
| |
| public void testDeleteFileIfExists() throws Exception { |
| Path dir = createTempDir(); |
| Path file1 = dir.resolve("file1"); |
| Files.createFile(file1); |
| IOUtils.deleteFilesIfExist(file1); |
| assertFalse(Files.exists(file1)); |
| // actually deletes |
| } |
| |
| public void testDontDeleteDoesntExist() throws Exception { |
| Path dir = createTempDir(); |
| Path file1 = dir.resolve("file1"); |
| IOUtils.deleteFilesIfExist(file1); |
| // no exception |
| } |
| |
| public void testDeleteTwoFilesIfExist() throws Exception { |
| Path dir = createTempDir(); |
| Path file1 = dir.resolve("file1"); |
| Path file2 = dir.resolve("file2"); |
| // only create file2 |
| Files.createFile(file2); |
| IOUtils.deleteFilesIfExist(file1, file2); |
| assertFalse(Files.exists(file2)); |
| // no exception |
| // actually deletes file2 |
| } |
| |
| public void testSpinsBasics() throws Exception { |
| Path dir = createTempDir(); |
| // no exception, directory exists |
| IOUtils.spins(dir); |
| Path file = dir.resolve("exists"); |
| Files.createFile(file); |
| // no exception, file exists |
| IOUtils.spins(file); |
| |
| // exception: file doesn't exist |
| Path fake = dir.resolve("nonexistent"); |
| expectThrows(IOException.class, () -> { |
| IOUtils.spins(fake); |
| }); |
| } |
| |
| // fake up a filestore to test some underlying methods |
| static class MockFileStore extends FileStore { |
| final String description; |
| final String type; |
| final String name; |
| |
| MockFileStore(String description, String type, String name) { |
| this.description = description; |
| this.type = type; |
| this.name = name; |
| } |
| |
| @Override |
| public String type() { |
| return type; |
| } |
| |
| @Override |
| public String name() { |
| return name; |
| } |
| |
| @Override |
| public String toString() { |
| return description; |
| } |
| |
| |
| // TODO: we can enable mocking of these when we need them later: |
| |
| @Override |
| public boolean isReadOnly() { |
| return false; |
| } |
| |
| @Override |
| public long getTotalSpace() throws IOException { |
| return 1000; |
| } |
| |
| @Override |
| public long getUsableSpace() throws IOException { |
| return 800; |
| } |
| |
| @Override |
| public long getUnallocatedSpace() throws IOException { |
| return 1000; |
| } |
| |
| @Override |
| public boolean supportsFileAttributeView(Class<? extends FileAttributeView> type) { |
| return false; |
| } |
| |
| @Override |
| public boolean supportsFileAttributeView(String name) { |
| return false; |
| } |
| |
| @Override |
| public <V extends FileStoreAttributeView> V getFileStoreAttributeView(Class<V> type) { |
| return null; |
| } |
| |
| @Override |
| public Object getAttribute(String attribute) throws IOException { |
| return null; |
| } |
| } |
| |
| public void testGetMountPoint() throws Exception { |
| assertEquals("/", IOUtils.getMountPoint(new MockFileStore("/ (/dev/sda1)", "ext4", "/dev/sda1"))); |
| assertEquals("/test/ space(((trash)))/", IOUtils.getMountPoint(new MockFileStore("/test/ space(((trash)))/ (/dev/sda1)", "ext3", "/dev/sda1"))); |
| assertEquals("/", IOUtils.getMountPoint(new MockFileStore("/ (notreal)", "ext2", "notreal"))); |
| } |
| |
| /** mock linux that takes mappings of test files, to their associated filesystems. |
| * it will chroot /dev and /sys requests to root, so you can mock those too. |
| * <p> |
| * It is hacky by definition, so don't try putting it around a complex chain or anything. |
| */ |
| static class MockLinuxFileSystemProvider extends FilterFileSystemProvider { |
| final Map<String,FileStore> filesToStore; |
| final Path root; |
| |
| public MockLinuxFileSystemProvider(FileSystem delegateInstance, final Map<String,FileStore> filesToStore, Path root) { |
| super("mocklinux://", delegateInstance); |
| if (mockedPath(root)) { |
| throw new AssumptionViolatedException("can't mock /sys and /dev inside of /sys or /dev!"); |
| } |
| final Collection<FileStore> allStores = new HashSet<>(filesToStore.values()); |
| this.fileSystem = new FilterFileSystem(this, delegateInstance) { |
| @Override |
| public Iterable<FileStore> getFileStores() { |
| return allStores; |
| } |
| |
| @Override |
| public Path getPath(String first, String... more) { |
| return new MockLinuxPath(delegateInstance.getPath(first, more), this); |
| } |
| }; |
| this.filesToStore = filesToStore; |
| this.root = new MockLinuxPath(root, this.fileSystem); |
| } |
| |
| @Override |
| public FileStore getFileStore(Path path) throws IOException { |
| FileStore ret = filesToStore.get(path.toString()); |
| if (ret == null) { |
| throw new IllegalArgumentException("this mock doesnt know wtf to do with: " + path); |
| } |
| // act like the linux fs provider here, return a crazy rootfs one |
| if (ret.toString().startsWith(root + " (")) { |
| return new MockFileStore(root + " (rootfs)", "rootfs", "rootfs"); |
| } |
| |
| return ret; |
| } |
| |
| static boolean mockedPath(Path path) { |
| return path.toAbsolutePath().startsWith("/sys") || path.toAbsolutePath().startsWith("/dev"); |
| } |
| |
| Path maybeChroot(Path path) { |
| if (mockedPath(path)) { |
| // map to our chrooted location; |
| return path.getRoot().resolve(root).resolve(path.toString().substring(1)); |
| } else { |
| return path; |
| } |
| } |
| |
| @Override |
| protected Path toDelegate(Path path) { |
| return super.toDelegate(maybeChroot(path)); |
| } |
| |
| class MockLinuxPath extends FilterPath { |
| MockLinuxPath(Path delegate, FileSystem fileSystem) { |
| super(delegate, fileSystem); |
| } |
| |
| @Override |
| public Path toRealPath(LinkOption... options) throws IOException { |
| Path p = maybeChroot(this); |
| if (p == this) { |
| return super.toRealPath(options); |
| } else { |
| return p.toRealPath(options); |
| } |
| } |
| |
| @Override |
| protected Path wrap(Path other) { |
| return new MockLinuxPath(other, fileSystem); |
| } |
| } |
| } |
| |
| public void testGetFileStore() throws Exception { |
| Path dir = createTempDir(); |
| dir = FilterPath.unwrap(dir).toRealPath(); |
| |
| // now we can create some fake mount points: |
| FileStore root = new MockFileStore(dir.toString() + " (/dev/sda1)", "ntfs", "/dev/sda1"); |
| FileStore usr = new MockFileStore(dir.resolve("usr").toString() + " (/dev/sda2)", "xfs", "/dev/sda2"); |
| |
| // associate some preset files to these |
| Map<String,FileStore> mappings = new HashMap<>(); |
| mappings.put(dir.toString(), root); |
| mappings.put(dir.resolve("foo.txt").toString(), root); |
| mappings.put(dir.resolve("usr").toString(), usr); |
| mappings.put(dir.resolve("usr/bar.txt").toString(), usr); |
| |
| FileSystem mockLinux = new MockLinuxFileSystemProvider(dir.getFileSystem(), mappings, dir).getFileSystem(null); |
| Path mockPath = mockLinux.getPath(dir.toString()); |
| |
| // sanity check our mock: |
| assertSame(usr, Files.getFileStore(mockPath.resolve("usr"))); |
| assertSame(usr, Files.getFileStore(mockPath.resolve("usr/bar.txt"))); |
| // for root filesystem we get a crappy one |
| assertNotSame(root, Files.getFileStore(mockPath)); |
| assertNotSame(usr, Files.getFileStore(mockPath)); |
| assertNotSame(root, Files.getFileStore(mockPath.resolve("foo.txt"))); |
| assertNotSame(usr, Files.getFileStore(mockPath.resolve("foo.txt"))); |
| |
| // now test our method: |
| assertSame(usr, IOUtils.getFileStore(mockPath.resolve("usr"))); |
| assertSame(usr, IOUtils.getFileStore(mockPath.resolve("usr/bar.txt"))); |
| assertSame(root, IOUtils.getFileStore(mockPath)); |
| assertSame(root, IOUtils.getFileStore(mockPath.resolve("foo.txt"))); |
| } |
| |
| public void testTmpfsDoesntSpin() throws Exception { |
| Path dir = createTempDir(); |
| dir = FilterPath.unwrap(dir).toRealPath(); |
| |
| // fake tmpfs |
| FileStore root = new MockFileStore(dir.toString() + " (/dev/sda1)", "tmpfs", "/dev/sda1"); |
| Map<String,FileStore> mappings = Collections.singletonMap(dir.toString(), root); |
| FileSystem mockLinux = new MockLinuxFileSystemProvider(dir.getFileSystem(), mappings, dir).getFileSystem(null); |
| |
| Path mockPath = mockLinux.getPath(dir.toString()); |
| assertFalse(IOUtils.spinsLinux(mockPath)); |
| } |
| |
| public void testNfsSpins() throws Exception { |
| Path dir = createTempDir(); |
| dir = FilterPath.unwrap(dir).toRealPath(); |
| |
| // fake nfs |
| FileStore root = new MockFileStore(dir.toString() + " (somenfsserver:/some/mount)", "nfs", "somenfsserver:/some/mount"); |
| Map<String,FileStore> mappings = Collections.singletonMap(dir.toString(), root); |
| FileSystem mockLinux = new MockLinuxFileSystemProvider(dir.getFileSystem(), mappings, dir).getFileSystem(null); |
| |
| Path mockPath = mockLinux.getPath(dir.toString()); |
| assertTrue(IOUtils.spinsLinux(mockPath)); |
| } |
| |
| public void testSSD() throws Exception { |
| assumeFalse("windows is not supported", Constants.WINDOWS); |
| Path dir = createTempDir(); |
| dir = FilterPath.unwrap(dir).toRealPath(); |
| |
| // fake ssd |
| FileStore root = new MockFileStore(dir.toString() + " (/dev/zzz1)", "btrfs", "/dev/zzz1"); |
| // make a fake /dev/zzz1 for it |
| Path devdir = dir.resolve("dev"); |
| Files.createDirectories(devdir); |
| Files.createFile(devdir.resolve("zzz1")); |
| // make a fake /sys/block/zzz/queue/rotational file for it |
| Path sysdir = dir.resolve("sys").resolve("block").resolve("zzz").resolve("queue"); |
| Files.createDirectories(sysdir); |
| try (OutputStream o = Files.newOutputStream(sysdir.resolve("rotational"))) { |
| o.write("0\n".getBytes(StandardCharsets.US_ASCII)); |
| } |
| Map<String,FileStore> mappings = Collections.singletonMap(dir.toString(), root); |
| FileSystem mockLinux = new MockLinuxFileSystemProvider(dir.getFileSystem(), mappings, dir).getFileSystem(null); |
| |
| Path mockPath = mockLinux.getPath(dir.toString()); |
| assertFalse(IOUtils.spinsLinux(mockPath)); |
| } |
| |
| public void testNVME() throws Exception { |
| assumeFalse("windows is not supported", Constants.WINDOWS); |
| Path dir = createTempDir(); |
| dir = FilterPath.unwrap(dir).toRealPath(); |
| |
| // fake ssd |
| FileStore root = new MockFileStore(dir.toString() + " (/dev/nvme0n1p1)", "btrfs", "/dev/nvme0n1p1"); |
| // make a fake /dev/nvme0n1p1 for it |
| Path devdir = dir.resolve("dev"); |
| Files.createDirectories(devdir); |
| Files.createFile(devdir.resolve("nvme0n1p1")); |
| // make a fake /sys/block/nvme0n1/queue/rotational file for it |
| Path sysdir = dir.resolve("sys").resolve("block").resolve("nvme0n1").resolve("queue"); |
| Files.createDirectories(sysdir); |
| try (OutputStream o = Files.newOutputStream(sysdir.resolve("rotational"))) { |
| o.write("0\n".getBytes(StandardCharsets.US_ASCII)); |
| } |
| // As test for the longest path match, add some other devices (that have no queue/rotational), too: |
| Files.createFile(dir.resolve("sys").resolve("block").resolve("nvme0")); |
| Files.createFile(dir.resolve("sys").resolve("block").resolve("dummy")); |
| Files.createFile(dir.resolve("sys").resolve("block").resolve("nvm")); |
| Map<String,FileStore> mappings = Collections.singletonMap(dir.toString(), root); |
| FileSystem mockLinux = new MockLinuxFileSystemProvider(dir.getFileSystem(), mappings, dir).getFileSystem(null); |
| |
| Path mockPath = mockLinux.getPath(dir.toString()); |
| assertFalse(IOUtils.spinsLinux(mockPath)); |
| } |
| |
| public void testRotatingPlatters() throws Exception { |
| assumeFalse("windows is not supported", Constants.WINDOWS); |
| Path dir = createTempDir(); |
| dir = FilterPath.unwrap(dir).toRealPath(); |
| |
| // fake ssd |
| FileStore root = new MockFileStore(dir.toString() + " (/dev/zzz1)", "reiser4", "/dev/zzz1"); |
| // make a fake /dev/zzz1 for it |
| Path devdir = dir.resolve("dev"); |
| Files.createDirectories(devdir); |
| Files.createFile(devdir.resolve("zzz1")); |
| // make a fake /sys/block/zzz/queue/rotational file for it |
| Path sysdir = dir.resolve("sys").resolve("block").resolve("zzz").resolve("queue"); |
| Files.createDirectories(sysdir); |
| try (OutputStream o = Files.newOutputStream(sysdir.resolve("rotational"))) { |
| o.write("1\n".getBytes(StandardCharsets.US_ASCII)); |
| } |
| Map<String,FileStore> mappings = Collections.singletonMap(dir.toString(), root); |
| FileSystem mockLinux = new MockLinuxFileSystemProvider(dir.getFileSystem(), mappings, dir).getFileSystem(null); |
| |
| Path mockPath = mockLinux.getPath(dir.toString()); |
| assertTrue(IOUtils.spinsLinux(mockPath)); |
| } |
| |
| public void testManyPartitions() throws Exception { |
| assumeFalse("windows is not supported", Constants.WINDOWS); |
| Path dir = createTempDir(); |
| dir = FilterPath.unwrap(dir).toRealPath(); |
| |
| // fake ssd |
| FileStore root = new MockFileStore(dir.toString() + " (/dev/zzz12)", "zfs", "/dev/zzz12"); |
| // make a fake /dev/zzz11 for it |
| Path devdir = dir.resolve("dev"); |
| Files.createDirectories(devdir); |
| Files.createFile(devdir.resolve("zzz12")); |
| // make a fake /sys/block/zzz/queue/rotational file for it |
| Path sysdir = dir.resolve("sys").resolve("block").resolve("zzz").resolve("queue"); |
| Files.createDirectories(sysdir); |
| try (OutputStream o = Files.newOutputStream(sysdir.resolve("rotational"))) { |
| o.write("0\n".getBytes(StandardCharsets.US_ASCII)); |
| } |
| Map<String,FileStore> mappings = Collections.singletonMap(dir.toString(), root); |
| FileSystem mockLinux = new MockLinuxFileSystemProvider(dir.getFileSystem(), mappings, dir).getFileSystem(null); |
| |
| Path mockPath = mockLinux.getPath(dir.toString()); |
| assertFalse(IOUtils.spinsLinux(mockPath)); |
| } |
| |
| public void testSymlinkSSD() throws Exception { |
| assumeFalse("windows is not supported", Constants.WINDOWS); |
| Path dir = createTempDir(); |
| dir = FilterPath.unwrap(dir).toRealPath(); |
| |
| // fake SSD with a symlink mount (Ubuntu-like): |
| Random rnd = random(); |
| String partitionUUID = new UUID(rnd.nextLong(), rnd.nextLong()).toString(); |
| FileStore root = new MockFileStore(dir.toString() + " (/dev/disk/by-uuid/"+partitionUUID+")", "btrfs", "/dev/disk/by-uuid/"+partitionUUID); |
| // make a fake /dev/sda1 for it |
| Path devdir = dir.resolve("dev"); |
| Files.createDirectories(devdir); |
| Path deviceFile = devdir.resolve("sda1"); |
| Files.createFile(deviceFile); |
| // create a symlink to the above device file |
| Path symlinkdir = devdir.resolve("disk").resolve("by-uuid"); |
| Files.createDirectories(symlinkdir); |
| try { |
| Files.createSymbolicLink(symlinkdir.resolve(partitionUUID), deviceFile); |
| } catch (UnsupportedOperationException | IOException e) { |
| assumeNoException("test requires filesystem that supports symbolic links", e); |
| } |
| // make a fake /sys/block/sda/queue/rotational file for it |
| Path sysdir = dir.resolve("sys").resolve("block").resolve("sda").resolve("queue"); |
| Files.createDirectories(sysdir); |
| try (OutputStream o = Files.newOutputStream(sysdir.resolve("rotational"))) { |
| o.write("0\n".getBytes(StandardCharsets.US_ASCII)); |
| } |
| Map<String,FileStore> mappings = Collections.singletonMap(dir.toString(), root); |
| FileSystem mockLinux = new MockLinuxFileSystemProvider(dir.getFileSystem(), mappings, dir).getFileSystem(null); |
| |
| Path mockPath = mockLinux.getPath(dir.toString()); |
| assertFalse(IOUtils.spinsLinux(mockPath)); |
| } |
| |
| public void testFsyncDirectory() throws Exception { |
| Path dir = createTempDir(); |
| dir = FilterPath.unwrap(dir).toRealPath(); |
| |
| Path devdir = dir.resolve("dev"); |
| Files.createDirectories(devdir); |
| IOUtils.fsync(devdir, true); |
| // no exception |
| } |
| |
| private static final class AccessDeniedWhileOpeningDirectoryFileSystem extends FilterFileSystemProvider { |
| |
| AccessDeniedWhileOpeningDirectoryFileSystem(final FileSystem delegate) { |
| super("access_denied://", Objects.requireNonNull(delegate)); |
| } |
| |
| @Override |
| public FileChannel newFileChannel( |
| final Path path, |
| final Set<? extends OpenOption> options, |
| final FileAttribute<?>... attrs) throws IOException { |
| if (Files.isDirectory(path)) { |
| throw new AccessDeniedException(path.toString()); |
| } |
| return delegate.newFileChannel(path, options, attrs); |
| } |
| |
| } |
| |
| public void testFsyncAccessDeniedOpeningDirectory() throws Exception { |
| final Path path = createTempDir().toRealPath(); |
| final FileSystem fs = new AccessDeniedWhileOpeningDirectoryFileSystem(path.getFileSystem()).getFileSystem(URI.create("file:///")); |
| final Path wrapped = new FilterPath(path, fs); |
| if (Constants.WINDOWS) { |
| // no exception, we early return and do not even try to open the directory |
| IOUtils.fsync(wrapped, true); |
| } else { |
| expectThrows(AccessDeniedException.class, () -> IOUtils.fsync(wrapped, true)); |
| } |
| } |
| |
| public void testFsyncNonExistentDirectory() throws Exception { |
| final Path dir = FilterPath.unwrap(createTempDir()).toRealPath(); |
| final Path nonExistentDir = dir.resolve("non-existent"); |
| expectThrows(NoSuchFileException.class, () -> IOUtils.fsync(nonExistentDir, true)); |
| } |
| |
| public void testFsyncFile() throws Exception { |
| Path dir = createTempDir(); |
| dir = FilterPath.unwrap(dir).toRealPath(); |
| |
| Path devdir = dir.resolve("dev"); |
| Files.createDirectories(devdir); |
| Path somefile = devdir.resolve("somefile"); |
| try (OutputStream o = Files.newOutputStream(somefile)) { |
| o.write("0\n".getBytes(StandardCharsets.US_ASCII)); |
| } |
| IOUtils.fsync(somefile, false); |
| // no exception |
| } |
| |
| public void testApplyToAll() { |
| ArrayList<Integer> closed = new ArrayList<>(); |
| RuntimeException runtimeException = expectThrows(RuntimeException.class, () -> |
| IOUtils.applyToAll(Arrays.asList(1, 2), i -> { |
| closed.add(i); |
| throw new RuntimeException("" + i); |
| })); |
| assertEquals("1", runtimeException.getMessage()); |
| assertEquals(1, runtimeException.getSuppressed().length); |
| assertEquals("2", runtimeException.getSuppressed()[0].getMessage()); |
| assertEquals(2, closed.size()); |
| assertEquals(1, closed.get(0).intValue()); |
| assertEquals(2, closed.get(1).intValue()); |
| } |
| |
| } |