View Javadoc
1   /*
2    * junixsocket
3    *
4    * Copyright 2009-2024 Christian Kohlschütter
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * 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.newsclub.net.unix.rmi;
19  
20  import java.io.File;
21  import java.io.IOException;
22  import java.nio.file.Files;
23  import java.rmi.Naming;
24  import java.rmi.RemoteException;
25  import java.rmi.ServerException;
26  import java.rmi.registry.Registry;
27  import java.rmi.server.RMIClientSocketFactory;
28  import java.rmi.server.RMIServerSocketFactory;
29  import java.rmi.server.UnicastRemoteObject;
30  import java.util.Objects;
31  import java.util.concurrent.TimeUnit;
32  
33  import org.newsclub.net.unix.AFUNIXSocket;
34  
35  /**
36   * The {@link AFUNIXSocket}-compatible equivalent of {@link Naming}. Use this class for accessing
37   * RMI registries that are reachable by {@link AFUNIXSocket}s.
38   *
39   * @author Christian Kohlschütter
40   */
41  public final class AFUNIXNaming extends AFNaming {
42    private static final String PROP_RMI_SOCKET_DIR = "org.newsclub.net.unix.rmi.socketdir";
43    private static final File DEFAULT_SOCKET_DIRECTORY = new File(System.getProperty(
44        PROP_RMI_SOCKET_DIR, "/tmp"));
45  
46    private boolean deleteRegistrySocketDir = false;
47    private final File registrySocketDir;
48  
49    private final RMIClientSocketFactory defaultClientSocketFactory;
50    private final RMIServerSocketFactory defaultServerSocketFactory;
51  
52    private final String socketPrefix;
53    private final String socketSuffix;
54  
55    private AFUNIXNaming(File socketDir, int registryPort, String socketPrefix, String socketSuffix)
56        throws IOException {
57      super(registryPort, RMIPorts.RMI_SERVICE_PORT);
58      Objects.requireNonNull(socketDir);
59      this.registrySocketDir = socketDir;
60      this.socketPrefix = socketPrefix;
61      this.socketSuffix = socketSuffix;
62  
63      this.defaultClientSocketFactory = null; // DefaultRMIClientSocketFactory.getInstance();
64      this.defaultServerSocketFactory = null; // DefaultRMIServerSocketFactory.getInstance();
65    }
66  
67    /**
68     * Returns the directory where RMI sockets are stored by default.
69     *
70     * You can configure this location by setting the System property
71     * {@code org.newsclub.net.unix.rmi.socketdir} upon start.
72     *
73     * @return The directory.
74     */
75    public static File getDefaultSocketDirectory() {
76      return DEFAULT_SOCKET_DIRECTORY;
77    }
78  
79    /**
80     * Returns a new private instance that resides in a custom location, to avoid any collisions with
81     * existing instances.
82     *
83     * @return The private {@link AFNaming} instance.
84     * @throws IOException if the operation fails.
85     */
86    public static AFUNIXNaming newPrivateInstance() throws IOException {
87      File tmpDir = Files.createTempDirectory("junixsocket-").toFile();
88      if (!tmpDir.canWrite()) {
89        throw new IOException("Could not create temporary directory: " + tmpDir);
90      }
91      AFUNIXNaming instance = getInstance(tmpDir, RMIPorts.DEFAULT_REGISTRY_PORT);
92      synchronized (instance) {
93        instance.deleteRegistrySocketDir = true;
94      }
95      return instance;
96    }
97  
98    /**
99     * Returns the default instance of {@link AFUNIXNaming}. Sockets are stored in
100    * <code>java.io.tmpdir</code>.
101    *
102    * @return The default instance.
103    * @throws IOException if the operation fails.
104    */
105   public static AFUNIXNaming getInstance() throws IOException {
106     return getInstance(DEFAULT_SOCKET_DIRECTORY, RMIPorts.DEFAULT_REGISTRY_PORT);
107   }
108 
109   /**
110    * Returns a {@link AFUNIXNaming} instance which support several socket files that can be stored
111    * under the same, given directory.
112    *
113    * @param socketDir The directory to store sockets in.
114    * @return The instance.
115    * @throws RemoteException if the operation fails.
116    */
117   public static AFUNIXNaming getInstance(final File socketDir) throws RemoteException {
118     return getInstance(socketDir, RMIPorts.DEFAULT_REGISTRY_PORT);
119   }
120 
121   /**
122    * Returns a {@link AFUNIXNaming} instance which support several socket files that can be stored
123    * under the same, given directory.
124    *
125    * A custom "registry port" can be specified. Typically, AF-UNIX specific ports should be above
126    * {@code 100000}.
127    *
128    * @param socketDir The directory to store sockets in.
129    * @param registryPort The registry port. Should be above {@code 100000}.
130    * @return The instance.
131    * @throws RemoteException if the operation fails.
132    */
133   public static AFUNIXNaming getInstance(File socketDir, final int registryPort)
134       throws RemoteException {
135     return getInstance(socketDir, registryPort, null, null);
136   }
137 
138   /**
139    * Returns a {@link AFUNIXNaming} instance which support several socket files that can be stored
140    * under the same, given directory.
141    *
142    * A custom "registry port" can be specified. Typically, AF-UNIX specific ports should be above
143    * {@code 100000}.
144    *
145    * @param socketDir The directory to store sockets in.
146    * @param registryPort The registry port. Should be above {@code 100000}.
147    * @param socketPrefix A string to be inserted at the beginning of each socket filename, or
148    *          {@code null}.
149    * @param socketSuffix A string to be added at the end of each socket filename, or {@code null}.
150    * @return The instance.
151    * @throws RemoteException if the operation fails.
152    */
153   public static AFUNIXNaming getInstance(File socketDir, final int registryPort,
154       String socketPrefix, String socketSuffix) throws RemoteException {
155     return AFNaming.getInstance(registryPort, new AFUNIXNamingProvider(socketDir, socketPrefix,
156         socketSuffix));
157   }
158 
159   private static final class AFUNIXNamingProvider implements AFNamingProvider<AFUNIXNaming> {
160     private final File socketDir;
161     private final String socketPrefix;
162     private final String socketSuffix;
163 
164     public AFUNIXNamingProvider(File socketDir, String socketPrefix, String socketSuffix)
165         throws RemoteException {
166       try {
167         this.socketDir = socketDir.getCanonicalFile();
168       } catch (IOException e) {
169         throw new RemoteException(e.getMessage(), e);
170       }
171       this.socketPrefix = socketPrefix == null ? AFUNIXRMISocketFactory.DEFAULT_SOCKET_FILE_PREFIX
172           : socketPrefix;
173       this.socketSuffix = socketSuffix == null ? AFUNIXRMISocketFactory.DEFAULT_SOCKET_FILE_SUFFIX
174           : socketSuffix;
175     }
176 
177     @Override
178     public AFUNIXNaming newInstance(int port) throws IOException {
179       return new AFUNIXNaming(socketDir, port, socketPrefix, socketSuffix); // NOPMD
180     }
181   }
182 
183   /**
184    * Returns an {@link AFUNIXNaming} instance which only supports one file. (Probably only useful
185    * when you want/can access the exported {@link UnicastRemoteObject} directly)
186    *
187    * @param socketFile The socket file.
188    * @return The instance.
189    * @throws IOException if the operation fails.
190    */
191   public static AFNaming getSingleFileInstance(final File socketFile) throws IOException {
192     return getInstance(socketFile, RMIPorts.PLAIN_FILE_SOCKET);
193   }
194 
195   @Override
196   public AFUNIXRMISocketFactory getSocketFactory() {
197     return (AFUNIXRMISocketFactory) super.getSocketFactory();
198   }
199 
200   @Override
201   public AFRegistry getRegistry() throws RemoteException {
202     return getRegistry(0, TimeUnit.SECONDS);
203   }
204 
205   @Override
206   public AFUNIXRegistry getRegistry(long timeout, TimeUnit unit) throws RemoteException {
207     return (AFUNIXRegistry) super.getRegistry(timeout, unit);
208   }
209 
210   @Override
211   public AFUNIXRegistry getRegistry(boolean create) throws RemoteException {
212     return (AFUNIXRegistry) super.getRegistry(create);
213   }
214 
215   @Override
216   public AFUNIXRegistry createRegistry() throws RemoteException {
217     return (AFUNIXRegistry) super.createRegistry();
218   }
219 
220   /**
221    * Returns the socket file which is used to control the RMI registry.
222    *
223    * The file is usually in the directory returned by {@link #getRegistrySocketDir()}.
224    *
225    * @return The directory.
226    */
227   public File getRegistrySocketFile() {
228     return getSocketFactory().getFile(getRegistryPort());
229   }
230 
231   @Override
232   protected AFUNIXRMISocketFactory initSocketFactory() throws IOException {
233     return new AFUNIXRMISocketFactory(this, registrySocketDir, defaultClientSocketFactory,
234         defaultServerSocketFactory, socketPrefix, socketSuffix);
235   }
236 
237   @Override
238   protected AFUNIXRegistry newAFRegistry(Registry impl) throws RemoteException {
239     return new AFUNIXRegistry(this, impl);
240   }
241 
242   @Override
243   protected AFRegistry openRegistry(long timeout, TimeUnit unit) throws RemoteException {
244     File socketFile = getRegistrySocketFile();
245     if (!socketFile.exists()) {
246       if (waitUntilFileExists(socketFile, timeout, unit)) {
247         AFRegistry reg = getRegistry(false);
248         if (reg != null) {
249           return reg;
250         }
251       }
252     }
253     throw new ShutdownException("Could not find registry at " + getRegistrySocketFile());
254   }
255 
256   private boolean waitUntilFileExists(File f, long timeout, TimeUnit unit) {
257     long timeWait = unit.toMillis(timeout);
258 
259     try {
260       while (timeWait > 0 && !f.exists()) {
261         Thread.sleep(Math.min(50, timeWait));
262         timeWait -= 50;
263       }
264     } catch (InterruptedException e) {
265       // ignored
266     }
267 
268     return f.exists();
269   }
270 
271   private synchronized void deleteSocketDir() {
272     if (deleteRegistrySocketDir && registrySocketDir != null) {
273       try {
274         Files.delete(registrySocketDir.toPath());
275       } catch (IOException e) {
276         // ignore
277       }
278     }
279   }
280 
281   @Override
282   protected void shutdownRegistryFinishingTouches() {
283     deleteSocketDir();
284   }
285 
286   /**
287    * Returns the directory in which sockets used by this registry are located.
288    *
289    * @return The directory.
290    */
291   public File getRegistrySocketDir() {
292     return registrySocketDir;
293   }
294 
295   @Override
296   protected void initRegistryPrerequisites() throws ServerException {
297     if (registrySocketDir != null && !registrySocketDir.mkdirs() && !registrySocketDir
298         .isDirectory()) {
299       throw new ServerException("Cannot create socket directory:" + registrySocketDir);
300     }
301   }
302 }