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    import org.apache.hadoop.io.nativeio.NativeIO;
030    import 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     */
045    public 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    }