001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hdfs.util;
019
020import java.io.File;
021import java.io.FileNotFoundException;
022import java.io.FileOutputStream;
023import java.io.FilterOutputStream;
024import java.io.IOException;
025
026import org.apache.commons.logging.Log;
027import org.apache.commons.logging.LogFactory;
028import org.apache.hadoop.io.IOUtils;
029import org.apache.hadoop.io.nativeio.NativeIO;
030import org.apache.hadoop.io.nativeio.NativeIOException;
031
032/**
033 * A FileOutputStream that has the property that it will only show
034 * up at its destination once it has been entirely written and flushed
035 * to disk. While being written, it will use a .tmp suffix.
036 * 
037 * When the output stream is closed, it is flushed, fsynced, and
038 * will be moved into place, overwriting any file that already
039 * exists at that location.
040 * 
041 * <b>NOTE</b>: on Windows platforms, it will not atomically
042 * replace the target file - instead the target file is deleted
043 * before this one is moved into place.
044 */
045public class AtomicFileOutputStream extends FilterOutputStream {
046
047  private static final String TMP_EXTENSION = ".tmp";
048  
049  private final static Log LOG = LogFactory.getLog(
050      AtomicFileOutputStream.class);
051  
052  private final File origFile;
053  private final File tmpFile;
054  
055  public AtomicFileOutputStream(File f) throws FileNotFoundException {
056    // Code unfortunately must be duplicated below since we can't assign anything
057    // before calling super
058    super(new FileOutputStream(new File(f.getParentFile(), f.getName() + TMP_EXTENSION)));
059    origFile = f.getAbsoluteFile();
060    tmpFile = new File(f.getParentFile(), f.getName() + TMP_EXTENSION).getAbsoluteFile();
061  }
062
063  @Override
064  public void close() throws IOException {
065    boolean triedToClose = false, success = false;
066    try {
067      flush();
068      ((FileOutputStream)out).getChannel().force(true);
069
070      triedToClose = true;
071      super.close();
072      success = true;
073    } finally {
074      if (success) {
075        boolean renamed = tmpFile.renameTo(origFile);
076        if (!renamed) {
077          // On windows, renameTo does not replace.
078          if (origFile.exists() && !origFile.delete()) {
079            throw new IOException("Could not delete original file " + origFile);
080          }
081          try {
082            NativeIO.renameTo(tmpFile, origFile);
083          } catch (NativeIOException e) {
084            throw new IOException("Could not rename temporary file " + tmpFile
085              + " to " + origFile + " due to failure in native rename. "
086              + e.toString());
087          }
088        }
089      } else {
090        if (!triedToClose) {
091          // If we failed when flushing, try to close it to not leak an FD
092          IOUtils.closeStream(out);
093        }
094        // close wasn't successful, try to delete the tmp file
095        if (!tmpFile.delete()) {
096          LOG.warn("Unable to delete tmp file " + tmpFile);
097        }
098      }
099    }
100  }
101
102  /**
103   * Close the atomic file, but do not "commit" the temporary file
104   * on top of the destination. This should be used if there is a failure
105   * in writing.
106   */
107  public void abort() {
108    try {
109      super.close();
110    } catch (IOException ioe) {
111      LOG.warn("Unable to abort file " + tmpFile, ioe);
112    }
113    if (!tmpFile.delete()) {
114      LOG.warn("Unable to delete tmp file during abort " + tmpFile);
115    }
116  }
117
118}