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 }