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