View Javadoc
1   /*
2    * junixsocket
3    *
4    * Copyright 2009-2026 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;
19  
20  import java.io.File;
21  import java.io.FileDescriptor;
22  import java.io.IOException;
23  import java.io.RandomAccessFile;
24  import java.nio.channels.FileChannel;
25  import java.util.UUID;
26  import java.util.concurrent.atomic.AtomicBoolean;
27  
28  import com.kohlschutter.annotations.compiletime.SuppressFBWarnings;
29  
30  /**
31   * Hack to get a readable AND writable {@link FileChannel} for a {@link FileDescriptor}.
32   *
33   * @author Christian Kohlschütter
34   */
35  @SuppressFBWarnings("PATH_TRAVERSAL_IN")
36  final class RAFChannelProvider extends RandomAccessFile implements FileDescriptorAccess {
37    private static final File DEV_NULL = new File("/dev/null");
38  
39    private final File tempPath;
40    private final FileDescriptor fdObj;
41    private final FileDescriptor rafFdOrig = new FileDescriptor();
42    private final AtomicBoolean closed = new AtomicBoolean(false);
43  
44    private RAFChannelProvider(FileDescriptor fdObj) throws IOException {
45      this(fdObj, TempFileUtil.INSTANCE.newPathForUnixDomainSocket(false).toFile());
46    }
47  
48    private RAFChannelProvider(FileDescriptor fdObj, File tempPath) throws IOException {
49      this(fdObj, tempPath, true);
50    }
51  
52    private RAFChannelProvider(FileDescriptor fdObj, File tempPath, boolean delete)
53        throws IOException {
54      super(tempPath, "rw");
55      NativeUnixSocket.ensureSupported();
56  
57      this.tempPath = tempPath;
58      if (delete) {
59        if (!tempPath.delete() && tempPath.exists()) {
60          if (!tempPath.delete()) {
61            // we tried our best (looking at you, Windows)
62          }
63          tempPath = new File(tempPath.getParentFile(), "jux-" + UUID.randomUUID().toString()
64              + ".sock");
65          if (tempPath.exists()) {
66            throw new IOException("Could not create a temporary path: " + tempPath);
67          }
68        }
69        tempPath.deleteOnExit();
70      }
71  
72      this.fdObj = fdObj;
73  
74      FileDescriptor rafFdObj = getFD();
75      NativeUnixSocket.copyFileDescriptor(rafFdObj, rafFdOrig);
76      NativeUnixSocket.copyFileDescriptor(fdObj, rafFdObj);
77    }
78  
79    @SuppressFBWarnings("EI_EXPOSE_REP")
80    @Override
81    public FileDescriptor getFileDescriptor() {
82      return fdObj;
83    }
84  
85    @Override
86    public synchronized void close() throws IOException {
87      if (!closed.getAndSet(true)) {
88        NativeUnixSocket.copyFileDescriptor(rafFdOrig, getFD());
89        if (!tempPath.delete()) {
90          // we tried our best
91        }
92      }
93    }
94  
95    public static FileChannel getFileChannel(FileDescriptor fd) throws IOException {
96      return getFileChannel0(fd);
97    }
98  
99    @SuppressWarnings("resource")
100   private static FileChannel getFileChannel0(FileDescriptor fd) throws IOException {
101     if (DEV_NULL.canRead() && DEV_NULL.canWrite()) {
102       try {
103         return new RAFChannelProvider(fd, DEV_NULL, false).getChannel();
104       } catch (IOException e) {
105         // ignore
106       }
107     }
108     return new RAFChannelProvider(fd).getChannel();
109   }
110 }