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}