RAFChannelProvider.java

/*
 * junixsocket
 *
 * Copyright 2009-2024 Christian Kohlschütter
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.newsclub.net.unix;

import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Hack to get a readable AND writable {@link FileChannel} for a {@link FileDescriptor}.
 *
 * @author Christian Kohlschütter
 */
final class RAFChannelProvider extends RandomAccessFile implements FileDescriptorAccess {
  private final File tempPath;
  private final FileDescriptor fdObj;
  private final FileDescriptor rafFdOrig = new FileDescriptor();
  private final AtomicBoolean closed = new AtomicBoolean(false);

  private RAFChannelProvider(FileDescriptor fdObj) throws IOException {
    this(fdObj, File.createTempFile("jux", ".sock"));
  }

  private RAFChannelProvider(FileDescriptor fdObj, File tempPath) throws IOException {
    super(tempPath, "rw");
    this.tempPath = tempPath;
    if (!tempPath.delete() && tempPath.exists()) {
      if (!tempPath.delete()) {
        // we tried our best (looking at you, Windows)
      }
      tempPath = new File(tempPath.getParentFile(), "jux-" + UUID.randomUUID().toString()
          + ".sock");
      if (tempPath.exists()) {
        throw new IOException("Could not create a temporary path: " + tempPath);
      }
    }
    tempPath.deleteOnExit();

    NativeUnixSocket.ensureSupported();

    this.fdObj = fdObj;

    FileDescriptor rafFdObj = getFD();
    NativeUnixSocket.copyFileDescriptor(rafFdObj, rafFdOrig);
    NativeUnixSocket.copyFileDescriptor(fdObj, rafFdObj);
  }

  @Override
  public FileDescriptor getFileDescriptor() {
    return fdObj;
  }

  @Override
  public synchronized void close() throws IOException {
    if (!closed.getAndSet(true)) {
      NativeUnixSocket.copyFileDescriptor(rafFdOrig, getFD());
      if (!tempPath.delete()) {
        // we tried our best
      }
    }
  }

  public static FileChannel getFileChannel(FileDescriptor fd) throws IOException {
    return getFileChannel0(fd);
  }

  @SuppressWarnings("resource")
  private static FileChannel getFileChannel0(FileDescriptor fd) throws IOException {
    return new RAFChannelProvider(fd).getChannel();
  }
}