|
@@ -0,0 +1,1006 @@
|
|
|
+/**
|
|
|
+ * 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
|
|
|
+ * <p>
|
|
|
+ * http://www.apache.org/licenses/LICENSE-2.0
|
|
|
+ * <p>
|
|
|
+ * 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.hadoop.hdfs.server.datanode;
|
|
|
+
|
|
|
+
|
|
|
+import org.apache.commons.io.FileUtils;
|
|
|
+import org.apache.hadoop.classification.InterfaceAudience;
|
|
|
+import org.apache.hadoop.classification.InterfaceStability;
|
|
|
+import org.apache.hadoop.conf.Configuration;
|
|
|
+import org.apache.hadoop.fs.FileUtil;
|
|
|
+import org.apache.hadoop.fs.HardLink;
|
|
|
+import org.apache.hadoop.hdfs.DFSConfigKeys;
|
|
|
+import org.apache.hadoop.hdfs.server.common.Storage;
|
|
|
+import org.apache.hadoop.hdfs.server.datanode.fsdataset.FsVolumeSpi;
|
|
|
+import org.apache.hadoop.hdfs.server.datanode.fsdataset.impl.FsDatasetUtil;
|
|
|
+import org.apache.hadoop.io.IOUtils;
|
|
|
+import org.apache.hadoop.io.LongWritable;
|
|
|
+import org.apache.hadoop.io.nativeio.NativeIO;
|
|
|
+import org.apache.hadoop.io.nativeio.NativeIOException;
|
|
|
+import org.apache.hadoop.net.SocketOutputStream;
|
|
|
+import org.apache.hadoop.util.ReflectionUtils;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+
|
|
|
+import javax.annotation.Nonnull;
|
|
|
+import javax.annotation.Nullable;
|
|
|
+import java.io.File;
|
|
|
+import java.io.FileDescriptor;
|
|
|
+import java.io.FileInputStream;
|
|
|
+import java.io.FileNotFoundException;
|
|
|
+import java.io.FileOutputStream;
|
|
|
+import java.io.FilenameFilter;
|
|
|
+import java.io.Flushable;
|
|
|
+import java.io.IOException;
|
|
|
+import java.io.RandomAccessFile;
|
|
|
+import java.nio.channels.FileChannel;
|
|
|
+import java.nio.file.CopyOption;
|
|
|
+import java.nio.file.Files;
|
|
|
+import java.nio.file.Path;
|
|
|
+import java.util.List;
|
|
|
+
|
|
|
+import static org.apache.hadoop.hdfs.server.datanode.FileIoProvider.OPERATION.*;
|
|
|
+
|
|
|
+/**
|
|
|
+ * This class abstracts out various file IO operations performed by the
|
|
|
+ * DataNode and invokes event hooks before and after each file IO.
|
|
|
+ *
|
|
|
+ * Behavior can be injected into these events by implementing
|
|
|
+ * {@link FileIoEvents} and replacing the default implementation
|
|
|
+ * with {@link DFSConfigKeys#DFS_DATANODE_FILE_IO_EVENTS_CLASS_KEY}.
|
|
|
+ *
|
|
|
+ * Most functions accept an optional {@link FsVolumeSpi} parameter for
|
|
|
+ * instrumentation/logging.
|
|
|
+ *
|
|
|
+ * Some methods may look redundant, especially the multiple variations of
|
|
|
+ * move/rename/list. They exist to retain behavior compatibility for existing
|
|
|
+ * code.
|
|
|
+ */
|
|
|
+@InterfaceAudience.Private
|
|
|
+@InterfaceStability.Unstable
|
|
|
+public class FileIoProvider {
|
|
|
+ public static final Logger LOG = LoggerFactory.getLogger(
|
|
|
+ FileIoProvider.class);
|
|
|
+
|
|
|
+ private final FileIoEvents eventHooks;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @param conf Configuration object. May be null. When null,
|
|
|
+ * the event handlers are no-ops.
|
|
|
+ */
|
|
|
+ public FileIoProvider(@Nullable Configuration conf) {
|
|
|
+ if (conf != null) {
|
|
|
+ final Class<? extends FileIoEvents> clazz = conf.getClass(
|
|
|
+ DFSConfigKeys.DFS_DATANODE_FILE_IO_EVENTS_CLASS_KEY,
|
|
|
+ DefaultFileIoEvents.class,
|
|
|
+ FileIoEvents.class);
|
|
|
+ eventHooks = ReflectionUtils.newInstance(clazz, conf);
|
|
|
+ } else {
|
|
|
+ eventHooks = new DefaultFileIoEvents();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Lists the types of file system operations. Passed to the
|
|
|
+ * IO hooks so implementations can choose behavior based on
|
|
|
+ * specific operations.
|
|
|
+ */
|
|
|
+ public enum OPERATION {
|
|
|
+ OPEN,
|
|
|
+ EXISTS,
|
|
|
+ LIST,
|
|
|
+ DELETE,
|
|
|
+ MOVE,
|
|
|
+ MKDIRS,
|
|
|
+ TRANSFER,
|
|
|
+ SYNC,
|
|
|
+ FADVISE,
|
|
|
+ READ,
|
|
|
+ WRITE,
|
|
|
+ FLUSH,
|
|
|
+ NATIVE_COPY
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Retrieve statistics from the underlying {@link FileIoEvents}
|
|
|
+ * implementation as a JSON string, if it maintains them.
|
|
|
+ * @return statistics as a JSON string. May be null.
|
|
|
+ */
|
|
|
+ public @Nullable String getStatistics() {
|
|
|
+ return eventHooks.getStatistics();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * See {@link Flushable#flush()}.
|
|
|
+ *
|
|
|
+ * @param volume target volume. null if unavailable.
|
|
|
+ * @throws IOException
|
|
|
+ */
|
|
|
+ public void flush(
|
|
|
+ @Nullable FsVolumeSpi volume, Flushable f) throws IOException {
|
|
|
+ final long begin = eventHooks.beforeFileIo(volume, FLUSH, 0);
|
|
|
+ try {
|
|
|
+ f.flush();
|
|
|
+ eventHooks.afterFileIo(volume, FLUSH, begin, 0);
|
|
|
+ } catch (Exception e) {
|
|
|
+ eventHooks.onFailure(volume, FLUSH, e, begin);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Sync the given {@link FileOutputStream}.
|
|
|
+ *
|
|
|
+ * @param volume target volume. null if unavailable.
|
|
|
+ * @throws IOException
|
|
|
+ */
|
|
|
+ public void sync(
|
|
|
+ @Nullable FsVolumeSpi volume, FileOutputStream fos) throws IOException {
|
|
|
+ final long begin = eventHooks.beforeFileIo(volume, SYNC, 0);
|
|
|
+ try {
|
|
|
+ fos.getChannel().force(true);
|
|
|
+ eventHooks.afterFileIo(volume, SYNC, begin, 0);
|
|
|
+ } catch (Exception e) {
|
|
|
+ eventHooks.onFailure(volume, SYNC, e, begin);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Call sync_file_range on the given file descriptor.
|
|
|
+ *
|
|
|
+ * @param volume target volume. null if unavailable.
|
|
|
+ * @throws IOException
|
|
|
+ */
|
|
|
+ public void syncFileRange(
|
|
|
+ @Nullable FsVolumeSpi volume, FileDescriptor outFd,
|
|
|
+ long offset, long numBytes, int flags) throws NativeIOException {
|
|
|
+ final long begin = eventHooks.beforeFileIo(volume, SYNC, 0);
|
|
|
+ try {
|
|
|
+ NativeIO.POSIX.syncFileRangeIfPossible(outFd, offset, numBytes, flags);
|
|
|
+ eventHooks.afterFileIo(volume, SYNC, begin, 0);
|
|
|
+ } catch (Exception e) {
|
|
|
+ eventHooks.onFailure(volume, SYNC, e, begin);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Call posix_fadvise on the given file descriptor.
|
|
|
+ *
|
|
|
+ * @param volume target volume. null if unavailable.
|
|
|
+ * @throws IOException
|
|
|
+ */
|
|
|
+ public void posixFadvise(
|
|
|
+ @Nullable FsVolumeSpi volume, String identifier, FileDescriptor outFd,
|
|
|
+ long offset, long length, int flags) throws NativeIOException {
|
|
|
+ final long begin = eventHooks.beforeMetadataOp(volume, FADVISE);
|
|
|
+ try {
|
|
|
+ NativeIO.POSIX.getCacheManipulator().posixFadviseIfPossible(
|
|
|
+ identifier, outFd, offset, length, flags);
|
|
|
+ eventHooks.afterMetadataOp(volume, FADVISE, begin);
|
|
|
+ } catch (Exception e) {
|
|
|
+ eventHooks.onFailure(volume, FADVISE, e, begin);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Delete a file.
|
|
|
+ * @param volume target volume. null if unavailable.
|
|
|
+ * @param f File to delete.
|
|
|
+ * @return true if the file was successfully deleted.
|
|
|
+ */
|
|
|
+ public boolean delete(@Nullable FsVolumeSpi volume, File f) {
|
|
|
+ final long begin = eventHooks.beforeMetadataOp(volume, DELETE);
|
|
|
+ try {
|
|
|
+ boolean deleted = f.delete();
|
|
|
+ eventHooks.afterMetadataOp(volume, DELETE, begin);
|
|
|
+ return deleted;
|
|
|
+ } catch (Exception e) {
|
|
|
+ eventHooks.onFailure(volume, DELETE, e, begin);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Delete a file, first checking to see if it exists.
|
|
|
+ * @param volume target volume. null if unavailable.
|
|
|
+ * @param f File to delete
|
|
|
+ * @return true if the file was successfully deleted or if it never
|
|
|
+ * existed.
|
|
|
+ */
|
|
|
+ public boolean deleteWithExistsCheck(@Nullable FsVolumeSpi volume, File f) {
|
|
|
+ final long begin = eventHooks.beforeMetadataOp(volume, DELETE);
|
|
|
+ try {
|
|
|
+ boolean deleted = !f.exists() || f.delete();
|
|
|
+ eventHooks.afterMetadataOp(volume, DELETE, begin);
|
|
|
+ if (!deleted) {
|
|
|
+ LOG.warn("Failed to delete file {}", f);
|
|
|
+ }
|
|
|
+ return deleted;
|
|
|
+ } catch (Exception e) {
|
|
|
+ eventHooks.onFailure(volume, DELETE, e, begin);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Transfer data from a FileChannel to a SocketOutputStream.
|
|
|
+ *
|
|
|
+ * @param volume target volume. null if unavailable.
|
|
|
+ * @param sockOut SocketOutputStream to write the data.
|
|
|
+ * @param fileCh FileChannel from which to read data.
|
|
|
+ * @param position position within the channel where the transfer begins.
|
|
|
+ * @param count number of bytes to transfer.
|
|
|
+ * @param waitTime returns the nanoseconds spent waiting for the socket
|
|
|
+ * to become writable.
|
|
|
+ * @param transferTime returns the nanoseconds spent transferring data.
|
|
|
+ * @throws IOException
|
|
|
+ */
|
|
|
+ public void transferToSocketFully(
|
|
|
+ @Nullable FsVolumeSpi volume, SocketOutputStream sockOut,
|
|
|
+ FileChannel fileCh, long position, int count,
|
|
|
+ LongWritable waitTime, LongWritable transferTime) throws IOException {
|
|
|
+ final long begin = eventHooks.beforeFileIo(volume, TRANSFER, count);
|
|
|
+ try {
|
|
|
+ sockOut.transferToFully(fileCh, position, count,
|
|
|
+ waitTime, transferTime);
|
|
|
+ eventHooks.afterFileIo(volume, TRANSFER, begin, count);
|
|
|
+ } catch (Exception e) {
|
|
|
+ eventHooks.onFailure(volume, TRANSFER, e, begin);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Create a file.
|
|
|
+ * @param volume target volume. null if unavailable.
|
|
|
+ * @param f File to be created.
|
|
|
+ * @return true if the file does not exist and was successfully created.
|
|
|
+ * false if the file already exists.
|
|
|
+ * @throws IOException
|
|
|
+ */
|
|
|
+ public boolean createFile(
|
|
|
+ @Nullable FsVolumeSpi volume, File f) throws IOException {
|
|
|
+ final long begin = eventHooks.beforeMetadataOp(volume, OPEN);
|
|
|
+ try {
|
|
|
+ boolean created = f.createNewFile();
|
|
|
+ eventHooks.afterMetadataOp(volume, OPEN, begin);
|
|
|
+ return created;
|
|
|
+ } catch (Exception e) {
|
|
|
+ eventHooks.onFailure(volume, OPEN, e, begin);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Create a FileInputStream using
|
|
|
+ * {@link FileInputStream#FileInputStream(File)}.
|
|
|
+ *
|
|
|
+ * Wraps the created input stream to intercept read calls
|
|
|
+ * before delegating to the wrapped stream.
|
|
|
+ *
|
|
|
+ * @param volume target volume. null if unavailable.
|
|
|
+ * @param f File object.
|
|
|
+ * @return FileInputStream to the given file.
|
|
|
+ * @throws FileNotFoundException
|
|
|
+ */
|
|
|
+ public FileInputStream getFileInputStream(
|
|
|
+ @Nullable FsVolumeSpi volume, File f) throws FileNotFoundException {
|
|
|
+ final long begin = eventHooks.beforeMetadataOp(volume, OPEN);
|
|
|
+ FileInputStream fis = null;
|
|
|
+ try {
|
|
|
+ fis = new WrappedFileInputStream(volume, f);
|
|
|
+ eventHooks.afterMetadataOp(volume, OPEN, begin);
|
|
|
+ return fis;
|
|
|
+ } catch(Exception e) {
|
|
|
+ org.apache.commons.io.IOUtils.closeQuietly(fis);
|
|
|
+ eventHooks.onFailure(volume, OPEN, e, begin);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Create a FileOutputStream using
|
|
|
+ * {@link FileOutputStream#FileOutputStream(File, boolean)}.
|
|
|
+ *
|
|
|
+ * Wraps the created output stream to intercept write calls
|
|
|
+ * before delegating to the wrapped stream.
|
|
|
+ *
|
|
|
+ * @param volume target volume. null if unavailable.
|
|
|
+ * @param f File object.
|
|
|
+ * @param append if true, then bytes will be written to the end of the
|
|
|
+ * file rather than the beginning.
|
|
|
+ * @param FileOutputStream to the given file object.
|
|
|
+ * @throws FileNotFoundException
|
|
|
+ */
|
|
|
+ public FileOutputStream getFileOutputStream(
|
|
|
+ @Nullable FsVolumeSpi volume, File f,
|
|
|
+ boolean append) throws FileNotFoundException {
|
|
|
+ final long begin = eventHooks.beforeMetadataOp(volume, OPEN);
|
|
|
+ FileOutputStream fos = null;
|
|
|
+ try {
|
|
|
+ fos = new WrappedFileOutputStream(volume, f, append);
|
|
|
+ eventHooks.afterMetadataOp(volume, OPEN, begin);
|
|
|
+ return fos;
|
|
|
+ } catch(Exception e) {
|
|
|
+ org.apache.commons.io.IOUtils.closeQuietly(fos);
|
|
|
+ eventHooks.onFailure(volume, OPEN, e, begin);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Create a FileOutputStream using
|
|
|
+ * {@link FileOutputStream#FileOutputStream(File, boolean)}.
|
|
|
+ *
|
|
|
+ * Wraps the created output stream to intercept write calls
|
|
|
+ * before delegating to the wrapped stream.
|
|
|
+ *
|
|
|
+ * @param volume target volume. null if unavailable.
|
|
|
+ * @param f File object.
|
|
|
+ * @return FileOutputStream to the given file object.
|
|
|
+ * @throws FileNotFoundException
|
|
|
+ */
|
|
|
+ public FileOutputStream getFileOutputStream(
|
|
|
+ @Nullable FsVolumeSpi volume, File f) throws FileNotFoundException {
|
|
|
+ return getFileOutputStream(volume, f, false);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Create a FileOutputStream using
|
|
|
+ * {@link FileOutputStream#FileOutputStream(FileDescriptor)}.
|
|
|
+ *
|
|
|
+ * Wraps the created output stream to intercept write calls
|
|
|
+ * before delegating to the wrapped stream.
|
|
|
+ *
|
|
|
+ * @param volume target volume. null if unavailable.
|
|
|
+ * @param f File object.
|
|
|
+ * @return FileOutputStream to the given file object.
|
|
|
+ * @throws FileNotFoundException
|
|
|
+ */
|
|
|
+ public FileOutputStream getFileOutputStream(
|
|
|
+ @Nullable FsVolumeSpi volume, FileDescriptor fd) {
|
|
|
+ return new WrappedFileOutputStream(volume, fd);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Create a FileInputStream using
|
|
|
+ * {@link NativeIO#getShareDeleteFileDescriptor}.
|
|
|
+ * Wraps the created input stream to intercept input calls
|
|
|
+ * before delegating to the wrapped stream.
|
|
|
+ *
|
|
|
+ * @param volume target volume. null if unavailable.
|
|
|
+ * @param f File object.
|
|
|
+ * @param offset the offset position, measured in bytes from the
|
|
|
+ * beginning of the file, at which to set the file
|
|
|
+ * pointer.
|
|
|
+ * @return FileOutputStream to the given file object.
|
|
|
+ * @throws FileNotFoundException
|
|
|
+ */
|
|
|
+ public FileInputStream getShareDeleteFileInputStream(
|
|
|
+ @Nullable FsVolumeSpi volume, File f,
|
|
|
+ long offset) throws IOException {
|
|
|
+ final long begin = eventHooks.beforeMetadataOp(volume, OPEN);
|
|
|
+ FileInputStream fis = null;
|
|
|
+ try {
|
|
|
+ fis = new WrappedFileInputStream(volume,
|
|
|
+ NativeIO.getShareDeleteFileDescriptor(f, offset));
|
|
|
+ eventHooks.afterMetadataOp(volume, OPEN, begin);
|
|
|
+ return fis;
|
|
|
+ } catch(Exception e) {
|
|
|
+ org.apache.commons.io.IOUtils.closeQuietly(fis);
|
|
|
+ eventHooks.onFailure(volume, OPEN, e, begin);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Create a FileInputStream using
|
|
|
+ * {@link FileInputStream#FileInputStream(File)} and position
|
|
|
+ * it at the given offset.
|
|
|
+ *
|
|
|
+ * Wraps the created input stream to intercept read calls
|
|
|
+ * before delegating to the wrapped stream.
|
|
|
+ *
|
|
|
+ * @param volume target volume. null if unavailable.
|
|
|
+ * @param f File object.
|
|
|
+ * @param offset the offset position, measured in bytes from the
|
|
|
+ * beginning of the file, at which to set the file
|
|
|
+ * pointer.
|
|
|
+ * @throws FileNotFoundException
|
|
|
+ */
|
|
|
+ public FileInputStream openAndSeek(
|
|
|
+ @Nullable FsVolumeSpi volume, File f, long offset) throws IOException {
|
|
|
+ final long begin = eventHooks.beforeMetadataOp(volume, OPEN);
|
|
|
+ FileInputStream fis = null;
|
|
|
+ try {
|
|
|
+ fis = new WrappedFileInputStream(volume,
|
|
|
+ FsDatasetUtil.openAndSeek(f, offset));
|
|
|
+ eventHooks.afterMetadataOp(volume, OPEN, begin);
|
|
|
+ return fis;
|
|
|
+ } catch(Exception e) {
|
|
|
+ org.apache.commons.io.IOUtils.closeQuietly(fis);
|
|
|
+ eventHooks.onFailure(volume, OPEN, e, begin);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Create a RandomAccessFile using
|
|
|
+ * {@link RandomAccessFile#RandomAccessFile(File, String)}.
|
|
|
+ *
|
|
|
+ * Wraps the created input stream to intercept IO calls
|
|
|
+ * before delegating to the wrapped RandomAccessFile.
|
|
|
+ *
|
|
|
+ * @param volume target volume. null if unavailable.
|
|
|
+ * @param f File object.
|
|
|
+ * @param mode See {@link RandomAccessFile} for a description
|
|
|
+ * of the mode string.
|
|
|
+ * @return RandomAccessFile representing the given file.
|
|
|
+ * @throws FileNotFoundException
|
|
|
+ */
|
|
|
+ public RandomAccessFile getRandomAccessFile(
|
|
|
+ @Nullable FsVolumeSpi volume, File f,
|
|
|
+ String mode) throws FileNotFoundException {
|
|
|
+ final long begin = eventHooks.beforeMetadataOp(volume, OPEN);
|
|
|
+ RandomAccessFile raf = null;
|
|
|
+ try {
|
|
|
+ raf = new WrappedRandomAccessFile(volume, f, mode);
|
|
|
+ eventHooks.afterMetadataOp(volume, OPEN, begin);
|
|
|
+ return raf;
|
|
|
+ } catch(Exception e) {
|
|
|
+ org.apache.commons.io.IOUtils.closeQuietly(raf);
|
|
|
+ eventHooks.onFailure(volume, OPEN, e, begin);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Delete the given directory using {@link FileUtil#fullyDelete(File)}.
|
|
|
+ *
|
|
|
+ * @param volume target volume. null if unavailable.
|
|
|
+ * @param dir directory to be deleted.
|
|
|
+ * @return true on success false on failure.
|
|
|
+ */
|
|
|
+ public boolean fullyDelete(@Nullable FsVolumeSpi volume, File dir) {
|
|
|
+ final long begin = eventHooks.beforeMetadataOp(volume, DELETE);
|
|
|
+ try {
|
|
|
+ boolean deleted = FileUtil.fullyDelete(dir);
|
|
|
+ eventHooks.afterMetadataOp(volume, DELETE, begin);
|
|
|
+ return deleted;
|
|
|
+ } catch(Exception e) {
|
|
|
+ eventHooks.onFailure(volume, DELETE, e, begin);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Move the src file to the target using
|
|
|
+ * {@link FileUtil#replaceFile(File, File)}.
|
|
|
+ *
|
|
|
+ * @param volume target volume. null if unavailable.
|
|
|
+ * @param src source path.
|
|
|
+ * @param target target path.
|
|
|
+ * @throws IOException
|
|
|
+ */
|
|
|
+ public void replaceFile(
|
|
|
+ @Nullable FsVolumeSpi volume, File src, File target) throws IOException {
|
|
|
+ final long begin = eventHooks.beforeMetadataOp(volume, MOVE);
|
|
|
+ try {
|
|
|
+ FileUtil.replaceFile(src, target);
|
|
|
+ eventHooks.afterMetadataOp(volume, MOVE, begin);
|
|
|
+ } catch(Exception e) {
|
|
|
+ eventHooks.onFailure(volume, MOVE, e, begin);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Move the src file to the target using
|
|
|
+ * {@link Storage#rename(File, File)}.
|
|
|
+ *
|
|
|
+ * @param volume target volume. null if unavailable.
|
|
|
+ * @param src source path.
|
|
|
+ * @param target target path.
|
|
|
+ * @throws IOException
|
|
|
+ */
|
|
|
+ public void rename(
|
|
|
+ @Nullable FsVolumeSpi volume, File src, File target)
|
|
|
+ throws IOException {
|
|
|
+ final long begin = eventHooks.beforeMetadataOp(volume, MOVE);
|
|
|
+ try {
|
|
|
+ Storage.rename(src, target);
|
|
|
+ eventHooks.afterMetadataOp(volume, MOVE, begin);
|
|
|
+ } catch(Exception e) {
|
|
|
+ eventHooks.onFailure(volume, MOVE, e, begin);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Move the src file to the target using
|
|
|
+ * {@link FileUtils#moveFile(File, File)}.
|
|
|
+ *
|
|
|
+ * @param volume target volume. null if unavailable.
|
|
|
+ * @param src source path.
|
|
|
+ * @param target target path.
|
|
|
+ * @throws IOException
|
|
|
+ */
|
|
|
+ public void moveFile(
|
|
|
+ @Nullable FsVolumeSpi volume, File src, File target)
|
|
|
+ throws IOException {
|
|
|
+ final long begin = eventHooks.beforeMetadataOp(volume, MOVE);
|
|
|
+ try {
|
|
|
+ FileUtils.moveFile(src, target);
|
|
|
+ eventHooks.afterMetadataOp(volume, MOVE, begin);
|
|
|
+ } catch(Exception e) {
|
|
|
+ eventHooks.onFailure(volume, MOVE, e, begin);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Move the src file to the target using
|
|
|
+ * {@link Files#move(Path, Path, CopyOption...)}.
|
|
|
+ *
|
|
|
+ * @param volume target volume. null if unavailable.
|
|
|
+ * @param src source path.
|
|
|
+ * @param target target path.
|
|
|
+ * @param options See {@link Files#move} for a description
|
|
|
+ * of the options.
|
|
|
+ * @throws IOException
|
|
|
+ */
|
|
|
+ public void move(
|
|
|
+ @Nullable FsVolumeSpi volume, Path src, Path target,
|
|
|
+ CopyOption... options) throws IOException {
|
|
|
+ final long begin = eventHooks.beforeMetadataOp(volume, MOVE);
|
|
|
+ try {
|
|
|
+ Files.move(src, target, options);
|
|
|
+ eventHooks.afterMetadataOp(volume, MOVE, begin);
|
|
|
+ } catch(Exception e) {
|
|
|
+ eventHooks.onFailure(volume, MOVE, e, begin);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * See {@link Storage#nativeCopyFileUnbuffered(File, File, boolean)}.
|
|
|
+ *
|
|
|
+ * @param volume target volume. null if unavailable.
|
|
|
+ * @param src an existing file to copy, must not be {@code null}
|
|
|
+ * @param target the new file, must not be {@code null}
|
|
|
+ * @param preserveFileDate true if the file date of the copy
|
|
|
+ * should be the same as the original
|
|
|
+ * @throws IOException
|
|
|
+ */
|
|
|
+ public void nativeCopyFileUnbuffered(
|
|
|
+ @Nullable FsVolumeSpi volume, File src, File target,
|
|
|
+ boolean preserveFileDate) throws IOException {
|
|
|
+ final long length = src.length();
|
|
|
+ final long begin = eventHooks.beforeFileIo(volume, NATIVE_COPY, length);
|
|
|
+ try {
|
|
|
+ Storage.nativeCopyFileUnbuffered(src, target, preserveFileDate);
|
|
|
+ eventHooks.afterFileIo(volume, NATIVE_COPY, begin, length);
|
|
|
+ } catch(Exception e) {
|
|
|
+ eventHooks.onFailure(volume, NATIVE_COPY, e, begin);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * See {@link File#mkdirs()}.
|
|
|
+ *
|
|
|
+ * @param volume target volume. null if unavailable.
|
|
|
+ * @param dir directory to be created.
|
|
|
+ * @return true only if the directory was created. false if
|
|
|
+ * the directory already exists.
|
|
|
+ * @throws IOException if a directory with the given name does
|
|
|
+ * not exist and could not be created.
|
|
|
+ */
|
|
|
+ public boolean mkdirs(
|
|
|
+ @Nullable FsVolumeSpi volume, File dir) throws IOException {
|
|
|
+ final long begin = eventHooks.beforeMetadataOp(volume, MKDIRS);
|
|
|
+ boolean created = false;
|
|
|
+ boolean isDirectory;
|
|
|
+ try {
|
|
|
+ created = dir.mkdirs();
|
|
|
+ isDirectory = !created && dir.isDirectory();
|
|
|
+ eventHooks.afterMetadataOp(volume, MKDIRS, begin);
|
|
|
+ } catch(Exception e) {
|
|
|
+ eventHooks.onFailure(volume, MKDIRS, e, begin);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!created && !isDirectory) {
|
|
|
+ throw new IOException("Mkdirs failed to create " + dir);
|
|
|
+ }
|
|
|
+ return created;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Create the target directory using {@link File#mkdirs()} only if
|
|
|
+ * it doesn't exist already.
|
|
|
+ *
|
|
|
+ * @param volume target volume. null if unavailable.
|
|
|
+ * @param dir directory to be created.
|
|
|
+ * @throws IOException if the directory could not created
|
|
|
+ */
|
|
|
+ public void mkdirsWithExistsCheck(
|
|
|
+ @Nullable FsVolumeSpi volume, File dir) throws IOException {
|
|
|
+ final long begin = eventHooks.beforeMetadataOp(volume, MKDIRS);
|
|
|
+ boolean succeeded = false;
|
|
|
+ try {
|
|
|
+ succeeded = dir.isDirectory() || dir.mkdirs();
|
|
|
+ eventHooks.afterMetadataOp(volume, MKDIRS, begin);
|
|
|
+ } catch(Exception e) {
|
|
|
+ eventHooks.onFailure(volume, MKDIRS, e, begin);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!succeeded) {
|
|
|
+ throw new IOException("Mkdirs failed to create " + dir);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get a listing of the given directory using
|
|
|
+ * {@link FileUtil#listFiles(File)}.
|
|
|
+ *
|
|
|
+ * @param volume target volume. null if unavailable.
|
|
|
+ * @param dir Directory to be listed.
|
|
|
+ * @return array of file objects representing the directory entries.
|
|
|
+ * @throws IOException
|
|
|
+ */
|
|
|
+ public File[] listFiles(
|
|
|
+ @Nullable FsVolumeSpi volume, File dir) throws IOException {
|
|
|
+ final long begin = eventHooks.beforeMetadataOp(volume, LIST);
|
|
|
+ try {
|
|
|
+ File[] children = FileUtil.listFiles(dir);
|
|
|
+ eventHooks.afterMetadataOp(volume, LIST, begin);
|
|
|
+ return children;
|
|
|
+ } catch(Exception e) {
|
|
|
+ eventHooks.onFailure(volume, LIST, e, begin);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get a listing of the given directory using
|
|
|
+ * {@link FileUtil#listFiles(File)}.
|
|
|
+ *
|
|
|
+ * @param volume target volume. null if unavailable.
|
|
|
+ * @param Driectory to be listed.
|
|
|
+ * @return array of strings representing the directory entries.
|
|
|
+ * @throws IOException
|
|
|
+ */
|
|
|
+ public String[] list(
|
|
|
+ @Nullable FsVolumeSpi volume, File dir) throws IOException {
|
|
|
+ final long begin = eventHooks.beforeMetadataOp(volume, LIST);
|
|
|
+ try {
|
|
|
+ String[] children = FileUtil.list(dir);
|
|
|
+ eventHooks.afterMetadataOp(volume, LIST, begin);
|
|
|
+ return children;
|
|
|
+ } catch(Exception e) {
|
|
|
+ eventHooks.onFailure(volume, LIST, e, begin);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get a listing of the given directory using
|
|
|
+ * {@link IOUtils#listDirectory(File, FilenameFilter)}.
|
|
|
+ *
|
|
|
+ * @param volume target volume. null if unavailable.
|
|
|
+ * @param dir Directory to list.
|
|
|
+ * @param filter {@link FilenameFilter} to filter the directory entries.
|
|
|
+ * @throws IOException
|
|
|
+ */
|
|
|
+ public List<String> listDirectory(
|
|
|
+ @Nullable FsVolumeSpi volume, File dir,
|
|
|
+ FilenameFilter filter) throws IOException {
|
|
|
+ final long begin = eventHooks.beforeMetadataOp(volume, LIST);
|
|
|
+ try {
|
|
|
+ List<String> children = IOUtils.listDirectory(dir, filter);
|
|
|
+ eventHooks.afterMetadataOp(volume, LIST, begin);
|
|
|
+ return children;
|
|
|
+ } catch(Exception e) {
|
|
|
+ eventHooks.onFailure(volume, LIST, e, begin);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Retrieves the number of links to the specified file.
|
|
|
+ *
|
|
|
+ * @param volume target volume. null if unavailable.
|
|
|
+ * @param f file whose link count is being queried.
|
|
|
+ * @return number of hard-links to the given file, including the
|
|
|
+ * given path itself.
|
|
|
+ * @throws IOException
|
|
|
+ */
|
|
|
+ public int getHardLinkCount(
|
|
|
+ @Nullable FsVolumeSpi volume, File f) throws IOException {
|
|
|
+ final long begin = eventHooks.beforeMetadataOp(volume, LIST);
|
|
|
+ try {
|
|
|
+ int count = HardLink.getLinkCount(f);
|
|
|
+ eventHooks.afterMetadataOp(volume, LIST, begin);
|
|
|
+ return count;
|
|
|
+ } catch(Exception e) {
|
|
|
+ eventHooks.onFailure(volume, LIST, e, begin);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Check for file existence using {@link File#exists()}.
|
|
|
+ *
|
|
|
+ * @param volume target volume. null if unavailable.
|
|
|
+ * @param f file object.
|
|
|
+ * @return true if the file exists.
|
|
|
+ */
|
|
|
+ public boolean exists(@Nullable FsVolumeSpi volume, File f) {
|
|
|
+ final long begin = eventHooks.beforeMetadataOp(volume, EXISTS);
|
|
|
+ try {
|
|
|
+ boolean exists = f.exists();
|
|
|
+ eventHooks.afterMetadataOp(volume, EXISTS, begin);
|
|
|
+ return exists;
|
|
|
+ } catch(Exception e) {
|
|
|
+ eventHooks.onFailure(volume, EXISTS, e, begin);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * A thin wrapper over {@link FileInputStream} that allows
|
|
|
+ * instrumenting disk IO.
|
|
|
+ */
|
|
|
+ private final class WrappedFileInputStream extends FileInputStream {
|
|
|
+ private @Nullable final FsVolumeSpi volume;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * {@inheritDoc}.
|
|
|
+ */
|
|
|
+ private WrappedFileInputStream(@Nullable FsVolumeSpi volume, File f)
|
|
|
+ throws FileNotFoundException {
|
|
|
+ super(f);
|
|
|
+ this.volume = volume;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * {@inheritDoc}.
|
|
|
+ */
|
|
|
+ private WrappedFileInputStream(
|
|
|
+ @Nullable FsVolumeSpi volume, FileDescriptor fd) {
|
|
|
+ super(fd);
|
|
|
+ this.volume = volume;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * {@inheritDoc}.
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public int read() throws IOException {
|
|
|
+ final long begin = eventHooks.beforeFileIo(volume, READ, 1);
|
|
|
+ try {
|
|
|
+ int b = super.read();
|
|
|
+ eventHooks.afterFileIo(volume, READ, begin, 1);
|
|
|
+ return b;
|
|
|
+ } catch(Exception e) {
|
|
|
+ eventHooks.onFailure(volume, READ, e, begin);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * {@inheritDoc}.
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public int read(@Nonnull byte[] b) throws IOException {
|
|
|
+ final long begin = eventHooks.beforeFileIo(volume, READ, b.length);
|
|
|
+ try {
|
|
|
+ int numBytesRead = super.read(b);
|
|
|
+ eventHooks.afterFileIo(volume, READ, begin, numBytesRead);
|
|
|
+ return numBytesRead;
|
|
|
+ } catch(Exception e) {
|
|
|
+ eventHooks.onFailure(volume, READ, e, begin);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * {@inheritDoc}.
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public int read(@Nonnull byte[] b, int off, int len) throws IOException {
|
|
|
+ final long begin = eventHooks.beforeFileIo(volume, READ, len);
|
|
|
+ try {
|
|
|
+ int numBytesRead = super.read(b, off, len);
|
|
|
+ eventHooks.afterFileIo(volume, READ, begin, numBytesRead);
|
|
|
+ return numBytesRead;
|
|
|
+ } catch(Exception e) {
|
|
|
+ eventHooks.onFailure(volume, READ, e, begin);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * A thin wrapper over {@link FileOutputStream} that allows
|
|
|
+ * instrumenting disk IO.
|
|
|
+ */
|
|
|
+ private final class WrappedFileOutputStream extends FileOutputStream {
|
|
|
+ private @Nullable final FsVolumeSpi volume;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * {@inheritDoc}.
|
|
|
+ */
|
|
|
+ private WrappedFileOutputStream(
|
|
|
+ @Nullable FsVolumeSpi volume, File f,
|
|
|
+ boolean append) throws FileNotFoundException {
|
|
|
+ super(f, append);
|
|
|
+ this.volume = volume;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * {@inheritDoc}.
|
|
|
+ */
|
|
|
+ private WrappedFileOutputStream(
|
|
|
+ @Nullable FsVolumeSpi volume, FileDescriptor fd) {
|
|
|
+ super(fd);
|
|
|
+ this.volume = volume;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * {@inheritDoc}.
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void write(int b) throws IOException {
|
|
|
+ final long begin = eventHooks.beforeFileIo(volume, WRITE, 1);
|
|
|
+ try {
|
|
|
+ super.write(b);
|
|
|
+ eventHooks.afterFileIo(volume, WRITE, begin, 1);
|
|
|
+ } catch(Exception e) {
|
|
|
+ eventHooks.onFailure(volume, WRITE, e, begin);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * {@inheritDoc}.
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void write(@Nonnull byte[] b) throws IOException {
|
|
|
+ final long begin = eventHooks.beforeFileIo(volume, WRITE, b.length);
|
|
|
+ try {
|
|
|
+ super.write(b);
|
|
|
+ eventHooks.afterFileIo(volume, WRITE, begin, b.length);
|
|
|
+ } catch(Exception e) {
|
|
|
+ eventHooks.onFailure(volume, WRITE, e, begin);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * {@inheritDoc}.
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void write(@Nonnull byte[] b, int off, int len) throws IOException {
|
|
|
+ final long begin = eventHooks.beforeFileIo(volume, WRITE, len);
|
|
|
+ try {
|
|
|
+ super.write(b, off, len);
|
|
|
+ eventHooks.afterFileIo(volume, WRITE, begin, len);
|
|
|
+ } catch(Exception e) {
|
|
|
+ eventHooks.onFailure(volume, WRITE, e, begin);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * A thin wrapper over {@link FileInputStream} that allows
|
|
|
+ * instrumenting IO.
|
|
|
+ */
|
|
|
+ private final class WrappedRandomAccessFile extends RandomAccessFile {
|
|
|
+ private @Nullable final FsVolumeSpi volume;
|
|
|
+
|
|
|
+ public WrappedRandomAccessFile(
|
|
|
+ @Nullable FsVolumeSpi volume, File f, String mode)
|
|
|
+ throws FileNotFoundException {
|
|
|
+ super(f, mode);
|
|
|
+ this.volume = volume;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public int read() throws IOException {
|
|
|
+ final long begin = eventHooks.beforeFileIo(volume, READ, 1);
|
|
|
+ try {
|
|
|
+ int b = super.read();
|
|
|
+ eventHooks.afterFileIo(volume, READ, begin, 1);
|
|
|
+ return b;
|
|
|
+ } catch(Exception e) {
|
|
|
+ eventHooks.onFailure(volume, READ, e, begin);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public int read(byte[] b, int off, int len) throws IOException {
|
|
|
+ final long begin = eventHooks.beforeFileIo(volume, READ, len);
|
|
|
+ try {
|
|
|
+ int numBytesRead = super.read(b, off, len);
|
|
|
+ eventHooks.afterFileIo(volume, READ, begin, numBytesRead);
|
|
|
+ return numBytesRead;
|
|
|
+ } catch(Exception e) {
|
|
|
+ eventHooks.onFailure(volume, READ, e, begin);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public int read(byte[] b) throws IOException {
|
|
|
+ final long begin = eventHooks.beforeFileIo(volume, READ, b.length);
|
|
|
+ try {
|
|
|
+ int numBytesRead = super.read(b);
|
|
|
+ eventHooks.afterFileIo(volume, READ, begin, numBytesRead);
|
|
|
+ return numBytesRead;
|
|
|
+ } catch(Exception e) {
|
|
|
+ eventHooks.onFailure(volume, READ, e, begin);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void write(int b) throws IOException {
|
|
|
+ final long begin = eventHooks.beforeFileIo(volume, WRITE, 1);
|
|
|
+ try {
|
|
|
+ super.write(b);
|
|
|
+ eventHooks.afterFileIo(volume, WRITE, begin, 1);
|
|
|
+ } catch(Exception e) {
|
|
|
+ eventHooks.onFailure(volume, WRITE, e, begin);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void write(@Nonnull byte[] b) throws IOException {
|
|
|
+ final long begin = eventHooks.beforeFileIo(volume, WRITE, b.length);
|
|
|
+ try {
|
|
|
+ super.write(b);
|
|
|
+ eventHooks.afterFileIo(volume, WRITE, begin, b.length);
|
|
|
+ } catch(Exception e) {
|
|
|
+ eventHooks.onFailure(volume, WRITE, e, begin);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void write(byte[] b, int off, int len) throws IOException {
|
|
|
+ final long begin = eventHooks.beforeFileIo(volume, WRITE, len);
|
|
|
+ try {
|
|
|
+ super.write(b, off, len);
|
|
|
+ eventHooks.afterFileIo(volume, WRITE, begin, len);
|
|
|
+ } catch(Exception e) {
|
|
|
+ eventHooks.onFailure(volume, WRITE, e, begin);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|