package org.jruby.util.io;
import java.io.Closeable;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channel;
import java.nio.channels.Channels;
import java.nio.channels.FileLock;
import java.nio.channels.OverlappingFileLockException;
import java.nio.channels.Pipe;
import jnr.constants.platform.Errno;
import jnr.constants.platform.Fcntl;
import jnr.posix.FileStat;
import jnr.posix.POSIX;
import org.jruby.Ruby;
import org.jruby.ext.fcntl.FcntlLibrary;
import org.jruby.platform.Platform;
import org.jruby.runtime.Helpers;
import org.jruby.util.JRubyFile;
import org.jruby.util.ResourceException;
public class PosixShim {
public static final int LOCK_SH = 1;
public static final int LOCK_EX = 2;
public static final int LOCK_NB = 4;
public static final int LOCK_UN = 8;
public static final int SEEK_SET = 0;
public static final int SEEK_CUR = 1;
public static final int SEEK_END = 2;
public PosixShim(Ruby runtime) {
this.runtime = runtime;
this.posix = runtime.getPosix();
}
public long lseek(ChannelFD fd, long offset, int type) {
clear();
if (fd.chSeek != null) {
int adj = 0;
try {
switch (type) {
case SEEK_SET:
return fd.chSeek.position(offset).position();
case SEEK_CUR:
return fd.chSeek.position(fd.chSeek.position() - adj + offset).position();
case SEEK_END:
return fd.chSeek.position(fd.chSeek.size() + offset).position();
default:
setErrno(Errno.EINVAL);
return -1;
}
} catch (IllegalArgumentException e) {
setErrno(Errno.EINVAL);
return -1;
} catch (IOException ioe) {
setErrno(Helpers.errnoFromException(ioe));
return -1;
}
} else if (fd.chNative != null) {
long ret = posix.lseekLong(fd.chNative.getFD(), offset, type);
if (ret == -1) setErrno(Errno.valueOf(posix.errno()));
return ret;
}
if (fd.chSelect != null) {
setErrno(Errno.EPIPE);
return -1;
}
return 0;
}
public int write(ChannelFD fd, byte[] bytes, int offset, int length, boolean nonblock) {
clear();
ByteBuffer tmp = ByteBuffer.wrap(bytes, offset, length);
try {
if (nonblock) {
}
if (fd.chWrite == null) {
setErrno(Errno.EACCES);
return -1;
}
int written = fd.chWrite.write(tmp);
if (written == 0 && length > 0) {
if (nonblock) {
setErrno(Errno.EAGAIN);
return -1;
}
}
return written;
} catch (IOException ioe) {
setErrno(Helpers.errnoFromException(ioe));
error = ioe;
return -1;
}
}
private static final int NATIVE_EOF = 0;
private static final int JAVA_EOF = -1;
public int read(ChannelFD fd, byte[] target, int offset, int length, boolean nonblock) {
clear();
try {
if (nonblock) {
if (fd.chSelect != null) {
} else {
if (fd.chFile != null) {
long position = fd.chFile.position();
long size = fd.chFile.size();
if (position != -1 && size != -1 && position < size) {
} else {
setErrno(Errno.EAGAIN);
return -1;
}
} else if (fd.chNative != null && fd.isNativeFile) {
} else {
setErrno(Errno.EAGAIN);
return -1;
}
}
}
ByteBuffer buffer = ByteBuffer.wrap(target, offset, length);
int read = fd.chRead.read(buffer);
if (nonblock) {
if (read == JAVA_EOF) {
read = NATIVE_EOF;
} else if (read == 0) {
setErrno(Errno.EAGAIN);
return -1;
} else {
return read;
}
} else {
if (read == JAVA_EOF) read = NATIVE_EOF;
}
return read;
} catch (IOException ioe) {
setErrno(Helpers.errnoFromException(ioe));
return -1;
}
}
public int flock(ChannelFD fd, int lockMode) {
clear();
int real_fd = fd.realFileno;
if (posix.isNative() && real_fd != -1 && real_fd < FilenoUtil.FIRST_FAKE_FD && !Platform.IS_SOLARIS) {
int result = posix.flock(real_fd, lockMode);
if (result < 0) {
setErrno(Errno.valueOf(posix.errno()));
return -1;
}
return 0;
}
if (fd.chFile != null) {
int ret = checkSharedExclusive(fd, lockMode);
if (ret < 0) return ret;
if (!lockStateChanges(fd.currentLock.get(), lockMode)) return 0;
try {
synchronized (fd.chFile) {
if (!lockStateChanges(fd.currentLock.get(), lockMode)) return 0;
switch (lockMode) {
case LOCK_UN:
case LOCK_UN | LOCK_NB:
return unlock(fd);
case LOCK_EX:
return lock(fd, true);
case LOCK_EX | LOCK_NB:
return tryLock(fd, true);
case LOCK_SH:
return lock(fd, false);
case LOCK_SH | LOCK_NB:
return tryLock(fd, false);
}
}
} catch (IOException ioe) {
setErrno(Helpers.errnoFromException(ioe));
return -1;
} catch (OverlappingFileLockException ioe) {
setErrno(Errno.EINVAL);
errmsg = "overlapping file locks";
}
return lockFailedReturn(lockMode);
} else {
setErrno(Errno.EINVAL);
errmsg = "stream is not a file";
return -1;
}
}
public int dup2(ChannelFD filedes, ChannelFD filedes2) {
return filedes2.dup2From(posix, filedes);
}
public int close(ChannelFD fd) {
return close((Closeable)fd);
}
public int close(Closeable closeable) {
clear();
try {
closeable.close();
return 0;
} catch (IOException ioe) {
Errno errno = Helpers.errnoFromException(ioe);
if (errno == null) {
throw new RuntimeException("unknown IOException: " + ioe);
}
this.setErrno(errno);
return -1;
}
}
public Channel[] pipe() {
clear();
try {
Pipe pipe = Pipe.open();
Channel source = pipe.source(), sink = pipe.sink();
if (posix.isNative() && !Platform.IS_WINDOWS) {
int read = FilenoUtil.filenoFrom(source);
int write = FilenoUtil.filenoFrom(sink);
setCloexec(read, true);
setCloexec(write, true);
}
return new Channel[]{source, sink};
} catch (IOException ioe) {
setErrno(Helpers.errnoFromException(ioe));
return null;
}
}
public Errno getErrno() {
return errno.get();
}
public void setErrno(Errno errno) {
this.errno.set(errno);
}
public interface WaitMacros {
public abstract boolean WIFEXITED(long status);
public abstract boolean WIFSIGNALED(long status);
public abstract int WTERMSIG(long status);
public abstract int WEXITSTATUS(long status);
public abstract int WSTOPSIG(long status);
public abstract boolean WIFSTOPPED(long status);
public abstract boolean WCOREDUMP(long status);
}
public static class BSDWaitMacros implements WaitMacros {
public final long _WSTOPPED = 0177;
public final long WCOREFLAG = 0200;
public long _WSTATUS(long status) {
return status & _WSTOPPED;
}
public boolean WIFEXITED(long status) {
return _WSTATUS(status) == 0;
}
public boolean WIFSIGNALED(long status) {
return _WSTATUS(status) != _WSTOPPED && _WSTATUS(status) != 0;
}
public int WTERMSIG(long status) {
return (int)_WSTATUS(status);
}
public int WEXITSTATUS(long status) {
return (int)((status >>> 8) & 0xFF);
}
public int WSTOPSIG(long status) {
return (int)(status >>> 8);
}
public boolean WIFSTOPPED(long status) {
return _WSTATUS(status) == _WSTOPPED && WSTOPSIG(status) != 0x13;
}
public boolean WCOREDUMP(long status) {
return (status & WCOREFLAG) != 0;
}
}
public static class LinuxWaitMacros implements WaitMacros {
private int __WAIT_INT(long status) { return (int)status; }
private int __W_EXITCODE(int ret, int sig) { return (ret << 8) | sig; }
private int __W_STOPCODE(int sig) { return (sig << 8) | 0x7f; }
private static int __W_CONTINUED = 0xffff;
private static int __WCOREFLAG = 0x80;
private int __WEXITSTATUS(long status) { return (int)((status & 0xff00) >> 8); }
private int __WTERMSIG(long status) { return (int)(status & 0x7f); }
private int __WSTOPSIG(long status) { return __WEXITSTATUS(status); }
private boolean __WIFEXITED(long status) { return __WTERMSIG(status) == 0; }
private boolean __WIFSIGNALED(long status) {
return ((status & 0x7f) + 1) >> 1 > 0;
}
private boolean __WIFSTOPPED(long status) { return (status & 0xff) == 0x7f; }
private boolean __WCOREDUMP(long status) { return (status & __WCOREFLAG) != 0; }
public int WEXITSTATUS(long status) { return __WEXITSTATUS (__WAIT_INT (status)); }
public int WTERMSIG(long status) { return __WTERMSIG(__WAIT_INT(status)); }
public int WSTOPSIG(long status) { return __WSTOPSIG(__WAIT_INT(status)); }
public boolean WIFEXITED(long status) { return __WIFEXITED(__WAIT_INT(status)); }
public boolean WIFSIGNALED(long status) { return __WIFSIGNALED(__WAIT_INT(status)); }
public boolean WIFSTOPPED(long status) { return __WIFSTOPPED(__WAIT_INT(status)); }
public boolean WCOREDUMP(long status) { return __WCOREDUMP(__WAIT_INT(status)); }
}
public static final WaitMacros WAIT_MACROS;
static {
if (Platform.IS_BSD) {
WAIT_MACROS = new BSDWaitMacros();
} else {
WAIT_MACROS = new LinuxWaitMacros();
}
}
public int setCloexec(int fd, boolean cloexec) {
int ret = posix.fcntl(fd, Fcntl.F_GETFD);
if (ret == -1) {
setErrno(Errno.valueOf(posix.errno()));
return -1;
}
if (
(cloexec && (ret & FcntlLibrary.FD_CLOEXEC) == FcntlLibrary.FD_CLOEXEC)
|| (!cloexec && (ret & FcntlLibrary.FD_CLOEXEC) == 0)) {
return 0;
}
ret = cloexec ?
ret | FcntlLibrary.FD_CLOEXEC :
ret & ~FcntlLibrary.FD_CLOEXEC;
ret = posix.fcntlInt(fd, Fcntl.F_SETFD, ret);
if (ret == -1) setErrno(Errno.valueOf(posix.errno()));
return ret;
}
public int fcntlSetFD(int fd, int flags) {
int ret = posix.fcntlInt(fd, Fcntl.F_SETFD, flags);
if (ret == -1) setErrno(Errno.valueOf(posix.errno()));
return ret;
}
public int fcntlGetFD(int fd) {
int ret = posix.fcntl(fd, Fcntl.F_GETFD);
if (ret == -1) {
setErrno(Errno.valueOf(posix.errno()));
}
return ret;
}
public Channel open(String cwd, String path, int flags, int perm) {
if (Platform.IS_WINDOWS && (path.equals("/dev/null") || path.equalsIgnoreCase("nul"))) {
path = "NUL:";
}
try {
return JRubyFile.createResource(runtime, cwd, path).openChannel(flags, perm);
} catch (ResourceException.FileExists e) {
setErrno(Errno.EEXIST);
} catch (ResourceException.FileIsDirectory e) {
setErrno(Errno.EISDIR);
} catch (ResourceException.FileIsNotDirectory e) {
setErrno(Errno.ENOTDIR);
} catch (ResourceException.NotFound e) {
setErrno(Errno.ENOENT);
} catch (ResourceException.PermissionDenied e) {
setErrno(Errno.EACCES);
} catch (ResourceException.TooManySymlinks e) {
setErrno(Errno.ELOOP);
} catch (ResourceException ex) {
throw ex.newRaiseException(runtime);
} catch (IOException ex) {
throw runtime.newIOErrorFromException(ex);
}
return null;
}
public Channel open(String cwd, String path, ModeFlags flags, int perm) {
return open(cwd, path, flags, perm);
}
@Deprecated
public Channel open(String cwd, String path, ModeFlags flags, int perm, ClassLoader classLoader) {
if (path.startsWith("classpath:/") && classLoader != null) {
path = path.substring("classpath:/".length());
return Channels.newChannel(classLoader.getResourceAsStream(path));
}
return open(cwd, path, flags, perm);
}
public static int umask(POSIX posix) {
synchronized (_umaskLock) {
final int umask = posix.umask(_cachedUmask);
if (_cachedUmask != umask ) {
posix.umask(umask);
_cachedUmask = umask;
}
return umask;
}
}
public static int umask(POSIX posix, int newMask) {
int oldMask;
synchronized (_umaskLock) {
oldMask = posix.umask(newMask);
_cachedUmask = newMask;
}
return oldMask;
}
public int ftruncate(ChannelFD fd, long pos) {
if (fd.chNative != null) {
int ret = posix.ftruncate(fd.chNative.getFD(), pos);
if (ret == -1) setErrno(Errno.valueOf(posix.errno()));
return ret;
} else if (fd.chFile != null) {
try {
fd.chFile.truncate(pos);
} catch (IOException ioe) {
setErrno(Helpers.errnoFromException(ioe));
return -1;
}
} else {
setErrno(Errno.EINVAL);
return -1;
}
return 0;
}
public long size(ChannelFD fd) {
if (fd.chNative != null) {
FileStat stat = posix.allocateStat();
int ret = posix.fstat(fd.chNative.getFD(), stat);
if (ret == -1) {
setErrno(Errno.valueOf(posix.errno()));
return -1;
}
return stat.st_size();
} else if (fd.chSeek != null) {
try {
return fd.chSeek.size();
} catch (IOException ioe) {
setErrno(Helpers.errnoFromException(ioe));
return -1;
}
} else {
setErrno(Errno.EINVAL);
return -1;
}
}
private void clear() {
setErrno(null);
errmsg = null;
}
private int checkSharedExclusive(ChannelFD fd, int lockMode) {
if (fd.chWrite == null && (lockMode & LOCK_EX) > 0) {
setErrno(Errno.EINVAL);
errmsg = "cannot acquire exclusive lock on File not opened for write";
return -1;
}
if (fd.chRead == null && (lockMode & LOCK_SH) > 0) {
setErrno(Errno.EINVAL);
errmsg = "cannot acquire shared lock on File not opened for read";
return -1;
}
return 0;
}
private static int lockFailedReturn(int lockMode) {
return (lockMode & LOCK_EX) == 0 ? 0 : -1;
}
private static boolean lockStateChanges(FileLock lock, int lockMode) {
if (lock == null) {
switch (lockMode & 0xF) {
case LOCK_UN:
case LOCK_UN | LOCK_NB:
return false;
default:
return true;
}
} else {
switch (lockMode & 0xF) {
case LOCK_UN:
case LOCK_UN | LOCK_NB:
return true;
case LOCK_EX:
case LOCK_EX | LOCK_NB:
return lock.isShared();
case LOCK_SH:
case LOCK_SH | LOCK_NB:
return !lock.isShared();
default:
return false;
}
}
}
private int unlock(ChannelFD fd) throws IOException {
FileLock fileLock = fd.currentLock.get();
if (fileLock != null) {
fileLock.release();
fd.currentLock.remove();
return 0;
}
return -1;
}
private int lock(ChannelFD fd, boolean exclusive) throws IOException {
FileLock fileLock = fd.currentLock.get();
if (fileLock != null) fileLock.release();
fileLock = fd.chFile.lock(0L, Long.MAX_VALUE, !exclusive);
fd.currentLock.set(fileLock);
if (fileLock != null) {
return 0;
}
return lockFailedReturn(exclusive ? LOCK_EX : LOCK_SH);
}
private int tryLock(ChannelFD fd, boolean exclusive) throws IOException {
FileLock fileLock = fd.currentLock.get();
if (fileLock != null) fileLock.release();
fileLock = fd.chFile.tryLock(0L, Long.MAX_VALUE, !exclusive);
fd.currentLock.set(fileLock);
if (fileLock != null) {
return 0;
}
return lockFailedReturn(exclusive ? LOCK_EX : LOCK_SH);
}
public Throwable error;
private ThreadLocal<Errno> errno = new ThreadLocal<>();
public String errmsg;
private final POSIX posix;
private final Ruby runtime;
private static final Object _umaskLock = new Object();
private static int _cachedUmask = 0;
}