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 /**
095 * Close the atomic file, but do not "commit" the temporary file
096 * on top of the destination. This should be used if there is a failure
097 * in writing.
098 */
099 public void abort() {
100 try {
101 super.close();
102 } catch (IOException ioe) {
103 LOG.warn("Unable to abort file " + tmpFile, ioe);
104 }
105 if (!tmpFile.delete()) {
106 LOG.warn("Unable to delete tmp file during abort " + tmpFile);
107 }
108 }
109
110 }