|
@@ -19,6 +19,7 @@ package org.apache.hadoop.hdfs.server.common;
|
|
|
|
|
|
import java.io.File;
|
|
import java.io.File;
|
|
import java.io.FileOutputStream;
|
|
import java.io.FileOutputStream;
|
|
|
|
+import java.io.FileNotFoundException;
|
|
import java.io.IOException;
|
|
import java.io.IOException;
|
|
import java.io.RandomAccessFile;
|
|
import java.io.RandomAccessFile;
|
|
import java.lang.management.ManagementFactory;
|
|
import java.lang.management.ManagementFactory;
|
|
@@ -997,6 +998,93 @@ public abstract class Storage extends StorageInfo {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Copies a file (usually large) to a new location using native unbuffered IO.
|
|
|
|
+ * <p>
|
|
|
|
+ * This method copies the contents of the specified source file
|
|
|
|
+ * to the specified destination file using OS specific unbuffered IO.
|
|
|
|
+ * The goal is to avoid churning the file system buffer cache when copying
|
|
|
|
+ * large files. TheFileUtils#copyLarge function from apache-commons-io library
|
|
|
|
+ * can be used to achieve this with an internal memory buffer but is less
|
|
|
|
+ * efficient than the native unbuffered APIs such as sendfile() in Linux and
|
|
|
|
+ * CopyFileEx() in Windows wrapped in {@link NativeIO#copyFileUnbuffered}.
|
|
|
|
+ *
|
|
|
|
+ * The directory holding the destination file is created if it does not exist.
|
|
|
|
+ * If the destination file exists, then this method will delete it first.
|
|
|
|
+ * <p>
|
|
|
|
+ * <strong>Note:</strong> Setting <code>preserveFileDate</code> to
|
|
|
|
+ * {@code true} tries to preserve the file's last modified
|
|
|
|
+ * date/times using {@link File#setLastModified(long)}, however it is
|
|
|
|
+ * not guaranteed that the operation will succeed.
|
|
|
|
+ * If the modification operation fails, no indication is provided.
|
|
|
|
+ *
|
|
|
|
+ * @param srcFile an existing file to copy, must not be {@code null}
|
|
|
|
+ * @param destFile 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 NullPointerException if source or destination is {@code null}
|
|
|
|
+ * @throws IOException if source or destination is invalid
|
|
|
|
+ * @throws IOException if an IO error occurs during copying
|
|
|
|
+ */
|
|
|
|
+ public static void nativeCopyFileUnbuffered(File srcFile, File destFile,
|
|
|
|
+ boolean preserveFileDate) throws IOException {
|
|
|
|
+ if (srcFile == null) {
|
|
|
|
+ throw new NullPointerException("Source must not be null");
|
|
|
|
+ }
|
|
|
|
+ if (destFile == null) {
|
|
|
|
+ throw new NullPointerException("Destination must not be null");
|
|
|
|
+ }
|
|
|
|
+ if (srcFile.exists() == false) {
|
|
|
|
+ throw new FileNotFoundException("Source '" + srcFile + "' does not exist");
|
|
|
|
+ }
|
|
|
|
+ if (srcFile.isDirectory()) {
|
|
|
|
+ throw new IOException("Source '" + srcFile + "' exists but is a directory");
|
|
|
|
+ }
|
|
|
|
+ if (srcFile.getCanonicalPath().equals(destFile.getCanonicalPath())) {
|
|
|
|
+ throw new IOException("Source '" + srcFile + "' and destination '" +
|
|
|
|
+ destFile + "' are the same");
|
|
|
|
+ }
|
|
|
|
+ File parentFile = destFile.getParentFile();
|
|
|
|
+ if (parentFile != null) {
|
|
|
|
+ if (!parentFile.mkdirs() && !parentFile.isDirectory()) {
|
|
|
|
+ throw new IOException("Destination '" + parentFile
|
|
|
|
+ + "' directory cannot be created");
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ if (destFile.exists()) {
|
|
|
|
+ if (FileUtil.canWrite(destFile) == false) {
|
|
|
|
+ throw new IOException("Destination '" + destFile
|
|
|
|
+ + "' exists but is read-only");
|
|
|
|
+ } else {
|
|
|
|
+ if (destFile.delete() == false) {
|
|
|
|
+ throw new IOException("Destination '" + destFile
|
|
|
|
+ + "' exists but cannot be deleted");
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ try {
|
|
|
|
+ NativeIO.copyFileUnbuffered(srcFile, destFile);
|
|
|
|
+ } catch (NativeIOException e) {
|
|
|
|
+ throw new IOException("Failed to copy " + srcFile.getCanonicalPath()
|
|
|
|
+ + " to " + destFile.getCanonicalPath()
|
|
|
|
+ + " due to failure in NativeIO#copyFileUnbuffered(). "
|
|
|
|
+ + e.toString());
|
|
|
|
+ }
|
|
|
|
+ if (srcFile.length() != destFile.length()) {
|
|
|
|
+ throw new IOException("Failed to copy full contents from '" + srcFile
|
|
|
|
+ + "' to '" + destFile + "'");
|
|
|
|
+ }
|
|
|
|
+ if (preserveFileDate) {
|
|
|
|
+ if (destFile.setLastModified(srcFile.lastModified()) == false) {
|
|
|
|
+ if (LOG.isDebugEnabled()) {
|
|
|
|
+ LOG.debug("Failed to preserve last modified date from'" + srcFile
|
|
|
|
+ + "' to '" + destFile + "'");
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
/**
|
|
/**
|
|
* Recursively delete all the content of the directory first and then
|
|
* Recursively delete all the content of the directory first and then
|
|
* the directory itself from the local filesystem.
|
|
* the directory itself from the local filesystem.
|