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.io.nativeio;
19 
20 import java.io.FileInputStream;
21 import java.io.IOException;
22 import java.io.FileDescriptor;
23 
24 import org.apache.commons.lang.SystemUtils;
25 import org.apache.commons.logging.Log;
26 import org.apache.commons.logging.LogFactory;
27 import org.apache.hadoop.classification.InterfaceAudience;
28 import org.apache.hadoop.classification.InterfaceStability;
29 
30 /**
31  * A factory for creating shared file descriptors inside a given directory.
32  * Typically, the directory will be /dev/shm or /tmp.
33  *
34  * We will hand out file descriptors that correspond to unlinked files residing
35  * in that directory.  These file descriptors are suitable for sharing across
36  * multiple processes and are both readable and writable.
37  *
38  * Because we unlink the temporary files right after creating them, a JVM crash
39  * usually does not leave behind any temporary files in the directory.  However,
40  * it may happen that we crash right after creating the file and before
41  * unlinking it.  In the constructor, we attempt to clean up after any such
42  * remnants by trying to unlink any temporary files created by previous
43  * SharedFileDescriptorFactory instances that also used our prefix.
44  */
45 @InterfaceAudience.Private
46 @InterfaceStability.Unstable
47 public class SharedFileDescriptorFactory {
48   public static final Log LOG = LogFactory.getLog(SharedFileDescriptorFactory.class);
49   private final String prefix;
50   private final String path;
51 
getLoadingFailureReason()52   public static String getLoadingFailureReason() {
53     if (!NativeIO.isAvailable()) {
54       return "NativeIO is not available.";
55     }
56     if (false) {
57       return "The OS is not UNIX.";
58     }
59     return null;
60   }
61 
62   /**
63    * Create a new SharedFileDescriptorFactory.
64    *
65    * @param prefix       The prefix to prepend to all the file names created
66    *                       by this factory.
67    * @param paths        An array of paths to use.  We will try each path in
68    *                       succession, and return a factory using the first
69    *                       usable path.
70    * @return             The factory.
71    * @throws IOException If a factory could not be created for any reason.
72    */
create(String prefix, String paths[])73   public static SharedFileDescriptorFactory create(String prefix,
74       String paths[]) throws IOException {
75     String loadingFailureReason = getLoadingFailureReason();
76     if (loadingFailureReason != null) {
77       throw new IOException(loadingFailureReason);
78     }
79     if (paths.length == 0) {
80       throw new IOException("no SharedFileDescriptorFactory paths were " +
81           "configured.");
82     }
83     StringBuilder errors = new StringBuilder();
84     String strPrefix = "";
85     for (String path : paths) {
86       try {
87         FileInputStream fis =
88             new FileInputStream(createDescriptor0(prefix + "test", path, 1));
89         fis.close();
90         deleteStaleTemporaryFiles0(prefix, path);
91         return new SharedFileDescriptorFactory(prefix, path);
92       } catch (IOException e) {
93         errors.append(strPrefix).append("Error creating file descriptor in ").
94                append(path).append(": ").append(e.getMessage());
95         strPrefix = ", ";
96       }
97     }
98     throw new IOException(errors.toString());
99   }
100 
101   /**
102    * Create a SharedFileDescriptorFactory.
103    *
104    * @param prefix    Prefix to add to all file names we use.
105    * @param path      Path to use.
106    */
SharedFileDescriptorFactory(String prefix, String path)107   private SharedFileDescriptorFactory(String prefix, String path) {
108     this.prefix = prefix;
109     this.path = path;
110   }
111 
getPath()112   public String getPath() {
113     return path;
114   }
115 
116   /**
117    * Create a shared file descriptor which will be both readable and writable.
118    *
119    * @param info           Information to include in the path of the
120    *                         generated descriptor.
121    * @param length         The starting file length.
122    *
123    * @return               The file descriptor, wrapped in a FileInputStream.
124    * @throws IOException   If there was an I/O or configuration error creating
125    *                         the descriptor.
126    */
createDescriptor(String info, int length)127   public FileInputStream createDescriptor(String info, int length)
128       throws IOException {
129     return new FileInputStream(
130         createDescriptor0(prefix + info, path, length));
131   }
132 
133   /**
134    * Delete temporary files in the directory, NOT following symlinks.
135    */
deleteStaleTemporaryFiles0(String prefix, String path)136   private static native void deleteStaleTemporaryFiles0(String prefix,
137       String path) throws IOException;
138 
139   /**
140    * Create a file with O_EXCL, and then resize it to the desired size.
141    */
createDescriptor0(String prefix, String path, int length)142   private static native FileDescriptor createDescriptor0(String prefix,
143       String path, int length) throws IOException;
144 }
145