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 }