1 /** 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 package org.apache.hadoop.hdfs.util; 19 20 import java.io.File; 21 import java.io.FileNotFoundException; 22 import java.io.FileOutputStream; 23 import java.io.FilterOutputStream; 24 import java.io.IOException; 25 26 import org.apache.commons.logging.Log; 27 import org.apache.commons.logging.LogFactory; 28 import org.apache.hadoop.io.IOUtils; 29 import org.apache.hadoop.io.nativeio.NativeIO; 30 import org.apache.hadoop.io.nativeio.NativeIOException; 31 32 /** 33 * A FileOutputStream that has the property that it will only show 34 * up at its destination once it has been entirely written and flushed 35 * to disk. While being written, it will use a .tmp suffix. 36 * 37 * When the output stream is closed, it is flushed, fsynced, and 38 * will be moved into place, overwriting any file that already 39 * exists at that location. 40 * 41 * <b>NOTE</b>: on Windows platforms, it will not atomically 42 * replace the target file - instead the target file is deleted 43 * before this one is moved into place. 44 */ 45 public class AtomicFileOutputStream extends FilterOutputStream { 46 47 private static final String TMP_EXTENSION = ".tmp"; 48 49 private final static Log LOG = LogFactory.getLog( 50 AtomicFileOutputStream.class); 51 52 private final File origFile; 53 private final File tmpFile; 54 AtomicFileOutputStream(File f)55 public AtomicFileOutputStream(File f) throws FileNotFoundException { 56 // Code unfortunately must be duplicated below since we can't assign anything 57 // before calling super 58 super(new FileOutputStream(new File(f.getParentFile(), f.getName() + TMP_EXTENSION))); 59 origFile = f.getAbsoluteFile(); 60 tmpFile = new File(f.getParentFile(), f.getName() + TMP_EXTENSION).getAbsoluteFile(); 61 } 62 63 @Override close()64 public void close() throws IOException { 65 boolean triedToClose = false, success = false; 66 try { 67 flush(); 68 ((FileOutputStream)out).getChannel().force(true); 69 70 triedToClose = true; 71 super.close(); 72 success = true; 73 } finally { 74 if (success) { 75 boolean renamed = tmpFile.renameTo(origFile); 76 if (!renamed) { 77 // On windows, renameTo does not replace. 78 if (origFile.exists() && !origFile.delete()) { 79 throw new IOException("Could not delete original file " + origFile); 80 } 81 try { 82 NativeIO.renameTo(tmpFile, origFile); 83 } catch (NativeIOException e) { 84 throw new IOException("Could not rename temporary file " + tmpFile 85 + " to " + origFile + " due to failure in native rename. " 86 + e.toString()); 87 } 88 } 89 } else { 90 if (!triedToClose) { 91 // If we failed when flushing, try to close it to not leak an FD 92 IOUtils.closeStream(out); 93 } 94 // close wasn't successful, try to delete the tmp file 95 if (!tmpFile.delete()) { 96 LOG.warn("Unable to delete tmp file " + tmpFile); 97 } 98 } 99 } 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 */ abort()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 } 119