|
@@ -18,12 +18,18 @@
|
|
|
package org.apache.hadoop.hdfs.server.datanode;
|
|
|
|
|
|
import java.io.File;
|
|
|
+import java.io.FileInputStream;
|
|
|
+import java.io.FileOutputStream;
|
|
|
+import java.io.IOException;
|
|
|
import java.util.HashMap;
|
|
|
import java.util.Map;
|
|
|
|
|
|
import org.apache.hadoop.classification.InterfaceAudience;
|
|
|
+import org.apache.hadoop.fs.FileUtil;
|
|
|
+import org.apache.hadoop.fs.HardLink;
|
|
|
import org.apache.hadoop.hdfs.protocol.Block;
|
|
|
import org.apache.hadoop.hdfs.server.datanode.fsdataset.FsVolumeSpi;
|
|
|
+import org.apache.hadoop.io.IOUtils;
|
|
|
import org.apache.hadoop.util.LightWeightResizableGSet;
|
|
|
|
|
|
import com.google.common.annotations.VisibleForTesting;
|
|
@@ -193,6 +199,22 @@ abstract public class ReplicaInfo extends Block
|
|
|
return new ReplicaDirInfo(currentDir.getAbsolutePath(), hasSubdirs);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * check if this replica has already been unlinked.
|
|
|
+ * @return true if the replica has already been unlinked
|
|
|
+ * or no need to be detached; false otherwise
|
|
|
+ */
|
|
|
+ public boolean isUnlinked() {
|
|
|
+ return true; // no need to be unlinked
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * set that this replica is unlinked
|
|
|
+ */
|
|
|
+ public void setUnlinked() {
|
|
|
+ // no need to be unlinked
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* Number of bytes reserved for this replica on disk.
|
|
|
*/
|
|
@@ -210,6 +232,72 @@ abstract public class ReplicaInfo extends Block
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Copy specified file into a temporary file. Then rename the
|
|
|
+ * temporary file to the original name. This will cause any
|
|
|
+ * hardlinks to the original file to be removed. The temporary
|
|
|
+ * files are created in the same directory. The temporary files will
|
|
|
+ * be recovered (especially on Windows) on datanode restart.
|
|
|
+ */
|
|
|
+ private void unlinkFile(File file, Block b) throws IOException {
|
|
|
+ File tmpFile = DatanodeUtil.createTmpFile(b, DatanodeUtil.getUnlinkTmpFile(file));
|
|
|
+ try {
|
|
|
+ FileInputStream in = new FileInputStream(file);
|
|
|
+ try {
|
|
|
+ FileOutputStream out = new FileOutputStream(tmpFile);
|
|
|
+ try {
|
|
|
+ IOUtils.copyBytes(in, out, 16*1024);
|
|
|
+ } finally {
|
|
|
+ out.close();
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ in.close();
|
|
|
+ }
|
|
|
+ if (file.length() != tmpFile.length()) {
|
|
|
+ throw new IOException("Copy of file " + file + " size " + file.length()+
|
|
|
+ " into file " + tmpFile +
|
|
|
+ " resulted in a size of " + tmpFile.length());
|
|
|
+ }
|
|
|
+ FileUtil.replaceFile(tmpFile, file);
|
|
|
+ } catch (IOException e) {
|
|
|
+ boolean done = tmpFile.delete();
|
|
|
+ if (!done) {
|
|
|
+ DataNode.LOG.info("detachFile failed to delete temporary file " +
|
|
|
+ tmpFile);
|
|
|
+ }
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Remove a hard link by copying the block to a temporary place and
|
|
|
+ * then moving it back
|
|
|
+ * @param numLinks number of hard links
|
|
|
+ * @return true if copy is successful;
|
|
|
+ * false if it is already detached or no need to be detached
|
|
|
+ * @throws IOException if there is any copy error
|
|
|
+ */
|
|
|
+ public boolean unlinkBlock(int numLinks) throws IOException {
|
|
|
+ if (isUnlinked()) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ File file = getBlockFile();
|
|
|
+ if (file == null || getVolume() == null) {
|
|
|
+ throw new IOException("detachBlock:Block not found. " + this);
|
|
|
+ }
|
|
|
+ File meta = getMetaFile();
|
|
|
+
|
|
|
+ if (HardLink.getLinkCount(file) > numLinks) {
|
|
|
+ DataNode.LOG.info("CopyOnWrite for block " + this);
|
|
|
+ unlinkFile(file, this);
|
|
|
+ }
|
|
|
+ if (HardLink.getLinkCount(meta) > numLinks) {
|
|
|
+ unlinkFile(meta, this);
|
|
|
+ }
|
|
|
+ setUnlinked();
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
@Override //Object
|
|
|
public String toString() {
|
|
|
return getClass().getSimpleName()
|