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     */
018    package org.apache.hadoop.hdfs.util;
019    
020    import java.io.File;
021    import java.io.FileNotFoundException;
022    import java.io.FileOutputStream;
023    import java.io.FilterOutputStream;
024    import java.io.IOException;
025    
026    import org.apache.commons.logging.Log;
027    import org.apache.commons.logging.LogFactory;
028    import org.apache.hadoop.io.IOUtils;
029    
030    /**
031     * A FileOutputStream that has the property that it will only show
032     * up at its destination once it has been entirely written and flushed
033     * to disk. While being written, it will use a .tmp suffix.
034     * 
035     * When the output stream is closed, it is flushed, fsynced, and
036     * will be moved into place, overwriting any file that already
037     * exists at that location.
038     * 
039     * <b>NOTE</b>: on Windows platforms, it will not atomically
040     * replace the target file - instead the target file is deleted
041     * before this one is moved into place.
042     */
043    public class AtomicFileOutputStream extends FilterOutputStream {
044    
045      private static final String TMP_EXTENSION = ".tmp";
046      
047      private final static Log LOG = LogFactory.getLog(
048          AtomicFileOutputStream.class);
049      
050      private final File origFile;
051      private final File tmpFile;
052      
053      public AtomicFileOutputStream(File f) throws FileNotFoundException {
054        // Code unfortunately must be duplicated below since we can't assign anything
055        // before calling super
056        super(new FileOutputStream(new File(f.getParentFile(), f.getName() + TMP_EXTENSION)));
057        origFile = f.getAbsoluteFile();
058        tmpFile = new File(f.getParentFile(), f.getName() + TMP_EXTENSION).getAbsoluteFile();
059      }
060    
061      @Override
062      public void close() throws IOException {
063        boolean triedToClose = false, success = false;
064        try {
065          flush();
066          ((FileOutputStream)out).getChannel().force(true);
067    
068          triedToClose = true;
069          super.close();
070          success = true;
071        } finally {
072          if (success) {
073            boolean renamed = tmpFile.renameTo(origFile);
074            if (!renamed) {
075              // On windows, renameTo does not replace.
076              if (!origFile.delete() || !tmpFile.renameTo(origFile)) {
077                throw new IOException("Could not rename temporary file " +
078                    tmpFile + " to " + origFile);
079              }
080            }
081          } else {
082            if (!triedToClose) {
083              // If we failed when flushing, try to close it to not leak an FD
084              IOUtils.closeStream(out);
085            }
086            // close wasn't successful, try to delete the tmp file
087            if (!tmpFile.delete()) {
088              LOG.warn("Unable to delete tmp file " + tmpFile);
089            }
090          }
091        }
092      }
093    
094    }